"use client"; import { useEffect, useState } from "react"; import { useTranslations } from "next-intl"; import { toast } from "sonner"; import { ShoppingCart, Check, X, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Input } from "@/components/ui/input"; export type ShoppingListProposal = { items: Array<{ rawName: string; quantity?: number; unit?: string }>; suggestedListName?: string; }; type ShoppingList = { id: string; name: string }; const NEW_LIST_VALUE = "__new__"; /** * Renders a pending addToShoppingList tool proposal — lets the user pick an * existing list or name a new one, then confirms through the exact same * two-endpoint flow AddToShoppingListButton already uses (create list if * needed, then POST items) rather than a new code path. */ export function ShoppingListProposalCard({ proposal, status, onStatusChange, }: { proposal: ShoppingListProposal; status: "pending" | "creating" | "created" | "discarded"; onStatusChange: (status: "pending" | "creating" | "created" | "discarded") => void; }) { const t = useTranslations("recipe"); const tShopping = useTranslations("shoppingLists"); const tChat = useTranslations("cookingChat"); const [lists, setLists] = useState(null); const [targetListId, setTargetListId] = useState(NEW_LIST_VALUE); const [newListName, setNewListName] = useState(proposal.suggestedListName || "Shopping List"); useEffect(() => { if (status !== "pending" || lists !== null) return; fetch("/api/v1/shopping-lists") .then((res) => (res.ok ? (res.json() as Promise) : [])) .then((data) => { setLists(data); if (data.length > 0) setTargetListId(data[0]!.id); }) .catch(() => setLists([])); }, [status, lists]); async function handleConfirm() { onStatusChange("creating"); try { let listId = targetListId; if (listId === NEW_LIST_VALUE) { const res = await fetch("/api/v1/shopping-lists", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: newListName.trim() || "Shopping List" }), }); if (!res.ok) throw new Error(); listId = (await res.json() as { id: string }).id; } const res = await fetch(`/api/v1/shopping-lists/${listId}/items`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items: proposal.items.map((item) => ({ rawName: item.rawName, quantity: item.quantity !== undefined ? String(item.quantity) : undefined, unit: item.unit, })), }), }); if (!res.ok) throw new Error(); onStatusChange("created"); toast.success(tShopping("addedCount", { count: proposal.items.length })); } catch { onStatusChange("pending"); toast.error(t("shoppingListAddFailed")); } } return (
{tChat("shoppingProposalTitle", { count: proposal.items.length })}
    {proposal.items.slice(0, 5).map((item, i) => (
  • {item.quantity ? `${item.quantity} ` : ""}{item.unit ? `${item.unit} ` : ""}{item.rawName}
  • ))} {proposal.items.length > 5 &&
  • {tChat("shoppingProposalMore", { count: proposal.items.length - 5 })}
  • }
{status === "created" ? (

{tChat("proposalCreated")}

) : status === "discarded" ? (

{tChat("proposalDiscarded")}

) : ( <> {lists !== null && (
{targetListId === NEW_LIST_VALUE && ( setNewListName(e.target.value)} placeholder={tShopping("listNamePlaceholder")} className="h-7 text-xs" maxLength={100} /> )}
)}
)}
); }