diff --git a/frontend/src/components/RecipeCard.tsx b/frontend/src/components/RecipeCard.tsx index 5a2b7d4..3fc44e0 100644 --- a/frontend/src/components/RecipeCard.tsx +++ b/frontend/src/components/RecipeCard.tsx @@ -7,6 +7,8 @@ interface RecipeCardProps { tags?: Tag[]; } +const foodEmojis = ['🥗', '🍲', '🍝', '🍜', '🍛', '🥘', '🍗', '🍤', '🍕', '🥪', '🍳', '🍱']; + function formatTime(minutes?: number): string { if (!minutes) return ''; if (minutes < 60) return `${minutes}m`; @@ -21,61 +23,83 @@ function formatDate(timestamp?: number): string { return date.toLocaleDateString(); } +function accentForRecipe(recipe: Recipe, tags: Tag[]) { + if (tags[0]?.color) return tags[0].color; + const palette = ['#f97316', '#ef4444', '#22c55e', '#06b6d4', '#3b82f6', '#a855f7']; + return palette[recipe.id % palette.length]; +} + +function emojiForRecipe(recipe: Recipe) { + return foodEmojis[recipe.id % foodEmojis.length]; +} + +function MetaChip({ label, value, emoji }: { label: string; value: string; emoji: string }) { + return ( +
+ + {value} + {label} +
+ ); +} + export function RecipeCard({ recipe, tags = [] }: RecipeCardProps) { const totalTime = (recipe.prep_time_minutes || 0) + (recipe.cook_time_minutes || 0); + const accent = accentForRecipe(recipe, tags); return ( -
- {/* Title */} -

{recipe.title}

- {/* Description */} - {recipe.description &&

{recipe.description}

} +
+
+ {tags[0]?.name ?? 'Homemade'} +
+ +
+ +
+

{recipe.title}

+ + {recipe.description ? ( +

{recipe.description}

+ ) : ( +

No description yet

+ )} + +
+ {recipe.servings ? : null} + {totalTime > 0 ? : null} + +
- {/* Tags */} {tags.length > 0 && ( -
- {tags.map(tag => ( +
+ {tags.slice(0, 4).map((tag) => ( {tag.name} ))} + {tags.length > 4 ? +{tags.length - 4} : null}
)} - {/* Meta information */} -
- {recipe.servings && ( -
- 🍽️ - {recipe.servings} servings -
- )} - {totalTime > 0 && ( -
- ⏱️ - {formatTime(totalTime)} -
- )} - {recipe.last_cooked_at && ( -
- 👨‍🍳 - Last cooked {formatDate(recipe.last_cooked_at)} -
- )} -
- -
- {recipe.ingredients.length} ingredients - View Recipe → +
+ {recipe.last_cooked_at ? `Cooked ${formatDate(recipe.last_cooked_at)}` : 'Not cooked yet'} + View recipe →
diff --git a/frontend/src/pages/RecipeListPage.tsx b/frontend/src/pages/RecipeListPage.tsx index 1d71e57..bfdee38 100644 --- a/frontend/src/pages/RecipeListPage.tsx +++ b/frontend/src/pages/RecipeListPage.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { Link } from 'react-router-dom'; +import heroImage from '../assets/hero.png'; import { useRecipes } from '../hooks/useRecipes'; import { useTags } from '../hooks/useTags'; import { RecipeCard } from '../components/RecipeCard'; @@ -43,37 +44,74 @@ export function RecipeListPage() { }; const filteredRecipes = recipes; - const hasActiveFilters = searchQuery || selectedTagId !== null; + const hasActiveFilters = Boolean(searchQuery) || selectedTagId !== null; return ( -
+
-
-
+
+
+
+

Kitchen Companion

+

Cook smarter with your favorite recipes in one place

+

+ Build your personal cookbook, tag meals by mood or diet, and find what you need fast. +

+
+ + Add Recipe + + + Browse Recipes + +
+
+
+ Fresh ingredients and plated food +
+
+
+
+ +
+
-

My Recipes

+

My Recipes

Browse and search your recipe collection

+ New Recipe
- {/* Search/Tag Filter Row */} -
-
+ +
setSearchTerm(e.target.value)} - className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-base" - style={{borderRadius: radius.md}} + onChange={(e) => setSearchTerm(e.target.value)} + className="w-full rounded-lg border border-gray-300 px-4 py-2 text-base focus:border-transparent focus:ring-2 focus:ring-blue-500" + style={{ borderRadius: radius.md }} /> {!!searchQuery && ( + > + ✕ + )}
{!tagsLoading && tags.length > 0 && ( -
- Filter by tag: +
+ Filter by tag: - {tags.map(tag => ( + {tags.map((tag) => ( @@ -120,52 +164,61 @@ export function RecipeListPage() {
)} - {/* Active Filters */} {hasActiveFilters && (
Active filters: - {searchQuery && Search: "{searchQuery}"} + {searchQuery && Search: "{searchQuery}"} {selectedTagId !== null && ( - Tag: {tags.find(t => t.id === selectedTagId)?.name} + Tag: {tags.find((t) => t.id === selectedTagId)?.name} )} - +
)} -
+
- {/* Error State */} {error && ( -
-

Error: {error}

+
+

+ Error: {error} +

)} - {/* Loading State */} {loading && recipes.length === 0 && ( -
-
-

Loading recipes...

+
+ +

Warming up your recipe shelf...

+
+
+
)} - {/* Empty State */} {!loading && !error && filteredRecipes.length === 0 && ( -
-
🍳
-

{searchQuery ? 'No recipes found' : 'No recipes yet'}

-

{searchQuery - ? 'Try a different search term' - : 'Get started by adding your first recipe.'}

+
+
🧑‍🍳
+

{searchQuery ? 'No recipes found' : 'No recipes yet'}

+

{searchQuery ? 'Try another keyword or clear filters.' : 'Start your cookbook with a first delicious recipe.'}

{!searchQuery && ( - Add Your First Recipe + + Add Your First Recipe + )}
)} - {/* Recipe Grid */} {filteredRecipes.length > 0 && ( <> -
+
{filteredRecipes.map((recipe) => ( ))} @@ -175,8 +228,8 @@ export function RecipeListPage() {