Seed: restaurant template (17 files)
This commit is contained in:
@@ -0,0 +1,156 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { ArrowRight, Wine, Clock, Shirt, Car, AlertCircle, MapPin, CheckCircle2 } from "lucide-react";
|
||||||
|
import Header from "@/components/Header";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
import { useSaasMutation } from "@/hooks/use-tygarun";
|
||||||
|
|
||||||
|
export default function ReservationsPage() {
|
||||||
|
const [time, setTime] = useState("7:00 PM");
|
||||||
|
const [guests, setGuests] = useState("2");
|
||||||
|
const [occasion, setOccasion] = useState("No special occasion");
|
||||||
|
const { mutate: bookTable, loading: booking } = useSaasMutation("scheduling", "/bookings");
|
||||||
|
|
||||||
|
const handleReserve = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const form = e.currentTarget;
|
||||||
|
const fd = new FormData(form);
|
||||||
|
try {
|
||||||
|
await bookTable({
|
||||||
|
name: fd.get("name"),
|
||||||
|
email: fd.get("email"),
|
||||||
|
phone: fd.get("phone"),
|
||||||
|
date: fd.get("date"),
|
||||||
|
time,
|
||||||
|
partySize: guests,
|
||||||
|
occasion,
|
||||||
|
requests: fd.get("requests"),
|
||||||
|
});
|
||||||
|
toast.success("Reservation confirmed! Check your email for details.");
|
||||||
|
form.reset();
|
||||||
|
} catch {
|
||||||
|
toast.error("Could not confirm reservation. Please try again.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background text-foreground antialiased">
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<section className="relative pt-32 pb-16 bg-gradient-to-b from-stone-950 to-background">
|
||||||
|
<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><Wine className="h-4 w-4" /><span>—</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-4xl sm:text-5xl font-serif tracking-wide text-white animate-fade-up">Make a Reservation</h1>
|
||||||
|
<p className="mt-4 text-stone-400 animate-fade-up">Join us for an unforgettable evening of exceptional cuisine</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-24">
|
||||||
|
<div className="mx-auto max-w-6xl px-6 grid lg:grid-cols-3 gap-12">
|
||||||
|
{/* ── FORM ── */}
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<div className="flex items-center justify-center gap-3 mb-8 text-amber-600/40 text-sm tracking-[0.3em]">
|
||||||
|
<span>—</span><span>✦</span><span>—</span>
|
||||||
|
</div>
|
||||||
|
<Card className="border-stone-200 dark:border-stone-800 rounded-none shadow-lg">
|
||||||
|
<CardContent className="p-8">
|
||||||
|
<form onSubmit={handleReserve} className="space-y-6">
|
||||||
|
<div className="grid sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium uppercase tracking-wider text-muted-foreground mb-2 block">Name</label>
|
||||||
|
<Input name="name" required placeholder="Your full name" className="rounded-none border-stone-300 dark:border-stone-700 focus-visible:ring-amber-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium uppercase tracking-wider text-muted-foreground mb-2 block">Email</label>
|
||||||
|
<Input name="email" required type="email" placeholder="your@email.com" className="rounded-none border-stone-300 dark:border-stone-700 focus-visible:ring-amber-600" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium uppercase tracking-wider text-muted-foreground mb-2 block">Phone</label>
|
||||||
|
<Input name="phone" type="tel" placeholder="+44 20 7946 0000" className="rounded-none border-stone-300 dark:border-stone-700 focus-visible:ring-amber-600" />
|
||||||
|
</div>
|
||||||
|
<div className="grid sm:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium uppercase tracking-wider text-muted-foreground mb-2 block">Date</label>
|
||||||
|
<Input name="date" required type="date" className="rounded-none border-stone-300 dark:border-stone-700 focus-visible:ring-amber-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium uppercase tracking-wider text-muted-foreground mb-2 block">Time</label>
|
||||||
|
<select value={time} onChange={(e) => setTime(e.target.value)} className="flex h-10 w-full rounded-none border border-stone-300 dark:border-stone-700 bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-600">
|
||||||
|
{["6:00 PM","6:30 PM","7:00 PM","7:30 PM","8:00 PM","8:30 PM","9:00 PM","9:30 PM"].map((t) => (
|
||||||
|
<option key={t} value={t}>{t}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<p className="text-xs text-amber-600/60 mt-1">Most popular: 7:00 PM — 8:30 PM</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium uppercase tracking-wider text-muted-foreground mb-2 block">Guests</label>
|
||||||
|
<select value={guests} onChange={(e) => setGuests(e.target.value)} className="flex h-10 w-full rounded-none border border-stone-300 dark:border-stone-700 bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-600">
|
||||||
|
{[1,2,3,4,5,6,7,8].map((n) => (
|
||||||
|
<option key={n} value={String(n)}>{n} {n === 1 ? "Guest" : "Guests"}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium uppercase tracking-wider text-muted-foreground mb-2 block">Occasion</label>
|
||||||
|
<select value={occasion} onChange={(e) => setOccasion(e.target.value)} className="flex h-10 w-full rounded-none border border-stone-300 dark:border-stone-700 bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-600">
|
||||||
|
{["No special occasion", "Birthday", "Anniversary", "Business Dinner", "Date Night", "Celebration", "Other"].map((o) => (
|
||||||
|
<option key={o} value={o}>{o}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium uppercase tracking-wider text-muted-foreground mb-2 block">Special Requests</label>
|
||||||
|
<Textarea name="requests" placeholder="Allergies, dietary requirements, special occasions..." className="rounded-none border-stone-300 dark:border-stone-700 focus-visible:ring-amber-600 min-h-[100px]" />
|
||||||
|
</div>
|
||||||
|
<Button type="submit" disabled={booking} className="w-full rounded-none bg-amber-700 hover:bg-amber-600 text-white tracking-widest uppercase text-sm py-6 active:scale-[0.98]">
|
||||||
|
{booking ? "Confirming..." : "Confirm Reservation"} <ArrowRight className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<p className="text-xs text-center text-muted-foreground mt-3 flex items-center justify-center gap-1.5"><CheckCircle2 className="h-3.5 w-3.5 text-amber-600/60" /> Confirmation sent to your email</p>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── SIDEBAR INFO ── */}
|
||||||
|
<div className="space-y-8">
|
||||||
|
{[
|
||||||
|
{ icon: Clock, title: "Opening Hours", lines: ["Tuesday – Thursday: 6:00 PM – 10:00 PM", "Friday – Saturday: 6:00 PM – 11:00 PM", "Sunday – Monday: Closed"] },
|
||||||
|
{ icon: Shirt, title: "Dress Code", lines: ["Smart casual. Jackets preferred for gentlemen in the main dining room."] },
|
||||||
|
{ icon: Car, title: "Parking", lines: ["Complimentary valet parking available. Underground garage on Golden Lane."] },
|
||||||
|
{ icon: AlertCircle, title: "Cancellation Policy", lines: ["Cancellations accepted up to 24 hours before your reservation. Late cancellations may incur a fee."] },
|
||||||
|
].map((info, i) => (
|
||||||
|
<div key={i}>
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<info.icon className="h-4 w-4 text-amber-600" />
|
||||||
|
<h3 className="font-medium text-sm uppercase tracking-wider">{info.title}</h3>
|
||||||
|
</div>
|
||||||
|
{info.lines.map((line, j) => (
|
||||||
|
<p key={j} className="text-sm text-muted-foreground">{line}</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── MAP PLACEHOLDER ── */}
|
||||||
|
<section className="py-24 bg-muted/30">
|
||||||
|
<div className="mx-auto max-w-6xl px-6">
|
||||||
|
<div className="aspect-[3/1] rounded-sm bg-stone-200 dark:bg-stone-800 flex items-center justify-center">
|
||||||
|
<MapPin className="h-12 w-12 text-muted-foreground/30" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user