"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { Plus, Trash2, GripVertical } from "lucide-react"; import { toast } from "sonner"; import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; import { DietaryTagPicker } from "./dietary-tag-picker"; import { PhotoUploader, type PhotoEntry } from "./photo-uploader"; type DietaryTags = { vegan?: boolean; vegetarian?: boolean; glutenFree?: boolean; dairyFree?: boolean; nutFree?: boolean; halal?: boolean; kosher?: boolean; }; type IngredientRow = { id: string; rawName: string; quantity: string; unit: string; note: string; }; type StepRow = { id: string; instruction: string; timerSeconds: string; }; type RecipeFormProps = { recipeId?: string; defaultValues?: { title?: string; description?: string; baseServings?: number; visibility?: "private" | "unlisted" | "public"; difficulty?: "easy" | "medium" | "hard" | null; prepMins?: number | null; cookMins?: number | null; dietaryTags?: DietaryTags; ingredients?: IngredientRow[]; steps?: StepRow[]; photos?: PhotoEntry[]; }; }; function newIngredient(): IngredientRow { return { id: crypto.randomUUID(), rawName: "", quantity: "", unit: "", note: "" }; } function newStep(): StepRow { return { id: crypto.randomUUID(), instruction: "", timerSeconds: "" }; } export function RecipeForm({ recipeId, defaultValues }: RecipeFormProps) { const router = useRouter(); const t = useTranslations("recipeForm"); const t_recipe = useTranslations("recipe"); const t_common = useTranslations("common"); const isEdit = !!recipeId; const id = recipeId ?? crypto.randomUUID(); const [title, setTitle] = useState(defaultValues?.title ?? ""); const [description, setDescription] = useState(defaultValues?.description ?? ""); const [baseServings, setBaseServings] = useState(String(defaultValues?.baseServings ?? 4)); const [visibility, setVisibility] = useState<"private" | "unlisted" | "public">(defaultValues?.visibility ?? "private"); const [difficulty, setDifficulty] = useState<"easy" | "medium" | "hard" | "">(defaultValues?.difficulty ?? ""); const [prepMins, setPrepMins] = useState(String(defaultValues?.prepMins ?? "")); const [cookMins, setCookMins] = useState(String(defaultValues?.cookMins ?? "")); const [dietaryTags, setDietaryTags] = useState(defaultValues?.dietaryTags ?? {}); const [ingredients, setIngredients] = useState( defaultValues?.ingredients?.length ? defaultValues.ingredients : [newIngredient()] ); const [steps, setSteps] = useState( defaultValues?.steps?.length ? defaultValues.steps : [newStep()] ); const [photos, setPhotos] = useState(defaultValues?.photos ?? []); const [saving, setSaving] = useState(false); function updateIngredient(i: number, patch: Partial) { setIngredients((prev) => prev.map((row, idx) => idx === i ? { ...row, ...patch } : row)); } function removeIngredient(i: number) { setIngredients((prev) => prev.filter((_, idx) => idx !== i)); } function updateStep(i: number, patch: Partial) { setSteps((prev) => prev.map((row, idx) => idx === i ? { ...row, ...patch } : row)); } function removeStep(i: number) { setSteps((prev) => prev.filter((_, idx) => idx !== i)); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!title.trim()) { toast.error(t("titleRequired")); return; } setSaving(true); try { const payload = { title: title.trim(), description: description.trim() || undefined, baseServings: parseInt(baseServings) || 4, visibility, difficulty: difficulty || undefined, prepMins: prepMins ? parseInt(prepMins) : undefined, cookMins: cookMins ? parseInt(cookMins) : undefined, dietaryTags, ingredients: ingredients .filter((ing) => ing.rawName.trim()) .map((ing, i) => ({ rawName: ing.rawName.trim(), quantity: ing.quantity.trim() || undefined, unit: ing.unit.trim() || undefined, note: ing.note.trim() || undefined, order: i, })), steps: steps .filter((s) => s.instruction.trim()) .map((s, i) => ({ instruction: s.instruction.trim(), timerSeconds: s.timerSeconds ? parseInt(s.timerSeconds) : undefined, order: i, })), }; const url = isEdit ? `/api/v1/recipes/${id}` : "/api/v1/recipes"; const method = isEdit ? "PUT" : "POST"; const res = await fetch(url, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!res.ok) { const err = await res.json() as { error?: string }; toast.error(err.error ?? t("saveError")); return; } const saved = await res.json() as { id: string }; toast.success(isEdit ? t("updateSuccess") : t("createSuccess")); router.push(`/recipes/${saved.id}`); router.refresh(); } finally { setSaving(false); } } return (
{/* Basic info */}
setTitle(e.target.value)} placeholder={t("titlePlaceholder")} required />