diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index a32c590f4438..ba3fff7a01ef 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -45,6 +45,7 @@ import { } from './hooks/useAgent'; import { useNavigation } from './hooks/useNavigation'; import Pair2 from './components/Pair2'; +import { errorMessage } from './utils/conversionUtils'; // Route Components const HubRouteWrapper = ({ @@ -580,7 +581,7 @@ export function AppInner() { }, [navigate]); if (fatalError) { - return ; + return ; } return ( diff --git a/ui/desktop/src/components/ErrorBoundary.tsx b/ui/desktop/src/components/ErrorBoundary.tsx index 5404a2768d4f..31101bca5723 100644 --- a/ui/desktop/src/components/ErrorBoundary.tsx +++ b/ui/desktop/src/components/ErrorBoundary.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Button } from './ui/button'; import { AlertTriangle } from 'lucide-react'; +import { errorMessage } from '../utils/conversionUtils'; // Capture unhandled promise rejections window.addEventListener('unhandledrejection', (event) => { @@ -14,7 +15,7 @@ window.addEventListener('error', (event) => { ); }); -export function ErrorUI({ error }: { error: Error }) { +export function ErrorUI({ error }: { error: string }) { return (
@@ -35,7 +36,7 @@ export function ErrorUI({ error }: { error: Error }) { )}
-          {error.message}
+          {error}
         
@@ -64,7 +65,7 @@ export class ErrorBoundary extends React.Component< render() { if (this.state.hasError) { - return ; + return ; } return this.props.children; } diff --git a/ui/desktop/src/components/schedule/CronPicker.tsx b/ui/desktop/src/components/schedule/CronPicker.tsx index 213081043ff8..4e164d566cd5 100644 --- a/ui/desktop/src/components/schedule/CronPicker.tsx +++ b/ui/desktop/src/components/schedule/CronPicker.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import cronstrue from 'cronstrue'; import { ScheduledJob } from '../../schedule'; +import { errorMessage } from '../../utils/conversionUtils'; type Period = 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'; @@ -17,6 +18,7 @@ type ParsedCron = { interface CronPickerProps { schedule: ScheduledJob | null; onChange: (cron: string) => void; + isValid: (valid: boolean) => void; } const parseCron = (cron: string): ParsedCron => { @@ -76,16 +78,16 @@ const to12Hour = (hour24: number): { hour: number; isPM: boolean } => { return { hour: hour24, isPM: false }; }; -export const CronPicker: React.FC = ({ schedule, onChange }) => { +export const CronPicker: React.FC = ({ schedule, onChange, isValid }) => { const [period, setPeriod] = useState('day'); const [second, setSecond] = useState('0'); const [minute, setMinute] = useState('0'); const [hour12, setHour12] = useState(2); - const [currentCron, setCurrentCron] = useState(''); const [isPM, setIsPM] = useState(true); const [dayOfWeek, setDayOfWeek] = useState('1'); const [dayOfMonth, setDayOfMonth] = useState('1'); const [month, setMonth] = useState('1'); + const [readableCron, setReadableCron] = useState(''); useEffect(() => { const parsed = parseCron(schedule?.cron || ''); @@ -128,16 +130,18 @@ export const CronPicker: React.FC = ({ schedule, onChange }) => cron = '0 0 0 * * *'; } onChange(cron); - setCurrentCron(cron); - }, [period, second, minute, hour12, isPM, dayOfWeek, dayOfMonth, month, onChange]); - - const getReadable = () => { - if (!currentCron) { - return ''; + if (cron) { + const cronWithoutSeconds = cron.split(' ').slice(1).join(' '); + try { + setReadableCron(cronstrue.toString(cronWithoutSeconds)); + isValid(true); + } catch (e) { + isValid(false); + setReadableCron('error: ' + errorMessage(e)); + } } - const cronWithoutSeconds = currentCron.split(' ').slice(1).join(' '); - return cronstrue.toString(cronWithoutSeconds); - }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [period, second, minute, hour12, isPM, dayOfWeek, dayOfMonth, month]); const selectClassName = 'px-2 py-1 border rounded bg-white dark:bg-gray-800 dark:border-gray-600'; @@ -277,7 +281,7 @@ export const CronPicker: React.FC = ({ schedule, onChange }) => )}
-
{getReadable()}
+
{readableCron}
); }; diff --git a/ui/desktop/src/components/schedule/ScheduleModal.tsx b/ui/desktop/src/components/schedule/ScheduleModal.tsx index e6c25935ba26..ef5a1bae60b4 100644 --- a/ui/desktop/src/components/schedule/ScheduleModal.tsx +++ b/ui/desktop/src/components/schedule/ScheduleModal.tsx @@ -212,6 +212,7 @@ export const ScheduleModal: React.FC = ({ const [parsedRecipe, setParsedRecipe] = useState(null); const [cronExpression, setCronExpression] = useState('0 0 14 * * *'); const [internalValidationError, setInternalValidationError] = useState(null); + const [isValid, setIsValid] = useState(true); const handleDeepLinkChange = useCallback(async (value: string) => { setDeepLinkInput(value); @@ -467,7 +468,7 @@ export const ScheduleModal: React.FC = ({
- +
@@ -484,7 +485,7 @@ export const ScheduleModal: React.FC = ({