feat: recipe detail + weekly shopping list (PRD §5.1, §5.4)

Recipe detail
- Seed catalog of 33 italian ingredients with macros, allergens, avg price.
- Each seed recipe now declares its RecipeIngredient links (qty + unit).
- /recipes/[id] page: editorial layout with prep meta, ingredients list,
  macros block, preparation, chef's note. Plan-week meal names linkable.

Shopping list
- GET /me/shopping-list aggregates RecipeIngredient quantities of the
  ACTIVE plan, grouped by ingredient category, sorted alphabetically.
- /shopping page: total items, estimated cost, per-category checklist
  with check-off persisted in localStorage per plan id.
- Home gets a "Spesa" nav item; /plan links the list when a plan exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Opus 4.7 committed 2026-04-29 21:13:00 +02:00
1 parent b79340efb7
commit ad1e4ecaeb
13 files changed
+1331 -14

No files matched your search

+16 -5
View File
@@ -1,5 +1,6 @@
'use client';
import Link from 'next/link';
import { useTranslations } from 'next-intl';
import { useState, useTransition } from 'react';
@@ -87,9 +88,16 @@ function SlotCard({ slot, index }: { slot: PlanSlot; index: number }) {
<span>{t(`meals.${slot.meal}`)}</span>
</p>
</header>
<p className="font-display text-ink text-lg font-medium leading-tight">
{slot.selected?.name ?? t('noRecipe')}
</p>
{slot.selected ? (
<Link
href={`/recipes/${slot.selected.id}`}
className="font-display text-ink decoration-pomodoro hover:decoration-ink text-lg font-medium leading-tight underline decoration-[1.5px] underline-offset-[5px] transition-colors"
>
{slot.selected.name}
</Link>
) : (
<p className="font-display text-ink-soft text-lg italic leading-tight">{t('noRecipe')}</p>
)}
{slot.selected ? (
<p className="text-ink-soft font-mono text-xs tracking-tight">
{Math.round(slot.selected.kcal)}{' '}
@@ -109,12 +117,15 @@ function SlotCard({ slot, index }: { slot: PlanSlot; index: number }) {
<ul className="border-ink/15 mt-3 space-y-2 border-t pt-3">
{slot.alternatives.map((alt) => (
<li key={alt.id} className="flex items-baseline justify-between gap-2">
<span className="font-display text-ink text-sm leading-snug">
<Link
href={`/recipes/${alt.id}`}
className="font-display text-ink hover:text-pomodoro text-sm leading-snug transition-colors"
>
{alt.name}{' '}
<span className="text-ink-dim font-mono text-[10px]">
{Math.round(alt.kcal)} kcal
</span>
</span>
</Link>
<Button
type="button"
size="sm"