"use client"; import { useState } from "react"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { FakeProgressBar } from "@/components/ui/fake-progress-bar"; import { UtensilsCrossed, Sparkles, Loader2, ChefHat, Salad, Wine, Cake, Sandwich, Soup, Check } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { cn } from "@/lib/utils"; type Pairing = { name: string; role: "starter" | "side" | "salad" | "bread" | "drink" | "dessert" | "sauce"; description: string; whyItPairs: string; difficulty: "easy" | "medium" | "hard"; prepTimeMins?: number; }; const ROLE_ICON: Record = { starter: Soup, side: ChefHat, salad: Salad, bread: Sandwich, drink: Wine, dessert: Cake, sauce: ChefHat, }; const ROLE_LABEL: Record = { starter: "Starter", side: "Side", salad: "Salad", bread: "Bread", drink: "Drink", dessert: "Dessert", sauce: "Sauce", }; const DIFFICULTY_VARIANT = { easy: "default", medium: "secondary", hard: "destructive", } as const; export function MealPairingButton({ recipeId }: { recipeId: string }) { const t = useTranslations("recipe"); const router = useRouter(); const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); const [selected, setSelected] = useState>(new Set()); const [generatingProgress, setGeneratingProgress] = useState<{ current: number; total: number } | null>(null); const [pairings, setPairings] = useState([]); async function suggest() { setLoading(true); setPairings([]); setSelected(new Set()); try { const res = await fetch(`/api/v1/ai/pairings/${recipeId}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ count: 4 }), }); if (!res.ok) { toast.error(t("pairingMealFailed")); return; } const data = await res.json() as { pairings: Pairing[] }; setPairings(data.pairings); } finally { setLoading(false); } } function toggleSelect(i: number) { setSelected((prev) => { const next = new Set(prev); next.has(i) ? next.delete(i) : next.add(i); return next; }); } async function generateSelected() { const indices = Array.from(selected).sort(); setGeneratingProgress({ current: 0, total: indices.length }); const savedIds: string[] = []; for (let n = 0; n < indices.length; n++) { const pairing = pairings[indices[n]!]!; setGeneratingProgress({ current: n + 1, total: indices.length }); try { const genRes = await fetch("/api/v1/ai/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: pairing.name }), }); if (!genRes.ok) { toast.error(t("pairingGenerateFailed", { name: pairing.name })); continue; } const generated = await genRes.json() as { title: string; description?: string; baseServings?: number; prepMins?: number; cookMins?: number; difficulty?: string; dietaryTags?: Record; ingredients: Array<{ rawName: string; quantity?: string; unit?: string; note?: string }>; steps: Array<{ instruction: string; timerSeconds?: number }>; }; const saveRes = await fetch("/api/v1/recipes", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...generated, visibility: "private", aiGenerated: true }), }); if (!saveRes.ok) { toast.error(t("pairingSaveFailed", { name: pairing.name })); continue; } const saved = await saveRes.json() as { id: string }; savedIds.push(saved.id); } catch { toast.error(t("pairingGenerateFailed", { name: pairing.name })); } } setGeneratingProgress(null); setSelected(new Set()); setOpen(false); if (savedIds.length === 1) { toast.success(t("pairingSuccess")); router.push(`/recipes/${savedIds[0]}/edit`); } else if (savedIds.length > 1) { toast.success(t("pairingBulkSuccess", { count: savedIds.length })); router.push("/recipes"); } } return ( <> { setOpen(true); if (pairings.length === 0) suggest(); }}> } /> {t("pairMealTooltip")} Complete the meal AI-suggested dishes that pair well with this recipe. Generate any of them as a new recipe. {loading ? (
) : pairings.length === 0 ? (
) : (
{pairings.map((pairing, i) => { const Icon = ROLE_ICON[pairing.role]; const isSelected = selected.has(i); return (
!generatingProgress && toggleSelect(i)} className={cn( "rounded-lg border p-4 cursor-pointer transition-colors", isSelected ? "border-primary bg-primary/5" : "hover:border-muted-foreground/40" )} >
{isSelected && }
{pairing.name} {ROLE_LABEL[pairing.role]} {pairing.difficulty} {pairing.prepTimeMins && ( {pairing.prepTimeMins}m )}

{pairing.description}

“{pairing.whyItPairs}”

{i < pairings.length - 1 && }
); })} {generatingProgress && ( )}
)}
); }