import { useActionMutation } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import { zodResolver } from "@hookform/resolvers/zod"; import type { Meal } from "@shared/types"; import { IconPlus, IconChevronDown } from "@tabler/icons-react"; import { useState, useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { formatLocalDate } from "@/lib/utils"; const formSchema = z.object({ name: z.string().min(1, "Name is required"), calories: z.string().transform((val) => parseInt(val, 10)), protein: z .string() .optional() .transform((val) => (val ? parseInt(val, 10) : 0)), carbs: z .string() .optional() .transform((val) => (val ? parseInt(val, 10) : 0)), fat: z .string() .optional() .transform((val) => (val ? parseInt(val, 10) : 0)), date: z.string(), notes: z.string().optional(), }); type FormData = z.input; interface AddMealDialogProps { editingMeal?: Meal | null; onOpenChange?: (open: boolean) => void; isOpen?: boolean; currentDate?: Date; } export function AddMealDialog({ editingMeal, onOpenChange, isOpen: controlledOpen, currentDate = new Date(), }: AddMealDialogProps) { const t = useT(); const [uncontrolledOpen, setUncontrolledOpen] = useState(false); const open = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen; const setOpen = controlledOpen !== undefined ? (v: boolean) => onOpenChange?.(v) : setUncontrolledOpen; const [showMacros, setShowMacros] = useState(false); const isEditing = !!editingMeal; const form = useForm({ // zod resolves at multiple minor versions across the workspace; cast the // schema so @hookform/resolvers v5's zod-v4 overload doesn't reject a // v4.3-internal schema under CI's frozen lockfile. Runtime is unaffected. resolver: zodResolver(formSchema as any) as any, defaultValues: { name: editingMeal?.name || "", calories: editingMeal?.calories.toString() || "", protein: editingMeal?.protein?.toString() || "", carbs: editingMeal?.carbs?.toString() || "", fat: editingMeal?.fat?.toString() || "", date: editingMeal?.date || formatLocalDate(currentDate), notes: editingMeal?.notes || "", }, }); const createMutation = useActionMutation("log-meal", { onSuccess: () => { toast.success(t("meals.added")); setOpen(false); form.reset(); setShowMacros(false); }, onError: () => toast.error(t("meals.addFailed")), }); const updateMutation = useActionMutation("update-meal", { onSuccess: () => { toast.success(t("meals.updated")); setOpen(false); form.reset(); setShowMacros(false); }, onError: () => toast.error(t("meals.updateFailed")), }); const onSubmit = (data: FormData) => { const date = isEditing ? editingMeal!.date : formatLocalDate(currentDate); if (isEditing) { updateMutation.mutate({ id: String(editingMeal!.id), name: data.name, calories: String(data.calories), protein: data.protein ? String(data.protein) : undefined, carbs: data.carbs ? String(data.carbs) : undefined, fat: data.fat ? String(data.fat) : undefined, date, }); } else { createMutation.mutate({ name: data.name, calories: String(data.calories), protein: data.protein ? String(data.protein) : undefined, carbs: data.carbs ? String(data.carbs) : undefined, fat: data.fat ? String(data.fat) : undefined, date, }); } }; const handleOpenChange = (newOpen: boolean) => { setOpen(newOpen); onOpenChange?.(newOpen); if (!newOpen) { form.reset(); setShowMacros(false); } }; useEffect(() => { if (editingMeal) { form.reset({ name: editingMeal.name, calories: editingMeal.calories.toString(), protein: editingMeal.protein?.toString() || "", carbs: editingMeal.carbs?.toString() || "", fat: editingMeal.fat?.toString() || "", date: editingMeal.date, notes: editingMeal.notes || "", }); setShowMacros( (editingMeal.protein ?? 0) > 0 || (editingMeal.carbs ?? 0) > 0 || (editingMeal.fat ?? 0) > 0, ); } }, [editingMeal, form]); return ( {!isEditing && ( )} {isEditing ? t("meals.edit") : t("meals.addNew")} {isEditing ? t("meals.updateDescription") : t("meals.logDescription")}
{form.formState.errors.name && (

{form.formState.errors.name.message}

)}
{form.formState.errors.calories && (

{form.formState.errors.calories.message}

)}
{showMacros && (

{t("common.optional")}

)}
); }