'use client'; import { useState, useEffect } from 'react'; import { useParams } from 'next/navigation'; import Link from 'next/link'; import { useApi } from '@/lib/useApi'; import { SetPageHeader } from '@/components/layout/SetPageHeader'; import { getRecipe, scaleRecipe } from '@/services/recipes'; import { NutritionWarning } from '@meshitrack/shared'; import type { z } from 'zod/v4'; import type { RecipeResponseSchema } from '@meshitrack/shared'; type Recipe = z.infer; const WARNING_LABELS: Partial> = { [NutritionWarning.HIGH_CALORIES]: 'High calories (>800 kcal/serving)', [NutritionWarning.HIGH_SODIUM]: 'High sodium (>1500mg/serving)', [NutritionWarning.HIGH_SUGAR]: 'High sugar (>25g/serving)', [NutritionWarning.HIGH_SATURATED_FAT]: 'High saturated fat (>13g/serving)', [NutritionWarning.LOW_PROTEIN]: 'Low protein (<10g/serving)', [NutritionWarning.LOW_FIBER]: 'Low fiber (<3g/serving)', [NutritionWarning.HIGH_CHOLESTEROL]: 'High cholesterol (>200mg/serving)', }; function formatTime(minutes?: number): string { if (!minutes) return ''; if (minutes < 60) return `${minutes} min`; const h = Math.floor(minutes / 60); const m = minutes % 60; return m > 0 ? `${h}h ${m}m` : `${h}h`; } function NutritionPanel({ nutrition, label, }: { nutrition: Recipe['perServingNutrition']; label: string; }) { return (

{label}

{nutrition.fiber !== undefined && ( )} {nutrition.sugar !== undefined && ( )} {nutrition.sodium !== undefined && ( )} {nutrition.saturatedFat !== undefined && ( )} {nutrition.cholesterol !== undefined && ( )}
); } function NutrRow({ label, value, bold }: { label: string; value: string; bold?: boolean }) { return ( <> {label} {value} ); } export default function RecipeDetailPage() { const params = useParams<{ id: string }>(); const { householdId, isLoading: sessionLoading } = useApi(); const [recipe, setRecipe] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [scaledServings, setScaledServings] = useState(null); const [scaledRecipe, setScaledRecipe] = useState(null); const [scaling, setScaling] = useState(false); useEffect(() => { if (!householdId || !params.id) return; setLoading(true); getRecipe(householdId, params.id) .then((r) => { setRecipe(r); setScaledServings(r.servings); }) .catch((err) => setError(err instanceof Error ? err.message : 'Failed to load recipe')) .finally(() => setLoading(false)); }, [householdId, params.id]); async function handleScale() { if (!householdId || !recipe || !scaledServings) return; if (scaledServings === recipe.servings) { setScaledRecipe(null); return; } setScaling(true); try { const result = await scaleRecipe(householdId, recipe._id, { targetServings: scaledServings }); setScaledRecipe(result); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to scale recipe'); } finally { setScaling(false); } } if (sessionLoading || loading) { return ( <>
); } if (error || !recipe) { return ( <>
{error || 'Recipe not found'}
); } const displayRecipe = scaledRecipe ?? recipe; const time = recipe.totalTime ?? (recipe.prepTime ?? 0) + (recipe.cookTime ?? 0); return ( <>
{/* Main content */}
{/* Meta */}
{recipe.servings} servings {time > 0 && ( {formatTime(time)} )} {recipe.isFavorite && ( Starred )} Edit
{recipe.description && (

{recipe.description}

)} {/* Warnings */} {recipe.warnings.length > 0 && (

Nutritional alerts

    {recipe.warnings.map((w) => (
  • {WARNING_LABELS[w] ?? w}
  • ))}
)} {/* Ingredients */}

Ingredients

    {displayRecipe.ingredients.map((ing, i) => (
  • {ing.isOptional && ( (optional) )} {ing.productName} {ing.preparation && ( , {ing.preparation} )} {ing.originalQuantity != null && ing.originalUnit ? `${ing.originalQuantity} ${ing.originalUnit}` : `${ing.quantity} ${ing.unit}`}
  • ))}
{/* Steps */} {recipe.steps.length > 0 && (

Instructions

    {recipe.steps .slice() .sort((a, b) => a.order - b.order) .map((step) => (
  1. {step.instruction}

    {step.duration && (

    {formatTime(step.duration)}

    )} {step.tip && (

    Tip: {step.tip}

    )}
  2. ))}
)}
{/* Sidebar */}
{/* Scale */}

Scale Recipe

setScaledServings(Number(e.target.value))} style={{ width: 70, padding: '6px 8px', borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--ink)', fontSize: 14, }} /> servings
{scaledRecipe && ( )}
{/* Nutrition */}
); }