"use client"; import { useEffect, useState } from "react"; import { useTranslations } from "next-intl"; import { toast } from "sonner"; import { FolderPlus, FolderOpen, Plus, Check } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; type CollectionSummary = { id: string; name: string }; export function AddToCollectionDialog({ open, onOpenChange, recipeIds, onDone, sourceCollectionId, }: { open: boolean; onOpenChange: (open: boolean) => void; recipeIds: string[]; onDone: () => void; /** When set, this is a "move" — after adding to the target, the recipes are removed from this collection. */ sourceCollectionId?: string; }) { const t = useTranslations("recipe"); const [collections, setCollections] = useState([]); const [loading, setLoading] = useState(false); const [creating, setCreating] = useState(false); const [newName, setNewName] = useState(""); const [showNewForm, setShowNewForm] = useState(false); const [busyId, setBusyId] = useState(null); useEffect(() => { if (!open) return; setShowNewForm(false); setNewName(""); setLoading(true); fetch("/api/v1/collections?limit=50") .then((res) => (res.ok ? res.json() : null)) .then((data: { data: CollectionSummary[] } | null) => { setCollections((data?.data ?? []).filter((c) => c.id !== sourceCollectionId)); }) .finally(() => setLoading(false)); }, [open, sourceCollectionId]); async function addTo(collectionId: string, collectionName: string) { setBusyId(collectionId); try { const res = await fetch(`/api/v1/collections/${collectionId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ addRecipeIds: recipeIds }), }); if (!res.ok) throw new Error(); if (sourceCollectionId) { await fetch(`/api/v1/collections/${sourceCollectionId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ removeRecipeIds: recipeIds }), }); } toast.success( sourceCollectionId ? t("moveToCollectionSuccess", { count: recipeIds.length, name: collectionName }) : t("addToCollectionSuccess", { count: recipeIds.length, name: collectionName }) ); onOpenChange(false); onDone(); } catch { toast.error(t("addToCollectionFailed")); } finally { setBusyId(null); } } async function createAndAdd() { if (!newName.trim()) return; setCreating(true); try { const createRes = await fetch("/api/v1/collections", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: newName.trim() }), }); if (!createRes.ok) throw new Error(); const { id } = (await createRes.json()) as { id: string }; await addTo(id, newName.trim()); } catch { toast.error(t("createCollectionFailed")); } finally { setCreating(false); } } return ( {sourceCollectionId ? t("moveToCollectionTitle", { count: recipeIds.length }) : t("addToCollectionTitle", { count: recipeIds.length })}
{loading ? (

) : (
{collections.length === 0 && !showNewForm && (

{t("noCollectionsYet")}

)} {collections.map((c) => ( ))}
)} {showNewForm ? (
setNewName(e.target.value)} placeholder={t("newCollectionNamePlaceholder")} onKeyDown={(e) => e.key === "Enter" && void createAndAdd()} disabled={creating} />
) : ( )}
); }