Seed: restaurant template (17 files)
This commit is contained in:
@@ -0,0 +1,167 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Download, UtensilsCrossed, Wine, Star } from "lucide-react";
|
||||||
|
import Header from "@/components/Header";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
import { useSaas } from "@/hooks/use-tygarun";
|
||||||
|
|
||||||
|
const menuItems = {
|
||||||
|
starters: [
|
||||||
|
{ name: "Burrata & Heirloom Tomato", desc: "Creamy burrata, vine-ripened tomatoes, basil oil, aged balsamic", price: "$18", badges: ["V"] },
|
||||||
|
{ name: "Seared Scallops", desc: "Pan-seared diver scallops, cauliflower purée, truffle vinaigrette", price: "$24", badges: ["GF"] },
|
||||||
|
{ name: "Duck Confit Terrine", desc: "Slow-cooked duck leg, pistachio, cornichon, sourdough toast", price: "$19", badges: [] },
|
||||||
|
{ name: "Wild Mushroom Risotto", desc: "Arborio rice, porcini, chanterelle, shaved parmesan, herb oil", price: "$16", badges: ["V", "GF"] },
|
||||||
|
],
|
||||||
|
mains: [
|
||||||
|
{ name: "Wagyu Beef Tenderloin", desc: "A5 wagyu, bone marrow butter, roasted root vegetables, red wine jus", price: "$62", badges: ["GF"] },
|
||||||
|
{ name: "Pan-Roasted Halibut", desc: "Wild halibut, saffron beurre blanc, fennel, olive tapenade", price: "$44", badges: [] },
|
||||||
|
{ name: "Rack of Lamb", desc: "Herb-crusted lamb, pommes dauphine, mint gremolata", price: "$52", badges: [] },
|
||||||
|
{ name: "Truffle Tagliatelle", desc: "Hand-rolled pasta, black truffle cream, pecorino, fresh herbs", price: "$36", badges: ["V"] },
|
||||||
|
{ name: "Roasted Duck Breast", desc: "Cherry-glazed duck, confit potatoes, wilted greens, port reduction", price: "$48", badges: ["GF"] },
|
||||||
|
],
|
||||||
|
desserts: [
|
||||||
|
{ name: "Valrhona Chocolate Fondant", desc: "Warm dark chocolate cake, vanilla bean ice cream, gold leaf", price: "$16", badges: ["V"] },
|
||||||
|
{ name: "Crème Brûlée", desc: "Tahitian vanilla custard, caramelized sugar, shortbread", price: "$14", badges: ["GF"] },
|
||||||
|
{ name: "Tarte Tatin", desc: "Caramelized apple, puff pastry, calvados cream", price: "$15", badges: [] },
|
||||||
|
{ name: "Lemon Posset", desc: "Set cream, fresh berries, lavender meringue", price: "$13", badges: ["V", "GF"] },
|
||||||
|
],
|
||||||
|
drinks: [
|
||||||
|
{ name: "Signature Old Fashioned", desc: "Woodford Reserve, demerara, Angostura, flamed orange peel", price: "$19", badges: [] },
|
||||||
|
{ name: "Garden Spritz", desc: "Elderflower, prosecco, cucumber, fresh mint, soda", price: "$16", badges: [] },
|
||||||
|
{ name: "Sommelier Wine Pairing", desc: "Five-course curated wine flight matched to your meal", price: "$65", badges: [] },
|
||||||
|
{ name: "Espresso Martini", desc: "Ketel One, fresh espresso, Kahlúa, vanilla", price: "$18", badges: [] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const tastingCourses = [
|
||||||
|
"Amuse-bouche · Seasonal selection", "Seared Scallops · Cauliflower, truffle", "Wagyu Beef · Root vegetables, red wine jus", "Artisan Cheese · Honeycomb, fig compote", "Chocolate Fondant · Vanilla ice cream"
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderItems = (items: { name: string; desc: string; price: string; badges: string[] }[]) =>
|
||||||
|
items.map((item, i) => (
|
||||||
|
<div key={i} className={`py-5 \${i < items.length - 1 ? "border-b border-dotted border-stone-300 dark:border-stone-700" : ""}`}>
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-serif text-lg font-medium text-foreground">{item.name}</span>
|
||||||
|
{item.badges.map((b, j) => (
|
||||||
|
<Badge key={j} variant="outline" className="text-[10px] px-1.5 py-0 font-medium border-amber-600/40 text-amber-700 dark:text-amber-400">{b}</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-sm italic text-muted-foreground">{item.desc}</p>
|
||||||
|
</div>
|
||||||
|
<span className="font-semibold text-amber-700 dark:text-amber-400 whitespace-nowrap">{item.price}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
export default function MenuPage() {
|
||||||
|
// Menu catalog from tyga.run ecommerce (falls back to seed until store is live)
|
||||||
|
const { data: catalog } = useSaas("ecommerce", "/catalog");
|
||||||
|
const byCat = (cat: keyof typeof menuItems) => {
|
||||||
|
const live = Array.isArray(catalog)
|
||||||
|
? catalog
|
||||||
|
.filter((p: any) => String(p.category || "").toLowerCase() === cat)
|
||||||
|
.map((p: any) => ({
|
||||||
|
name: p.name,
|
||||||
|
desc: p.description || "",
|
||||||
|
price: typeof p.priceCents === "number" ? "$" + (p.priceCents / 100).toFixed(0) : (p.price || ""),
|
||||||
|
badges: Array.isArray(p.badges) ? p.badges : [],
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
return live.length ? live : menuItems[cat];
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background text-foreground antialiased">
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
{/* ── HERO ── */}
|
||||||
|
<section className="relative pt-32 pb-16 bg-gradient-to-b from-stone-950 to-background overflow-hidden">
|
||||||
|
<div className="absolute inset-0 pointer-events-none">
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[400px] h-[400px] rounded-full bg-amber-600 opacity-5 blur-[100px]" />
|
||||||
|
</div>
|
||||||
|
<div className="relative z-10 mx-auto max-w-4xl px-6 text-center">
|
||||||
|
<div className="flex items-center justify-center gap-3 mb-4 text-amber-600/60 text-sm tracking-[0.3em]">
|
||||||
|
<span>—</span><UtensilsCrossed className="h-4 w-4" /><span>—</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-4xl sm:text-5xl font-serif tracking-wide text-white animate-fade-up">Our Menu</h1>
|
||||||
|
<p className="mt-4 text-stone-400 animate-fade-up">Seasonal selections crafted daily by our executive chef</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── TABBED MENU ── */}
|
||||||
|
<section className="py-24">
|
||||||
|
<div className="mx-auto max-w-4xl px-6">
|
||||||
|
<Tabs defaultValue="starters" className="w-full">
|
||||||
|
<TabsList className="grid w-full grid-cols-4 bg-stone-100 dark:bg-stone-900 rounded-none h-12">
|
||||||
|
<TabsTrigger value="starters" className="rounded-none uppercase text-xs tracking-wider data-[state=active]:bg-amber-700 data-[state=active]:text-white data-[state=active]:shadow-sm transition-all">Starters</TabsTrigger>
|
||||||
|
<TabsTrigger value="mains" className="rounded-none uppercase text-xs tracking-wider data-[state=active]:bg-amber-700 data-[state=active]:text-white data-[state=active]:shadow-sm transition-all">Mains</TabsTrigger>
|
||||||
|
<TabsTrigger value="desserts" className="rounded-none uppercase text-xs tracking-wider data-[state=active]:bg-amber-700 data-[state=active]:text-white data-[state=active]:shadow-sm transition-all">Desserts</TabsTrigger>
|
||||||
|
<TabsTrigger value="drinks" className="rounded-none uppercase text-xs tracking-wider data-[state=active]:bg-amber-700 data-[state=active]:text-white data-[state=active]:shadow-sm transition-all">Drinks</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="starters" className="mt-8">
|
||||||
|
{renderItems(byCat("starters"))}
|
||||||
|
<div className="flex items-center gap-4 my-8"><div className="flex-1 h-px bg-amber-700/20" /><UtensilsCrossed className="h-4 w-4 text-amber-600/40" /><div className="flex-1 h-px bg-amber-700/20" /></div>
|
||||||
|
<p className="text-xs text-center text-muted-foreground italic flex items-center justify-center gap-1"><Wine className="h-3.5 w-3.5 text-amber-500/50" /> Wine pairing recommended with select starters</p>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="mains" className="mt-8">
|
||||||
|
{renderItems(byCat("mains"))}
|
||||||
|
<div className="flex items-center gap-4 my-8"><div className="flex-1 h-px bg-amber-700/20" /><UtensilsCrossed className="h-4 w-4 text-amber-600/40" /><div className="flex-1 h-px bg-amber-700/20" /></div>
|
||||||
|
<p className="text-xs text-center text-muted-foreground italic flex items-center justify-center gap-1"><Wine className="h-3.5 w-3.5 text-amber-500/50" /> Ask your sommelier for pairing suggestions</p>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="desserts" className="mt-8">
|
||||||
|
{renderItems(byCat("desserts"))}
|
||||||
|
<div className="flex items-center gap-4 my-8"><div className="flex-1 h-px bg-amber-700/20" /><UtensilsCrossed className="h-4 w-4 text-amber-600/40" /><div className="flex-1 h-px bg-amber-700/20" /></div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="drinks" className="mt-8">{renderItems(byCat("drinks"))}</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
<div className="mt-6 flex items-center gap-4 text-xs text-muted-foreground">
|
||||||
|
<span className="flex items-center gap-1"><Badge variant="outline" className="text-[10px] px-1 py-0 border-amber-600/40 text-amber-700 dark:text-amber-400">V</Badge> Vegetarian</span>
|
||||||
|
<span className="flex items-center gap-1"><Badge variant="outline" className="text-[10px] px-1 py-0 border-amber-600/40 text-amber-700 dark:text-amber-400">GF</Badge> Gluten Free</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Separator className="mx-auto max-w-4xl" />
|
||||||
|
|
||||||
|
{/* ── TASTING MENU ── */}
|
||||||
|
<section className="py-24">
|
||||||
|
<div className="mx-auto max-w-3xl px-6 text-center">
|
||||||
|
<div className="flex items-center justify-center gap-3 mb-4 text-amber-600/60 text-sm tracking-[0.3em]">
|
||||||
|
<span>—</span><Star className="h-4 w-4" /><span>—</span>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-3xl font-serif tracking-wide mb-2 animate-fade-up">Chef's Tasting Menu</h2>
|
||||||
|
<p className="text-muted-foreground mb-8">A five-course journey through our most celebrated dishes</p>
|
||||||
|
<Card className="border-stone-200 dark:border-stone-800 rounded-sm max-w-md mx-auto">
|
||||||
|
<CardContent className="pt-6 space-y-3">
|
||||||
|
{tastingCourses.map((c, i) => (
|
||||||
|
<div key={i} className="py-2 text-sm font-serif text-muted-foreground border-b border-dotted border-stone-200 dark:border-stone-800 last:border-0">{c}</div>
|
||||||
|
))}
|
||||||
|
<div className="pt-4 text-2xl font-serif font-bold text-amber-700 dark:text-amber-400">$135 per person</div>
|
||||||
|
<p className="text-xs text-muted-foreground">Wine pairing available for $85</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<p className="mt-6 text-sm text-muted-foreground italic flex items-center justify-center gap-2">
|
||||||
|
<Wine className="h-4 w-4 text-amber-600" /> Ask your sommelier for wine pairing recommendations
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── DOWNLOAD ── */}
|
||||||
|
<section className="py-24 bg-muted/30">
|
||||||
|
<div className="mx-auto max-w-4xl px-6 text-center">
|
||||||
|
<Button variant="outline" className="rounded-none tracking-widest uppercase text-xs px-8 active:scale-[0.98]">
|
||||||
|
<Download className="mr-2 h-4 w-4" /> Download PDF Menu
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user