"use client"; import { useState } from "react"; import { useTranslations } from "next-intl"; import { toast } from "sonner"; import { Snowflake, Refrigerator, ChefHat, Check } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useLocale } from "@/lib/i18n/provider"; import { stripMarkdown } from "@/lib/utils"; import { queueOfflineAction } from "@/lib/offline-queue"; type Dish = { id: string; name: string; description: string | null; fridgeDays: number; freezerFriendly: boolean; freezerNote: string | null; dayOfInstructions: string; cookedAt: string | null; }; function expiresOn(cookedAt: string, fridgeDays: number): Date { const d = new Date(cookedAt); d.setDate(d.getDate() + fridgeDays); return d; } export function BatchCookDishes({ recipeId, dishes: initialDishes }: { recipeId: string; dishes: Dish[] }) { const t = useTranslations("recipe"); const { locale } = useLocale(); const [dishes, setDishes] = useState(initialDishes); const [markingId, setMarkingId] = useState(null); async function markCooked(dishId: string) { setMarkingId(dishId); const body = { batchDishId: dishId, deductFromPantry: false }; try { let res: Response; try { res = await fetch(`/api/v1/recipes/${recipeId}/cooked`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); } catch { // fetch() throwing (rather than resolving with a non-ok status) // means the network itself is unreachable — queue it for the // service worker (or the online-event fallback) to replay later, // rather than treating it as a real failure. await queueOfflineAction({ method: "POST", url: `/api/v1/recipes/${recipeId}/cooked`, body, description: `Mark dish ${dishId} as cooked`, }); const now = new Date().toISOString(); setDishes((prev) => prev.map((d) => (d.id === dishId ? { ...d, cookedAt: now } : d))); toast.info(t("batchCookMarkQueuedOffline")); return; } if (!res.ok) { toast.error(t("batchCookMarkFailed")); return; } const now = new Date().toISOString(); setDishes((prev) => prev.map((d) => (d.id === dishId ? { ...d, cookedAt: now } : d))); toast.success(t("batchCookMarkSuccess")); } finally { setMarkingId(null); } } return (

{t("batchCookDishesTitle")}

{dishes.map((dish) => (

{dish.name}

{dish.description &&

{stripMarkdown(dish.description)}

}
{t("batchCookFridgeDays", { days: dish.fridgeDays })} {dish.freezerFriendly && ( {t("batchCookFreezerFriendly")} )}
{dish.freezerNote && (

{dish.freezerNote}

)}

{t("batchCookDayOf")}

{stripMarkdown(dish.dayOfInstructions)}

{dish.cookedAt ? (

{t("batchCookExpiresOn", { date: expiresOn(dish.cookedAt, dish.fridgeDays).toLocaleDateString(locale, { month: "short", day: "numeric" }), })}

) : ( )}
))}
); }