"use client"; import { useState, useCallback } from "react"; import { Trash2, Globe, Lock, Link2, X, Check, ListChecks } from "lucide-react"; import { Button, buttonVariants } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { toast } from "sonner"; import { getPublicUrl } from "@/lib/storage"; import { Badge } from "@/components/ui/badge"; import { Clock, Users } from "lucide-react"; import Link from "next/link"; import { cn } from "@/lib/utils"; type Recipe = { id: string; title: string; description: string | null; baseServings: number; prepMins: number | null; cookMins: number | null; difficulty: "easy" | "medium" | "hard" | null; visibility: "private" | "unlisted" | "public"; tags: string[]; updatedAt: Date; photos?: Array<{ storageKey: string; isCover: boolean }>; }; const DIFFICULTY_COLOR = { easy: "default", medium: "secondary", hard: "destructive" } as const; const VISIBILITY_ICON = { private: Lock, unlisted: Link2, public: Globe }; function SelectableRecipeCard({ recipe, selected, selectMode, onToggle, }: { recipe: Recipe; selected: boolean; selectMode: boolean; onToggle: (id: string) => void; }) { const cover = recipe.photos?.find((p) => p.isCover) ?? recipe.photos?.[0]; const totalMins = (recipe.prepMins ?? 0) + (recipe.cookMins ?? 0); const VisibilityIcon = VISIBILITY_ICON[recipe.visibility]; const inner = (
{/* Cover image */}
{cover ? ( {recipe.title} ) : (
🍽️
)} {/* Selection overlay tint */} {selectMode && selected && (
)} {/* Checkbox */} {selectMode ? (
{selected && }
) : null}
{/* Body */}

{recipe.title}

{recipe.description && (

{recipe.description}

)} {recipe.tags.length > 0 && (
{recipe.tags.slice(0, 3).map((tag) => ( {tag} ))} {recipe.tags.length > 3 && ( +{recipe.tags.length - 3} )}
)}
{/* Footer */}
{recipe.baseServings} {totalMins > 0 && ( {totalMins}m )} {recipe.difficulty && ( {recipe.difficulty} )}
); if (selectMode) { return (
onToggle(recipe.id)} role="checkbox" aria-checked={selected} className="h-full" > {inner}
); } return ( {inner} ); } export function RecipesGrid({ recipes: initialRecipes }: { recipes: Recipe[] }) { const [recipes, setRecipes] = useState(initialRecipes); const [selectMode, setSelectMode] = useState(false); const [selected, setSelected] = useState>(new Set()); const [busy, setBusy] = useState(false); const toggleSelect = useCallback((id: string) => { setSelected((prev) => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next; }); }, []); const toggleAll = () => { setSelected((prev) => prev.size === recipes.length ? new Set() : new Set(recipes.map((r) => r.id)) ); }; const exitSelect = () => { setSelectMode(false); setSelected(new Set()); }; async function bulkDelete() { if (!confirm(`Delete ${selected.size} recipe${selected.size !== 1 ? "s" : ""}? This cannot be undone.`)) return; setBusy(true); try { const res = await fetch("/api/v1/recipes/bulk", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids: [...selected] }), }); if (!res.ok) throw new Error("Delete failed"); setRecipes((prev) => prev.filter((r) => !selected.has(r.id))); toast.success(`${selected.size} recipe${selected.size !== 1 ? "s" : ""} deleted`); exitSelect(); } catch { toast.error("Delete failed"); } finally { setBusy(false); } } async function bulkSetVisibility(visibility: "private" | "unlisted" | "public") { setBusy(true); try { const res = await fetch("/api/v1/recipes/bulk", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids: [...selected], visibility }), }); if (!res.ok) throw new Error("Update failed"); setRecipes((prev) => prev.map((r) => selected.has(r.id) ? { ...r, visibility } : r) ); toast.success(`${selected.size} recipe${selected.size !== 1 ? "s" : ""} set to ${visibility}`); exitSelect(); } catch { toast.error("Update failed"); } finally { setBusy(false); } } if (recipes.length === 0) return null; const allSelected = selected.size === recipes.length; return (
{/* Toolbar */}
{selectMode ? (
{selected.size > 0 ? `${selected.size} selected` : "Click cards to select"}
) : (
)}
{/* Grid */}
{recipes.map((recipe) => ( ))}
{/* Floating bulk action bar */} {selectMode && selected.size > 0 && (
{selected.size} selected
Visibility { void bulkSetVisibility("public"); }}> Make public { void bulkSetVisibility("unlisted"); }}> Make unlisted { void bulkSetVisibility("private"); }}> Make private
)}
); }