import { prisma } from '@ketopath/db'; import Link from 'next/link'; import { notFound, redirect } from 'next/navigation'; import { useTranslations } from 'next-intl'; import { setRequestLocale } from 'next-intl/server'; import { Masthead } from '@/components/masthead'; import { getServerSession } from '@/lib/auth'; interface RecipeIngredientView { id: string; name: string; category: string; quantity: number; unit: string; substitutes: string[]; } interface RecipeVariant { name: string; description: string; kcalDelta?: number; } interface RecipeView { id: string; name: string; category: 'COLAZIONE' | 'PRANZO' | 'SPUNTINO' | 'CENA'; description: string | null; prepMinutes: number; difficulty: 'FACILE' | 'MEDIA' | 'ELABORATA'; kcal: number; proteinG: number; fatG: number; netCarbG: number; phases: number[]; notesChef: string | null; ingredients: RecipeIngredientView[]; variants: RecipeVariant[]; } export default async function RecipePage({ params: { locale, id }, }: { params: { locale: string; id: string }; }) { setRequestLocale(locale); const session = await getServerSession(); if (!session?.user) redirect('/sign-in'); const recipe = await prisma.recipe.findUnique({ where: { id }, include: { ingredients: { include: { ingredient: { select: { id: true, name: true, category: true } }, }, }, }, }); if (!recipe) notFound(); const view: RecipeView = { id: recipe.id, name: recipe.name, category: recipe.category, description: recipe.description, prepMinutes: recipe.prepMinutes, difficulty: recipe.difficulty, kcal: recipe.kcal, proteinG: recipe.proteinG, fatG: recipe.fatG, netCarbG: recipe.netCarbG, phases: recipe.phases, notesChef: recipe.notesChef, ingredients: recipe.ingredients.map((ri) => ({ id: ri.id, name: ri.ingredient.name, category: ri.ingredient.category, quantity: ri.quantity, unit: ri.unit, substitutes: ri.substitutes ?? [], })), variants: parseVariants(recipe.variants), }; return ; } const ROMAN = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII']; function parseVariants(value: unknown): RecipeVariant[] { if (!Array.isArray(value)) return []; return value .filter((v): v is { name: string; description: string; kcalDelta?: number } => { if (!v || typeof v !== 'object') return false; const o = v as Record; return typeof o.name === 'string' && typeof o.description === 'string'; }) .map((v) => ({ name: v.name, description: v.description, ...(typeof v.kcalDelta === 'number' ? { kcalDelta: v.kcalDelta } : {}), })); } function RecipeContent({ recipe }: { recipe: RecipeView }) { const t = useTranslations('Recipe'); const phasesAll = recipe.phases.length === 3; return (
{t('back')}

{t(`categoryLabel.${recipe.category}`)}

{recipe.name} .

{recipe.description ? (

{recipe.description}

) : null}

{t('ingredientsTitle')}

{t('ingredientsSubtitle')}

    {recipe.ingredients.map((ing, idx) => (
  1. {ROMAN[idx] ?? idx + 1}

    {ing.name}

    {ing.category}

    {ing.substitutes.length > 0 ? (

    {t('substitutesPrefix')} {ing.substitutes.join(', ')}

    ) : null}
    {formatQuantity(ing.quantity)} {ing.unit}
  2. ))}

{t('preparationTitle')}

{(recipe.description ?? t('preparationFallback'))[0]} {(recipe.description ?? t('preparationFallback')).slice(1)}

{recipe.notesChef ? ( ) : null}
{recipe.variants.length > 0 ? ( <>

{t('variantsTitle')}

    {recipe.variants.map((v, i) => (
  • {v.name}

    {v.description}

    {v.kcalDelta != null ? (

    {v.kcalDelta > 0 ? '+' : ''} {v.kcalDelta} kcal

    ) : null}
  • ))}
) : null}
); } function MetaRow({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } function MacroRow({ label, value, unit, big = false, }: { label: string; value: number; unit: string; big?: boolean; }) { return (
{label}
{value} {unit}
); } function formatQuantity(q: number): string { if (Number.isInteger(q)) return String(q); if (q < 1) { if (q === 0.25) return '¼'; if (q === 0.5) return '½'; if (q === 0.75) return '¾'; } return q.toFixed(1); }