From 04a911b43107d02d4059889973474aa425ba73e4 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Fri, 24 Jul 2026 11:28:41 +0200 Subject: [PATCH] feat: mark recipe as cooked (multi-cook history + backdate) and enable auto-deduct pantry on cook (v0.75.0) Adds a general-purpose "mark cooked" dialog for any recipe (not just batch-cook dishes), with a date picker for backdating and a pantry-deduct checkbox defaulted on. Also flips the previously dead-in-the-UI deductFromPantry flag to true for the existing batch-cook and meal-planner cook actions. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 8 ++ FEATURE_AUDIT.md | 5 +- apps/web/app/(app)/recipes/[id]/page.tsx | 19 +++ .../app/api/v1/recipes/[id]/cooked/route.ts | 7 +- .../web/components/meal-plan/meal-planner.tsx | 2 +- .../components/recipe/batch-cook-dishes.tsx | 2 +- .../components/recipe/mark-cooked-dialog.tsx | 119 ++++++++++++++++++ .../components/recipe/mark-cooked-section.tsx | 46 +++++++ apps/web/lib/changelog.ts | 12 +- apps/web/lib/openapi.ts | 2 +- apps/web/messages/en.json | 14 +++ apps/web/messages/fr.json | 14 +++ apps/web/package.json | 2 +- package.json | 2 +- 14 files changed, 245 insertions(+), 9 deletions(-) create mode 100644 apps/web/components/recipe/mark-cooked-dialog.tsx create mode 100644 apps/web/components/recipe/mark-cooked-section.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 698dbf0..7537a7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to Epicure are documented here. This file is mirrored in-app at `/changelog` (and in the admin dashboard) via `apps/web/lib/changelog.ts` — update both together. +## 0.75.0 — 2026-07-24 11:30 + +### Added +- Mark any recipe as cooked (not just batch-cook dishes) — log the date, servings made, and whether to deduct matching ingredients from your pantry. Log the same recipe cooked as many times as you like; the recipe page shows how many times and when you last cooked it. + +### Fixed +- Auto-deduct pantry on cook now actually fires — both existing mark-cooked flows (batch-cook dishes, meal-plan entries) previously hardcoded it off despite the underlying logic working correctly. + ## 0.74.0 — 2026-07-24 10:30 ### Added diff --git a/FEATURE_AUDIT.md b/FEATURE_AUDIT.md index fc02ad3..97c141f 100644 --- a/FEATURE_AUDIT.md +++ b/FEATURE_AUDIT.md @@ -68,7 +68,8 @@ Status legend: **Exists** (fully working) · **Partial** (works but with a real | Grocery delivery integration | **Partial / stub** | Generic export payload works (integrator-shaped — no visible in-app UI consumer besides the Instacart adapter); Instacart adapter is an explicit stub (requires a partnership agreement Epicure doesn't have — returns 501 if unconfigured, throws if "configured") | `apps/web/lib/grocery-providers/instacart.ts` | | Other delivery/price integrations (DoorDash, Kroger, Walmart, live pricing) | **Missing** | Confirmed absent by repo-wide search | — | | Pantry manual CRUD | Exists | Includes a bulk case-insensitive name+unit merge endpoint (used by the scan-confirm flow), undocumented until this pass | `apps/web/app/api/v1/pantry/**` | -| Auto-deduct pantry on cook | **API-only, dead in the UI** | The API logic is correct (name+unit matching, defaults `deductFromPantry: true`) — but both real UI callers (`batch-cook-dishes.tsx`, `meal-planner.tsx`'s mark-cooked flow) hardcode `deductFromPantry: false`. A home cook using the app today never triggers auto-deduction; it's only reachable via a direct API/API-key client. Previously documented as "Exists, one nuance," which overstated real-world behavior. | `apps/web/app/api/v1/recipes/[id]/cooked/route.ts`, `apps/web/components/recipe/batch-cook-dishes.tsx`, `apps/web/components/meal-plan/meal-planner.tsx` | +| Auto-deduct pantry on cook | Exists (closed 2026-07-24) | Both UI callers that previously hardcoded `deductFromPantry: false` (`batch-cook-dishes.tsx`, `meal-planner.tsx`) now pass `true`. The new general "Mark cooked" feature (see below) additionally exposes it as a per-cook checkbox, default checked, rather than a silent always-on. | `apps/web/app/api/v1/recipes/[id]/cooked/route.ts`, `apps/web/components/recipe/batch-cook-dishes.tsx`, `apps/web/components/meal-plan/meal-planner.tsx` | +| Mark recipe as cooked (any recipe, not just batch-cook) | Exists (new, 2026-07-24) | A recipe can be logged as cooked any number of times — each log is a new `cookingHistory` row, no unique constraint, this was already true at the schema level, just not exposed for plain (non-batch) recipes before. New dialog on the recipe page: date (defaults today, can backdate), servings, and a deduct-from-pantry toggle (default on). Shows a "cooked N times · last DATE" indicator once logged. | `apps/web/components/recipe/{mark-cooked-dialog,mark-cooked-section}.tsx`, `apps/web/app/api/v1/recipes/[id]/cooked/route.ts` (`cookedAt` field, new) | | Expiring-soon tracking | Exists | With "use it up" recipe suggestions. The leftover-expiry push/email cron only covers batch-cook dishes — plain pantry items only get an in-app badge, no reminder. | `apps/web/components/pantry/expiring-soon-suggestions.tsx`, `apps/web/app/api/internal/cron/leftover-reminders/route.ts` | | **Barcode scan (pantry)** | **Exists** | Real Open Food Facts API lookup by UPC/EAN | `apps/web/app/api/v1/pantry/scan/barcode` | | Photo scan (pantry) | Exists | Vision-model based | `apps/web/app/api/v1/pantry/scan/photo` | @@ -159,7 +160,7 @@ Ranked roughly by likely value: 9. **Offline coverage for shopping lists / meal plan** — currently recipe-viewing + mark-cooked only. 10. ~~Moderator-scoped admin routes~~ — closed 2026-07-21 (reports + recipe-unpublish). 11. **Followers-only recipes invisible on the author's own profile grid** (2026-07-24) — `u/[username]/page.tsx` only queries `visibility = "public"` for the grid, so a followers-only recipe shows up in a follower's feed but never on the profile it belongs to. Real bug, not a design choice. -12. **Auto-deduct pantry on cook is dead in the UI** (2026-07-24) — correct, tested API logic that literally nothing in the product surfaces; both UI callers hardcode it off. Either wire it up (a real feature a home cook would want) or stop pretending it's a shipped feature. +12. ~~Auto-deduct pantry on cook is dead in the UI~~ — closed 2026-07-24 (both UI callers now pass `true`; the new Mark Cooked dialog exposes it as a checkbox). 13. ~~Model Prefs picker is ungated~~ — closed 2026-07-24 (now behind BYOK access, hidden entirely for everyone else). ## Consumer-friendliness assessment — "is Epicure too tech-savvy?" (2026-07-24) diff --git a/apps/web/app/(app)/recipes/[id]/page.tsx b/apps/web/app/(app)/recipes/[id]/page.tsx index 7b12248..fdafe05 100644 --- a/apps/web/app/(app)/recipes/[id]/page.tsx +++ b/apps/web/app/(app)/recipes/[id]/page.tsx @@ -31,6 +31,7 @@ import { ServingScaler } from "@/components/recipe/serving-scaler"; import { FavoriteButton } from "@/components/social/favorite-button"; import { RatingStars } from "@/components/social/rating-stars"; import { CookedItReview } from "@/components/social/cooked-it-review"; +import { MarkCookedSection } from "@/components/recipe/mark-cooked-section"; import { RecipeNotes } from "@/components/recipe/recipe-notes"; import { CommentsSection } from "@/components/social/comments-section"; import { getPublicUrl } from "@/lib/storage"; @@ -127,6 +128,12 @@ export default async function RecipePage({ params }: Params) { } } + // Non-batch cook log — batch-cook recipes track this per-dish instead + // (dishCookedAtMap above), logged via BatchCookDishes, not this list. + const plainCookLog = dishCookLog.filter((l) => !l.batchDishId); + const cookCount = plainCookLog.length; + const lastCookedAt = plainCookLog[0]?.cookedAt.toISOString() ?? null; + const avgScore = ratingData[0]?.avgScore ? parseFloat(ratingData[0].avgScore) : null; const ratingCount = ratingData[0]?.total ?? 0; const isFavorited = !!favoriteData; @@ -492,6 +499,18 @@ export default async function RecipePage({ params }: Params) { )} + {!recipe.isBatchCook && ( + <> + + + + )} + {recipe.visibility !== "private" && ( <> diff --git a/apps/web/app/api/v1/recipes/[id]/cooked/route.ts b/apps/web/app/api/v1/recipes/[id]/cooked/route.ts index 5534ef1..277f470 100644 --- a/apps/web/app/api/v1/recipes/[id]/cooked/route.ts +++ b/apps/web/app/api/v1/recipes/[id]/cooked/route.ts @@ -10,6 +10,9 @@ const Schema = z.object({ notes: z.string().max(2000).optional(), deductFromPantry: z.boolean().default(true), batchDishId: z.string().optional(), + /** ISO date (YYYY-MM-DD) or full datetime — lets a user log a past cook, + * not just "just now". Defaults to now when omitted. */ + cookedAt: z.string().optional(), }); export async function POST(req: NextRequest, { params }: Params) { @@ -44,6 +47,8 @@ export async function POST(req: NextRequest, { params }: Params) { isFirstBatchCook = !priorCook; } + const cookedAt = data.cookedAt ? new Date(data.cookedAt) : new Date(); + await db.insert(cookingHistory).values({ id: crypto.randomUUID(), userId, @@ -51,7 +56,7 @@ export async function POST(req: NextRequest, { params }: Params) { batchDishId: data.batchDishId, servings: data.servings, notes: data.notes, - cookedAt: new Date(), + cookedAt: isNaN(cookedAt.getTime()) ? new Date() : cookedAt, }); if (data.deductFromPantry && (!data.batchDishId || isFirstBatchCook)) { diff --git a/apps/web/components/meal-plan/meal-planner.tsx b/apps/web/components/meal-plan/meal-planner.tsx index 83c9e35..5f23bff 100644 --- a/apps/web/components/meal-plan/meal-planner.tsx +++ b/apps/web/components/meal-plan/meal-planner.tsx @@ -377,7 +377,7 @@ export function MealPlanner({ const res = await fetch(`/api/v1/recipes/${entry.recipe.id}/cooked`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ servings: entry.servings, deductFromPantry: false }), + body: JSON.stringify({ servings: entry.servings, deductFromPantry: true }), }); if (!res.ok) { toast.error(t("markCookedFailed")); diff --git a/apps/web/components/recipe/batch-cook-dishes.tsx b/apps/web/components/recipe/batch-cook-dishes.tsx index 6d4dc58..7b349a9 100644 --- a/apps/web/components/recipe/batch-cook-dishes.tsx +++ b/apps/web/components/recipe/batch-cook-dishes.tsx @@ -34,7 +34,7 @@ export function BatchCookDishes({ recipeId, dishes: initialDishes }: { recipeId: async function markCooked(dishId: string) { setMarkingId(dishId); - const body = { batchDishId: dishId, deductFromPantry: false }; + const body = { batchDishId: dishId, deductFromPantry: true }; try { let res: Response; try { diff --git a/apps/web/components/recipe/mark-cooked-dialog.tsx b/apps/web/components/recipe/mark-cooked-dialog.tsx new file mode 100644 index 0000000..076b32e --- /dev/null +++ b/apps/web/components/recipe/mark-cooked-dialog.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { cloneElement, isValidElement, useState, type ReactElement } from "react"; +import { useTranslations } from "next-intl"; +import { toast } from "sonner"; +import { ChefHat } from "lucide-react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; + +function todayIso(): string { + return new Date().toISOString().slice(0, 10); +} + +interface MarkCookedDialogProps { + recipeId: string; + baseServings: number; + batchDishId?: string; + trigger: React.ReactNode; + onLogged?: (cookedAt: string) => void; +} + +/** Logs a cook event — date, servings, and whether to deduct matching + * ingredients from the pantry (default on). A recipe can be logged as + * cooked any number of times; each submission is a new row, never an + * update. */ +export function MarkCookedDialog({ recipeId, baseServings, batchDishId, trigger, onLogged }: MarkCookedDialogProps) { + const t = useTranslations("recipe"); + const tCommon = useTranslations("common"); + const [open, setOpen] = useState(false); + const [date, setDate] = useState(todayIso()); + const [servings, setServings] = useState(baseServings); + const [deductFromPantry, setDeductFromPantry] = useState(true); + const [submitting, setSubmitting] = useState(false); + + async function handleSubmit() { + setSubmitting(true); + try { + const res = await fetch(`/api/v1/recipes/${recipeId}/cooked`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + servings, + cookedAt: date, + deductFromPantry, + ...(batchDishId ? { batchDishId } : {}), + }), + }); + if (!res.ok) throw new Error(); + toast.success(t("markCookedSuccess")); + setOpen(false); + onLogged?.(date); + } catch { + toast.error(t("markCookedFailed")); + } finally { + setSubmitting(false); + } + } + + const triggerElement = isValidElement(trigger) + ? cloneElement(trigger as ReactElement<{ onClick?: () => void }>, { onClick: () => setOpen(true) }) + : trigger; + + return ( + <> + {triggerElement} + + + + + + {t("markCookedTitle")} + + {t("markCookedDescription")} + +
+
+ + setDate(e.target.value || todayIso())} /> +
+
+ + setServings(Math.max(1, Number(e.target.value) || baseServings))} + /> +
+
+
+ +

