import { useState, useEffect } from 'react'; import type { Recipe, Tag } from '../types/recipe'; import { TagSelector } from './TagSelector'; export interface RecipeFormData { title: string; description?: string; ingredients: string[]; instructions: string[]; source_url?: string; notes?: string; servings?: number; prep_time_minutes?: number; 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 - Visually polished form component for creating/editing recipes */ export function RecipeForm({ recipe, initialTags = [], onSubmit, onCancel, submitLabel = 'Save Recipe' }: RecipeFormProps) { // Form state const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [ingredientsText, setIngredientsText] = useState(''); const [instructionsText, setInstructionsText] = useState(''); const [sourceUrl, setSourceUrl] = useState(''); const [notes, setNotes] = useState(''); const [servings, setServings] = useState(''); const [prepTime, setPrepTime] = useState(''); const [cookTime, setCookTime] = useState(''); const [selectedTags, setSelectedTags] = useState(initialTags); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); // Populate form from recipe prop useEffect(() => { if (recipe) { setTitle(recipe.title || ''); setDescription(recipe.description || ''); 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() || ''); setPrepTime(recipe.prep_time_minutes?.toString() || ''); setCookTime(recipe.cook_time_minutes?.toString() || ''); } }, [recipe]); useEffect(() => { setSelectedTags(initialTags); }, [initialTags]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); // Validation if (!title.trim()) { setError('Title is required'); return; } const ingredientsList = ingredientsText .split('\n') .map(line => line.trim()) .filter(line => line.length > 0); if (ingredientsList.length === 0) { setError('At least one ingredient is required'); return; } const instructionsList = instructionsText .split('\n') .map(line => line.trim()) .filter(line => line.length > 0); if (instructionsList.length === 0) { setError('At least one instruction step is required'); return; } const data: RecipeFormData = { title: title.trim(), description: description.trim() || undefined, ingredients: ingredientsList, instructions: instructionsList, source_url: sourceUrl.trim() || undefined, notes: notes.trim() || undefined, servings: servings ? parseInt(servings, 10) : undefined, prep_time_minutes: prepTime ? parseInt(prepTime, 10) : undefined, cook_time_minutes: cookTime ? parseInt(cookTime, 10) : undefined, }; try { setIsSubmitting(true); await onSubmit(data, selectedTags); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to save recipe'); setIsSubmitting(false); } }; return (
{error && (
{error}
)}
setTitle(e.target.value)} className="mt-1 block w-full rounded-md border-gray-300 shadow-card focus:border-primary focus:ring-primary text-[17px] py-2 px-4 font-medium" placeholder="e.g., Chocolate Chip Cookies" required />