"use client"; import { useState, useEffect } from "react"; import { useTranslations } from "next-intl"; import { ShoppingCart, Loader2, Plus, 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 { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Separator } from "@/components/ui/separator"; type Ingredient = { rawName: string; quantity?: string | number | null; unit?: string | null; }; type ShoppingList = { id: string; name: string }; function scaleQty(quantity: string | number | null | undefined, scale: number): string | undefined { if (!quantity) return undefined; const n = typeof quantity === "number" ? quantity : parseFloat(quantity); if (isNaN(n)) return String(quantity); if (n === 0) return undefined; const scaled = n * scale; return scaled % 1 === 0 ? String(scaled) : scaled.toFixed(2).replace(/\.?0+$/, ""); } export function AddToShoppingListButton({ recipeId, recipeTitle, baseServings, ingredients, }: { recipeId: string; recipeTitle: string; baseServings: number; ingredients: Ingredient[]; }) { const t = useTranslations("recipe"); const tShopping = useTranslations("shoppingLists"); const tCommon = useTranslations("common"); const tForm = useTranslations("recipeForm"); const [open, setOpen] = useState(false); const [lists, setLists] = useState([]); const [loadingLists, setLoadingLists] = useState(false); const [mode, setMode] = useState<"existing" | "new">("existing"); const [selectedListId, setSelectedListId] = useState(""); const [newListName, setNewListName] = useState(`${recipeTitle} shopping`); const [servings, setServings] = useState(baseServings); const [adding, setAdding] = useState(false); useEffect(() => { if (!open) return; setLoadingLists(true); fetch("/api/v1/shopping-lists") .then((r) => r.json() as Promise) .then((data) => { setLists(data); if (data.length > 0) { setMode("existing"); setSelectedListId(data[0]!.id); } else { setMode("new"); } }) .catch(() => setMode("new")) .finally(() => setLoadingLists(false)); }, [open]); const scale = servings / baseServings; const items = ingredients.map((ing) => ({ rawName: ing.rawName, quantity: scaleQty(ing.quantity, scale), unit: ing.unit ?? undefined, })); async function handleAdd() { setAdding(true); try { let listId = selectedListId; if (mode === "new") { const res = await fetch("/api/v1/shopping-lists", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: newListName.trim() || recipeTitle }), }); if (!res.ok) { toast.error(t("shoppingListCreateFailed")); return; } const created = await res.json() as { id: string }; listId = created.id; } const res = await fetch(`/api/v1/shopping-lists/${listId}/items`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items }), }); if (!res.ok) { toast.error(t("shoppingListAddFailed")); return; } toast.success(tShopping("addedCount", { count: items.length })); setOpen(false); } finally { setAdding(false); } } return ( <> setOpen(true)} aria-label={tShopping("addToList")}> } /> {tShopping("addToList")} {tShopping("dialogTitle")} {tShopping.rich("dialogDescription", { title: recipeTitle, strong: (chunks) => {chunks}, })}
{/* Servings scaler */}
{servings}
{scale !== 1 && ( ({scale > 1 ? "×" : "÷"}{Math.abs(scale) !== 1 ? (scale > 1 ? scale.toFixed(2).replace(/\.?0+$/, "") : (1 / scale).toFixed(2).replace(/\.?0+$/, "")) : ""} {tShopping("scaledSuffix")} )}
{/* List picker */} {loadingLists ? (
{tShopping("loadingLists")}
) : (
{lists.length > 0 && ( setMode(v as "existing" | "new")}>
{mode === "existing" && (
{lists.map((list) => ( ))}
)}
)} {(mode === "new" || lists.length === 0) && (
setNewListName(e.target.value)} placeholder={tShopping("listNamePlaceholder")} />
)}
)}

{tShopping("ingredientsWillBeAdded", { count: items.length })}

); }