import { CronExpressionParser } from "cron-parser"; import { AlertCircle, CheckCircle2 } from "lucide-react"; import { useMemo } from "react"; import { FormControl, FormDescription, FormItem, FormLabel, FormMessage } from "~/client/components/ui/form"; import { Input } from "~/client/components/ui/input"; import { useTimeFormat } from "~/client/lib/datetime"; import { cn } from "~/client/lib/utils"; interface CronInputProps { value: string; onChange: (value: string) => void; error?: string; } export function CronInput({ value, onChange, error }: CronInputProps) { const { formatDateTime } = useTimeFormat(); const { isValid, nextRuns, parseError } = useMemo(() => { if (!value) { return { isValid: false, nextRuns: [], parseError: null }; } const parts = value.trim().split(/\s+/); if (parts.length !== 5) { return { isValid: false, nextRuns: [], parseError: "Expression must have exactly 5 fields (minute, hour, day, month, day-of-week)", }; } try { const interval = CronExpressionParser.parse(value); const runs: Date[] = []; for (let i = 0; i < 5; i++) { runs.push(interval.next().toDate()); } return { isValid: true, nextRuns: runs, parseError: null }; } catch (e) { return { isValid: false, nextRuns: [], parseError: (e as Error).message }; } }, [value]); return ( Cron expression
onChange(e.target.value)} className={cn("font-mono", { "border-destructive": error || (value && !isValid), })} />
{value && (
{isValid ? ( ) : ( )}
)}
Standard cron format: minute hour day month day-of-week. {value && !isValid && parseError &&

{parseError}

} {isValid && nextRuns.length > 0 && (

Next 5 executions:

)}
); }