{t("markCookedDeductDescription")}

+
+ +
+
+ + +
+
+
+
+ + ); +} diff --git a/apps/web/components/recipe/mark-cooked-section.tsx b/apps/web/components/recipe/mark-cooked-section.tsx new file mode 100644 index 0000000..10a7c3f --- /dev/null +++ b/apps/web/components/recipe/mark-cooked-section.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { ChefHat } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { useLocale } from "@/lib/i18n/provider"; +import { MarkCookedDialog } from "./mark-cooked-dialog"; + +export function MarkCookedSection({ + recipeId, + baseServings, + cookCount, + lastCookedAt, +}: { + recipeId: string; + baseServings: number; + cookCount: number; + lastCookedAt: string | null; +}) { + const t = useTranslations("recipe"); + const router = useRouter(); + const { locale } = useLocale(); + + return ( +
+ router.refresh()} + trigger={ + + } + /> + {cookCount > 0 && ( +

+ {cookCount === 1 ? t("markCookedCountSingular") : t("markCookedCountPlural", { count: cookCount })} + {lastCookedAt && t("markCookedLast", { date: new Date(lastCookedAt).toLocaleDateString(locale, { month: "short", day: "numeric" }) })} +

+ )} +
+ ); +} diff --git a/apps/web/lib/changelog.ts b/apps/web/lib/changelog.ts index c891434..9dabe1e 100644 --- a/apps/web/lib/changelog.ts +++ b/apps/web/lib/changelog.ts @@ -1,5 +1,5 @@ // Mirrors CHANGELOG.md at the repo root — update both together. -export const APP_VERSION = "0.74.0"; +export const APP_VERSION = "0.75.0"; export type ChangelogEntry = { version: string; @@ -11,6 +11,16 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + version: "0.75.0", + date: "2026-07-24 11:30", + added: [ + "Mark any recipe as cooked (not just batch-cook dishes) — log the date, servings made, and whether to deduct matching ingredients from your pantry. Log the same recipe cooked as many times as you like; the recipe page shows how many times and when you last cooked it.", + ], + fixed: [ + "Auto-deduct pantry on cook now actually fires — both existing mark-cooked flows (batch-cook dishes, meal-plan entries) previously hardcoded it off despite the underlying logic working correctly.", + ], + }, { version: "0.74.0", date: "2026-07-24 10:30", diff --git a/apps/web/lib/openapi.ts b/apps/web/lib/openapi.ts index dc49660..ba3f686 100644 --- a/apps/web/lib/openapi.ts +++ b/apps/web/lib/openapi.ts @@ -290,7 +290,7 @@ export function generateOpenApiSpec(): object { registry.registerPath({ method: "patch", path: "/api/v1/recipes/bulk", summary: "Bulk update visibility/tags (owned only)", security, request: { body: { content: { "application/json": { schema: BulkUpdateRecipesRef } }, required: true } }, responses: { 200: { description: "OK", content: { "application/json": { schema: z.object({ ok: z.boolean() }) } } }, 400: { description: "Invalid request / nothing to update", content: { "application/json": { schema: ApiErrorRef } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } } } }); registry.registerPath({ method: "post", path: "/api/v1/recipes/bulk/export", summary: "Export several recipes as one Markdown document", description: "Gated by the markdown_export tier feature flag.", security, request: { body: { content: { "application/json": { schema: BulkIdsRef } }, required: true } }, responses: { 200: { description: "Markdown", content: { "application/json": { schema: z.object({ markdown: z.string() }) } } }, 400: { description: "Invalid request", content: { "application/json": { schema: ApiErrorRef } } }, 403: { description: "Feature disabled for your tier", content: { "application/json": { schema: ApiErrorRef } } }, 404: { description: "None of the ids belong to you", content: { "application/json": { schema: ApiErrorRef } } } } }); registry.registerPath({ method: "post", path: "/api/v1/recipes/{id}/fork", summary: "Fork (or duplicate your own) a recipe", description: "Rate-limited: 20 req/min. Source must be your own, public, or unlisted.", security, request: { params: idParam }, responses: { 201: { description: "New recipe id", content: { "application/json": { schema: z.object({ id: z.string() }) } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } }, 403: { description: "Recipe limit reached for your tier", content: { "application/json": { schema: ApiErrorRef } } }, 404: { description: "Not found", content: { "application/json": { schema: ApiErrorRef } } } } }); - registry.registerPath({ method: "post", path: "/api/v1/recipes/{id}/cooked", summary: "Log that you cooked this recipe", description: "Optionally deducts matching ingredients from your pantry.", security, request: { params: idParam, body: { content: { "application/json": { schema: z.object({ servings: z.number().int().min(1).max(1000).optional(), notes: z.string().max(2000).optional(), deductFromPantry: z.boolean().default(true), batchDishId: z.string().optional() }) } } } }, responses: { 201: { description: "Logged", content: { "application/json": { schema: z.object({ logged: z.boolean() }) } } }, 404: { description: "Not found", content: { "application/json": { schema: ApiErrorRef } } } } }); + registry.registerPath({ method: "post", path: "/api/v1/recipes/{id}/cooked", summary: "Log that you cooked this recipe", description: "Optionally deducts matching ingredients from your pantry. A recipe can be logged as cooked any number of times — each call inserts a new history row, never updates one. cookedAt lets you backdate a cook instead of only logging \"now\".", security, request: { params: idParam, body: { content: { "application/json": { schema: z.object({ servings: z.number().int().min(1).max(1000).optional(), notes: z.string().max(2000).optional(), deductFromPantry: z.boolean().default(true), batchDishId: z.string().optional(), cookedAt: z.string().optional().describe("ISO date (YYYY-MM-DD) or datetime; defaults to now") }) } } } }, responses: { 201: { description: "Logged", content: { "application/json": { schema: z.object({ logged: z.boolean() }) } } }, 404: { description: "Not found", content: { "application/json": { schema: ApiErrorRef } } } } }); registry.registerPath({ method: "get", path: "/api/v1/recipes/{id}/nutrition", summary: "Get cached nutrition estimate or manually-entered values", security, request: { params: idParam }, responses: { 200: { description: "Nutrition data or null", content: { "application/json": { schema: z.object({ nutrition: z.object({ perServing: NutritionInputRef }).nullable(), manual: z.boolean() }) } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } }, 404: { description: "Not found", content: { "application/json": { schema: ApiErrorRef } } } } }); registry.registerPath({ method: "post", path: "/api/v1/recipes/{id}/nutrition", summary: "Compute a fresh AI nutrition estimate (author only)", description: "Rate-limited: 10 req/min. Consumes AI quota. Overwrites any manually-entered nutrition and clears nutritionManual. Gated by the nutrition_estimation tier feature flag.", security, request: { params: idParam }, responses: { 200: { description: "Nutrition data", content: { "application/json": { schema: z.object({ nutrition: z.object({ perServing: NutritionInputRef }) }) } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } }, 403: { description: "Feature disabled for your tier", content: { "application/json": { schema: ApiErrorRef } } }, 404: { description: "Not found", content: { "application/json": { schema: ApiErrorRef } } }, 429: { description: "Rate limited or AI quota exhausted", content: { "application/json": { schema: ApiErrorRef } } } } }); registry.registerPath({ method: "get", path: "/api/v1/recipes/{id}/notes", summary: "Get your own private note on a recipe", security, request: { params: idParam }, responses: { 200: { description: "Note or null", content: { "application/json": { schema: z.object({ note: RecipeNoteRef }) } } }, 401: { description: "Unauthorized", content: { "application/json": { schema: ApiErrorRef } } }, 404: { description: "Not found", content: { "application/json": { schema: ApiErrorRef } } } } }); diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index d6ebfb9..fbb1504 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -147,6 +147,20 @@ "batchCookMarkSuccess": "Marked as cooked", "batchCookMarkFailed": "Failed to mark as cooked", "batchCookMarkQueuedOffline": "Saved offline — will sync when you're back online", + "markCookedButton": "Mark cooked", + "markCookedTitle": "Mark as cooked", + "markCookedDescription": "Log this cook — you can log the same recipe as cooked as many times as you like.", + "markCookedDateLabel": "Date", + "markCookedServingsLabel": "Servings made", + "markCookedDeductLabel": "Deduct from pantry", + "markCookedDeductDescription": "Remove matching ingredients you have on hand.", + "markCookedSubmit": "Mark cooked", + "markCookedSaving": "Saving…", + "markCookedSuccess": "Marked as cooked", + "markCookedFailed": "Failed to log this cook", + "markCookedCountSingular": "Cooked 1 time", + "markCookedCountPlural": "Cooked {count} times", + "markCookedLast": " · last {date}", "batchCookExpiresOn": "Cooked — good until {date}", "servings": "{count} servings", "prep": "{mins}m prep", diff --git a/apps/web/messages/fr.json b/apps/web/messages/fr.json index fd68cca..655882f 100644 --- a/apps/web/messages/fr.json +++ b/apps/web/messages/fr.json @@ -147,6 +147,20 @@ "batchCookMarkSuccess": "Marqué comme cuisiné", "batchCookMarkFailed": "Échec du marquage", "batchCookMarkQueuedOffline": "Enregistré hors ligne — sera synchronisé au retour de la connexion", + "markCookedButton": "Marquer comme cuisiné", + "markCookedTitle": "Marquer comme cuisiné", + "markCookedDescription": "Enregistrez cette cuisson — vous pouvez marquer la même recette comme cuisinée autant de fois que vous le souhaitez.", + "markCookedDateLabel": "Date", + "markCookedServingsLabel": "Portions réalisées", + "markCookedDeductLabel": "Déduire du garde-manger", + "markCookedDeductDescription": "Retire les ingrédients correspondants que vous avez sous la main.", + "markCookedSubmit": "Marquer comme cuisiné", + "markCookedSaving": "Enregistrement…", + "markCookedSuccess": "Marqué comme cuisiné", + "markCookedFailed": "Échec de l'enregistrement", + "markCookedCountSingular": "Cuisiné 1 fois", + "markCookedCountPlural": "Cuisiné {count} fois", + "markCookedLast": " · dernière fois le {date}", "batchCookExpiresOn": "Cuisiné — bon jusqu'au {date}", "servings": "{count} portions", "prep": "{mins}m prép.", diff --git a/apps/web/package.json b/apps/web/package.json index f06a87f..847f801 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@epicure/web", - "version": "0.74.0", + "version": "0.75.0", "private": true, "scripts": { "dev": "next dev", diff --git a/package.json b/package.json index 454976d..a8e0f70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "epicure", - "version": "0.74.0", + "version": "0.75.0", "private": true, "scripts": { "dev": "pnpm --filter web dev",