From 855dc622072b21498a1acb299381e29c46011d97 Mon Sep 17 00:00:00 2001 From: Paul Huliganga Date: Wed, 25 Mar 2026 18:31:07 -0400 Subject: [PATCH] feat(ui): visually polish RecipeDetailPage and RecipeForm with consistent theme tokens, improved layout, card sections, and input styling for UX parity with RecipeList --- frontend/src/components/RecipeForm.tsx | 147 +++++------ frontend/src/pages/RecipeDetailPage.tsx | 320 ++++++++---------------- 2 files changed, 162 insertions(+), 305 deletions(-) diff --git a/frontend/src/components/RecipeForm.tsx b/frontend/src/components/RecipeForm.tsx index e5a80de..e8e87f9 100644 --- a/frontend/src/components/RecipeForm.tsx +++ b/frontend/src/components/RecipeForm.tsx @@ -2,14 +2,6 @@ import { useState, useEffect } from 'react'; import type { Recipe, Tag } from '../types/recipe'; import { TagSelector } from './TagSelector'; -interface RecipeFormProps { - recipe?: Recipe | null; - initialTags?: Tag[]; - onSubmit: (data: RecipeFormData, tags: Tag[]) => Promise; - onCancel: () => void; - submitLabel?: string; -} - export interface RecipeFormData { title: string; description?: string; @@ -22,8 +14,16 @@ export interface RecipeFormData { cook_time_minutes?: number; } +interface RecipeFormProps { + recipe?: Recipe; // May be undefined when creating + initialTags?: Tag[]; + onSubmit: (data: RecipeFormData, tags: Tag[]) => Promise; + onCancel: () => void; + submitLabel?: string; +} + /** - * RecipeForm - Form component for creating/editing recipes + * RecipeForm - Visually polished form component for creating/editing recipes */ export function RecipeForm({ recipe, @@ -52,8 +52,10 @@ export function RecipeForm({ if (recipe) { setTitle(recipe.title || ''); setDescription(recipe.description || ''); - setIngredientsText(recipe.ingredients.join('\n')); - setInstructionsText(recipe.instructions.join('\n')); + setIngredientsText((Array.isArray(recipe.ingredients) ? recipe.ingredients.map(ingr => ('item' in ingr ? ingr.item : (typeof ingr === 'string' ? ingr : ''))) : []).join('\n')); + setInstructionsText( + (Array.isArray(recipe.instructions) ? recipe.instructions : recipe.steps?.map(s => s.instruction) || []).join('\n') + ); setSourceUrl(recipe.source_url || ''); setNotes(recipe.notes || ''); setServings(recipe.servings?.toString() || ''); @@ -62,12 +64,11 @@ export function RecipeForm({ } }, [recipe]); - // Update tags when initialTags changes useEffect(() => { setSelectedTags(initialTags); }, [initialTags]); - const handleSubmit = async (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); @@ -81,7 +82,6 @@ export function RecipeForm({ .split('\n') .map(line => line.trim()) .filter(line => line.length > 0); - if (ingredientsList.length === 0) { setError('At least one ingredient is required'); return; @@ -91,7 +91,6 @@ export function RecipeForm({ .split('\n') .map(line => line.trim()) .filter(line => line.length > 0); - if (instructionsList.length === 0) { setError('At least one instruction step is required'); return; @@ -108,7 +107,6 @@ export function RecipeForm({ prep_time_minutes: prepTime ? parseInt(prepTime, 10) : undefined, cook_time_minutes: cookTime ? parseInt(cookTime, 10) : undefined, }; - try { setIsSubmitting(true); await onSubmit(data, selectedTags); @@ -119,173 +117,140 @@ export function RecipeForm({ }; return ( -
+ {error && ( -
+
{error}
)} - - {/* Title */}
-
- - {/* Description */}
-