"use client"; import { useState } from "react"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { History, RotateCcw, ChevronDown, GitCompare } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Sheet, SheetContent, SheetHeader, SheetTitle, } from "@/components/ui/sheet"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { VersionDiffView, type DiffSnapshot } from "@/components/recipe/version-diff-view"; type VersionSummary = { id: string; version: number; title: string; createdAt: string; }; type SnapshotData = DiffSnapshot & { description?: string | null; baseServings?: number; difficulty?: string | null; prepMins?: number | null; cookMins?: number | null; dietaryTags?: Record; }; type VersionDetail = { id: string; version: number; title: string; createdAt: string; snapshotData: SnapshotData; }; function formatDate(dateStr: string): string { const date = new Date(dateStr); return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); } export function VersionHistoryButton({ recipeId, currentSnapshot, }: { recipeId: string; currentSnapshot: DiffSnapshot; }) { const t = useTranslations("recipe"); const tForm = useTranslations("recipeForm"); const router = useRouter(); const [open, setOpen] = useState(false); const [versions, setVersions] = useState([]); const [loading, setLoading] = useState(false); const [expandedId, setExpandedId] = useState(null); const [expandedData, setExpandedData] = useState>({}); const [restoringId, setRestoringId] = useState(null); const [comparingId, setComparingId] = useState(null); async function handleOpen(isOpen: boolean) { setOpen(isOpen); if (isOpen) { setLoading(true); try { const res = await fetch(`/api/v1/recipes/${recipeId}/versions`); if (res.ok) { const data = await res.json() as VersionSummary[]; setVersions(data); } } finally { setLoading(false); } } } async function ensureDetail(versionId: string): Promise { if (expandedData[versionId]) return expandedData[versionId]!; const res = await fetch(`/api/v1/recipes/${recipeId}/versions/${versionId}`); if (!res.ok) return null; const data = await res.json() as VersionDetail; setExpandedData((prev) => ({ ...prev, [versionId]: data })); return data; } async function handleExpand(version: VersionSummary) { if (expandedId === version.id) { setExpandedId(null); return; } setExpandedId(version.id); await ensureDetail(version.id); } async function handleCompare(version: VersionSummary) { await ensureDetail(version.id); setComparingId(version.id); } async function handleRestore(version: VersionSummary) { setRestoringId(version.id); try { const res = await fetch(`/api/v1/recipes/${recipeId}/versions/${version.id}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "restore" }), }); if (res.ok) { toast.success(`Recipe restored to version ${version.version}`); setOpen(false); router.refresh(); } else { toast.error(t("historyRestoreFailed")); } } finally { setRestoringId(null); } } return ( <> handleOpen(true)}> } /> History Version History
{loading && (

Loading versions...

)} {!loading && versions.length === 0 && (

No versions yet. Versions are saved automatically when you edit this recipe.

)} {versions.map((v) => { const isExpanded = expandedId === v.id; const detail = expandedData[v.id]; return (
{isExpanded && (
{!detail ? (

Loading...

) : (

{detail.snapshotData.ingredients.length} ingredient{detail.snapshotData.ingredients.length !== 1 ? "s" : ""},{" "} {detail.snapshotData.steps.length} step{detail.snapshotData.steps.length !== 1 ? "s" : ""}

)}
)}
); })}
!isOpen && setComparingId(null)}> {comparingId ? `Compare v${expandedData[comparingId]?.version} with current` : "Compare"} {comparingId && expandedData[comparingId] && ( )} ); }