diff --git a/src/components/event-dialog.tsx b/src/components/event-dialog.tsx index 6d00141..f7aa174 100644 --- a/src/components/event-dialog.tsx +++ b/src/components/event-dialog.tsx @@ -9,437 +9,486 @@ import { RecurrencePicker } from "@/components/recurrence-picker"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, } from "@/components/ui/dialog"; -import { cn } from "@/lib/utils"; import { - Drawer, - DrawerContent, - DrawerDescription, - DrawerFooter, - DrawerHeader, - DrawerTitle, + Drawer, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, } from "@/components/ui/drawer"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { useIsMobile } from "@/hooks/use-mobile"; import { - type EventFormValues, - getDefaultEventFormValues, - validateEventFormValues, + type EventFormValues, + getDefaultEventFormValues, + validateEventFormValues, } from "@/lib/event-form"; import { parseRecurrenceRule, validateRecurrence } from "@/lib/recurrence"; +import { cn } from "@/lib/utils"; const STEP_FIELDS: Record<1 | 2 | 3, (keyof EventFormValues)[]> = { - 1: ["title", "url"], - 2: ["start", "end"], - 3: ["recurrenceRule"], + 1: ["title", "url"], + 2: ["start", "end"], + 3: ["recurrenceRule"], }; interface EventDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - editingId: string | null; - dialogSource: "manual" | "ai"; - initialValues: EventFormValues; - onSave: (values: EventFormValues) => void; - onReset: () => void; + open: boolean; + onOpenChange: (open: boolean) => void; + editingId: string | null; + dialogSource: "manual" | "ai"; + initialValues: EventFormValues; + onSave: (values: EventFormValues) => void; + onReset: () => void; } export const EventDialog = ({ - open, - onOpenChange, - editingId, - dialogSource, - initialValues, - onSave, - onReset, + open, + onOpenChange, + editingId, + dialogSource, + initialValues, + onSave, + onReset, }: EventDialogProps) => { - const isMobile = useIsMobile(); - const isAiDraft = dialogSource === "ai" && !editingId; - const titleText = editingId ? "Edit Event" : isAiDraft ? "Review AI Draft" : "New Event"; - const descriptionText = editingId - ? "Update the event details below. Title and start date are required." - : isAiDraft - ? "AI filled in this event from your prompt. Review each field, then save when it looks right." - : "Create an event manually. Title and start date are required."; - const saveLabel = editingId ? "Update Event" : "Save Event"; + const isMobile = useIsMobile(); + const isAiDraft = dialogSource === "ai" && !editingId; + const titleText = editingId + ? "Edit Event" + : isAiDraft + ? "Review AI Draft" + : "New Event"; + const descriptionText = editingId + ? "Update the event details below. Title and start date are required." + : isAiDraft + ? "AI filled in this event from your prompt. Review each field, then save when it looks right." + : "Create an event manually. Title and start date are required."; + const saveLabel = editingId ? "Update Event" : "Save Event"; - const { - control, - handleSubmit, - register, - reset, - setError, - setValue, - trigger, - watch, - formState: { errors }, - } = useForm({ - defaultValues: getDefaultEventFormValues(), - }); + const { + control, + handleSubmit, + register, + reset, + setError, + setValue, + trigger, + watch, + formState: { errors }, + } = useForm({ + defaultValues: getDefaultEventFormValues(), + }); - const [step, setStep] = useState<1 | 2 | 3>(1); - const advanceStep = () => setStep((s) => Math.min(s + 1, 3) as 1 | 2 | 3); - const retreatStep = () => setStep((s) => Math.max(s - 1, 1) as 1 | 2 | 3); + const [step, setStep] = useState<1 | 2 | 3>(1); + const advanceStep = () => setStep((s) => Math.min(s + 1, 3) as 1 | 2 | 3); + const retreatStep = () => setStep((s) => Math.max(s - 1, 1) as 1 | 2 | 3); - useEffect(() => { - reset(initialValues); - }, [initialValues, reset]); + useEffect(() => { + reset(initialValues); + }, [initialValues, reset]); - const handleOpenChange = (nextOpen: boolean) => { - if (!nextOpen) { - reset(getDefaultEventFormValues()); - setStep(1); - onReset(); - } - onOpenChange(nextOpen); - }; + const handleOpenChange = (nextOpen: boolean) => { + if (!nextOpen) { + reset(getDefaultEventFormValues()); + setStep(1); + onReset(); + } + onOpenChange(nextOpen); + }; - const handleNext = async () => { - const valid = await trigger(STEP_FIELDS[step]); - if (valid) advanceStep(); - }; + const handleNext = async () => { + const valid = await trigger(STEP_FIELDS[step]); + if (valid) advanceStep(); + }; - const handleSave = handleSubmit((values) => { - const result = validateEventFormValues(values); - if (!result.success) { - const fieldErrors = result.error.flatten().fieldErrors; - let firstErrorStep: 1 | 2 | 3 | null = null; - for (const [fieldName, messages] of Object.entries(fieldErrors)) { - const firstMessage = messages?.[0]; - if (firstMessage) { - setError(fieldName as keyof EventFormValues, { message: firstMessage }); - const ownerStep = ( - Object.entries(STEP_FIELDS) as [string, (keyof EventFormValues)[]][] - ).find(([, fields]) => fields.includes(fieldName as keyof EventFormValues))?.[0]; - const ownerStepNum = ownerStep ? (Number(ownerStep) as 1 | 2 | 3) : null; - if (ownerStepNum !== null && (firstErrorStep === null || ownerStepNum < firstErrorStep)) { - firstErrorStep = ownerStepNum; - } - } - } - if (firstErrorStep !== null) setStep(firstErrorStep); - return; - } + const handleSave = handleSubmit((values) => { + const result = validateEventFormValues(values); + if (!result.success) { + const fieldErrors = result.error.flatten().fieldErrors; + let firstErrorStep: 1 | 2 | 3 | null = null; + for (const [fieldName, messages] of Object.entries(fieldErrors)) { + const firstMessage = messages?.[0]; + if (firstMessage) { + setError(fieldName as keyof EventFormValues, { + message: firstMessage, + }); + const ownerStep = ( + Object.entries(STEP_FIELDS) as [string, (keyof EventFormValues)[]][] + ).find(([, fields]) => + fields.includes(fieldName as keyof EventFormValues), + )?.[0]; + const ownerStepNum = ownerStep + ? (Number(ownerStep) as 1 | 2 | 3) + : null; + if ( + ownerStepNum !== null && + (firstErrorStep === null || ownerStepNum < firstErrorStep) + ) { + firstErrorStep = ownerStepNum; + } + } + } + if (firstErrorStep !== null) setStep(firstErrorStep); + return; + } - if (values.recurrenceRule) { - const recurrenceValidation = validateRecurrence(parseRecurrenceRule(values.recurrenceRule)); - if (!recurrenceValidation.isValid) { - setError("recurrenceRule", { - message: - recurrenceValidation.errors.rule || - recurrenceValidation.errors.count || - recurrenceValidation.errors.until || - "Invalid recurrence.", - }); - setStep(3); - return; - } - } + if (values.recurrenceRule) { + const recurrenceValidation = validateRecurrence( + parseRecurrenceRule(values.recurrenceRule), + ); + if (!recurrenceValidation.isValid) { + setError("recurrenceRule", { + message: + recurrenceValidation.errors.rule || + recurrenceValidation.errors.count || + recurrenceValidation.errors.until || + "Invalid recurrence.", + }); + setStep(3); + return; + } + } - onSave(result.data); - reset(getDefaultEventFormValues()); - }); + onSave(result.data); + reset(getDefaultEventFormValues()); + }); - const stepProps = { control, register, errors, watch, setValue, isAiDraft }; + const stepProps = { control, register, errors, watch, setValue, isAiDraft }; - if (!isMobile) { - return ( - - - - {titleText} - {descriptionText} - -
- {isAiDraft && } -
-

Event details

- -
-
-

Schedule

- -
-
-

Recurrence

- -
- - - - - -
-
- ); - } + if (!isMobile) { + return ( + + + + + {titleText} + + {descriptionText} + +
+ {isAiDraft && } +
+

+ Event details +

+ +
+
+

+ Schedule +

+ +
+
+

+ Recurrence +

+ +
+ + + + + +
+
+ ); + } - const progressBars = ( -
- {([1, 2, 3] as const).map((n) => ( -
- ))} -
- ); + const progressBars = ( +
+ {([1, 2, 3] as const).map((n) => ( +
+ ))} +
+ ); - const stepTitles: Record<1 | 2 | 3, string> = { - 1: "Event Details", - 2: "Schedule", - 3: "Recurrence", - }; + const stepTitles: Record<1 | 2 | 3, string> = { + 1: "Event Details", + 2: "Schedule", + 3: "Recurrence", + }; - return ( - - - -
-
- {stepTitles[step]} - - {step === 1 && "Title and start date are required."} - {step === 2 && "Set your event's date and time."} - {step === 3 && "Optionally repeat this event."} - -
- - {step} / 3 - -
-
- {progressBars} -
-
- {step === 1 && } - {step === 2 && } - {step === 3 && } -
- - - {step < 3 ? ( - - ) : ( - - )} - -
-
-
- ); + return ( + + + +
+
+ {stepTitles[step]} + + {step === 1 && "Title and start date are required."} + {step === 2 && "Set your event's date and time."} + {step === 3 && "Optionally repeat this event."} + +
+ + {step} / 3 + +
+
+ {progressBars} +
+
+ {step === 1 && } + {step === 2 && } + {step === 3 && } +
+ + + {step < 3 ? ( + + ) : ( + + )} + +
+
+
+ ); }; interface StepProps { - control: ReturnType>["control"]; - register: ReturnType>["register"]; - errors: ReturnType>["formState"]["errors"]; - watch: ReturnType>["watch"]; - setValue: ReturnType>["setValue"]; - isAiDraft: boolean; + control: ReturnType>["control"]; + register: ReturnType>["register"]; + errors: ReturnType>["formState"]["errors"]; + watch: ReturnType>["watch"]; + setValue: ReturnType>["setValue"]; + isAiDraft: boolean; } function AiDraftBanner() { - return ( -
- This draft was generated from natural language. Double-check dates, times, location, - recurrence, and links before saving. -
- ); + return ( +
+ This draft was generated from natural language. Double-check dates, times, + location, recurrence, and links before saving. +
+ ); } function DetailsStep({ - control, - register, - errors, - isAiDraft, + control, + register, + errors, + isAiDraft, }: Omit) { - const isMobile = useIsMobile(); - return ( -
- {isAiDraft && } -
- - - {errors.title &&

{errors.title.message}

} -
-
- -