Initial commit: json-render crepes demo
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
This commit is contained in:
172
lib/components.tsx
Normal file
172
lib/components.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
import React from "react";
|
||||
|
||||
export interface RecipeHeaderProps {
|
||||
title: string;
|
||||
chef: string;
|
||||
origin: string;
|
||||
prepTime: string;
|
||||
servings: number;
|
||||
}
|
||||
|
||||
export function RecipeHeader({ title, chef, origin, prepTime, servings }: RecipeHeaderProps) {
|
||||
return (
|
||||
<div className="bg-gradient-to-r from-amber-50 to-orange-50 p-8 rounded-2xl shadow-lg mb-8 border-2 border-amber-200">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-3">{title}</h2>
|
||||
<div className="flex flex-wrap gap-4 text-lg">
|
||||
<span className="bg-white px-4 py-2 rounded-full shadow-sm">
|
||||
👨🍳 <strong>Chef:</strong> {chef}
|
||||
</span>
|
||||
<span className="bg-white px-4 py-2 rounded-full shadow-sm">
|
||||
🇫🇷 <strong>Origin:</strong> {origin}
|
||||
</span>
|
||||
<span className="bg-white px-4 py-2 rounded-full shadow-sm">
|
||||
⏱️ <strong>Prep:</strong> {prepTime}
|
||||
</span>
|
||||
<span className="bg-white px-4 py-2 rounded-full shadow-sm">
|
||||
🍽️ <strong>Serves:</strong> {servings}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ChefInfoProps {
|
||||
name: string;
|
||||
bio: string;
|
||||
sourceUrl: string;
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export function ChefInfo({ name, bio, sourceUrl, imageUrl }: ChefInfoProps) {
|
||||
return (
|
||||
<div className="bg-blue-50 p-6 rounded-xl shadow-md mb-8 border-l-4 border-blue-500">
|
||||
<div className="flex items-start gap-4">
|
||||
{imageUrl && (
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt={name}
|
||||
className="w-24 h-24 rounded-full object-cover shadow-lg"
|
||||
/>
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-2">{name}</h3>
|
||||
<p className="text-gray-700 mb-3">{bio}</p>
|
||||
<a
|
||||
href={sourceUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 text-blue-600 hover:text-blue-800 font-semibold underline"
|
||||
>
|
||||
📖 View Original Recipe
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface IngredientListProps {
|
||||
title: string;
|
||||
ingredients: Array<{ quantity: string; name: string }>;
|
||||
}
|
||||
|
||||
export function IngredientList({ title, ingredients }: IngredientListProps) {
|
||||
return (
|
||||
<div className="bg-green-50 p-6 rounded-xl shadow-md mb-6 border-l-4 border-green-500">
|
||||
<h4 className="text-2xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||
🥚 {title}
|
||||
</h4>
|
||||
<ul className="space-y-2">
|
||||
{ingredients.map((ingredient, index) => (
|
||||
<li key={index} className="flex items-baseline gap-3 text-lg">
|
||||
<span className="text-green-600 font-bold">•</span>
|
||||
<span className="font-semibold text-gray-900 min-w-[120px]">
|
||||
{ingredient.quantity}
|
||||
</span>
|
||||
<span className="text-gray-700">{ingredient.name}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface InstructionStepsProps {
|
||||
steps: Array<{ number: number; instruction: string }>;
|
||||
}
|
||||
|
||||
export function InstructionSteps({ steps }: InstructionStepsProps) {
|
||||
return (
|
||||
<div className="bg-purple-50 p-6 rounded-xl shadow-md mb-6 border-l-4 border-purple-500">
|
||||
<h4 className="text-2xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||
📝 Instructions
|
||||
</h4>
|
||||
<ol className="space-y-4">
|
||||
{steps.map((step) => (
|
||||
<li key={step.number} className="flex gap-4">
|
||||
<span className="flex-shrink-0 w-8 h-8 bg-purple-500 text-white rounded-full flex items-center justify-center font-bold">
|
||||
{step.number}
|
||||
</span>
|
||||
<p className="text-gray-700 text-lg pt-1">{step.instruction}</p>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface TipCardProps {
|
||||
tip: string;
|
||||
author: string;
|
||||
}
|
||||
|
||||
export function TipCard({ tip, author }: TipCardProps) {
|
||||
return (
|
||||
<div className="bg-yellow-50 p-6 rounded-xl shadow-md mb-6 border-l-4 border-yellow-500">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-3xl">💡</span>
|
||||
<div>
|
||||
<p className="text-gray-800 text-lg mb-2 italic">“{tip}”</p>
|
||||
<p className="text-gray-600 font-semibold">— {author}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface Recipe {
|
||||
title: string;
|
||||
description: string;
|
||||
imageUrl?: string;
|
||||
chef: ChefInfoProps;
|
||||
header: RecipeHeaderProps;
|
||||
ingredientLists: IngredientListProps[];
|
||||
steps: InstructionStepsProps;
|
||||
tip: TipCardProps;
|
||||
}
|
||||
|
||||
export function RecipeCard({ recipe }: { recipe: Recipe }) {
|
||||
return (
|
||||
<div className="bg-white p-8 rounded-2xl shadow-xl mb-8 border border-gray-200">
|
||||
{recipe.imageUrl && (
|
||||
<img
|
||||
src={recipe.imageUrl}
|
||||
alt={recipe.title}
|
||||
className="w-full h-64 object-cover rounded-xl mb-6 shadow-md"
|
||||
/>
|
||||
)}
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-3">{recipe.title}</h2>
|
||||
<p className="text-gray-600 text-lg mb-6">{recipe.description}</p>
|
||||
|
||||
<ChefInfo {...recipe.chef} />
|
||||
<RecipeHeader {...recipe.header} />
|
||||
|
||||
{recipe.ingredientLists.map((list, index) => (
|
||||
<IngredientList key={index} {...list} />
|
||||
))}
|
||||
|
||||
<InstructionSteps {...recipe.steps} />
|
||||
<TipCard {...recipe.tip} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user