This commit is contained in:
Vula Builder
2026-06-23 08:39:09 +00:00
parent a3675cf284
commit 4b7c819b50
@@ -0,0 +1,123 @@
'use client'
import { useState } from 'react'
import { CalendarDays, CircleCheck, Clock, MessageSquare, Phone, User, Users } from 'lucide-react'
interface BookingForm {
customer_name: string
customer_phone: string
customer_email: string
booking_date: string
booking_time: string
party_size: string
notes: string
}
export default function ReservationSection() {
const [form, setForm] = useState<BookingForm>({
customer_name: '', customer_phone: '', customer_email: '',
booking_date: '', booking_time: '', party_size: '2', notes: '',
})
const [submitting, setSubmitting] = useState(false)
const [success, setSuccess] = useState(false)
const [error, setError] = useState<string | null>(null)
const update = (k: keyof BookingForm) => (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) =>
setForm(p => ({ ...p, [k]: e.target.value }))
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setSubmitting(true)
setError(null)
try {
const res = await fetch('/api/medusa/store/bookings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
booking_date: form.booking_date,
booking_time: form.booking_time,
service_name: 'Table Reservation',
customer_name: form.customer_name,
customer_phone: form.customer_phone,
customer_email: form.customer_email || undefined,
notes: [`Party of ${form.party_size}`, form.notes].filter(Boolean).join(' — ') || undefined,
}),
signal: AbortSignal.timeout(15000),
})
if (!res.ok) {
const body = await res.json().catch(() => ({}))
throw new Error((body as any).message || 'Booking failed')
}
setSuccess(true)
} catch (err: any) {
setError(err?.message || 'Could not submit reservation. Please call us directly.')
} finally {
setSubmitting(false)
}
}
const inputCls = 'w-full border border-border rounded-lg px-3 py-2.5 text-sm bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary'
if (success) return (
<section className="py-20 bg-background" id="reservations">
<div className="container mx-auto px-4 max-w-lg text-center">
<CircleCheck className="h-14 w-14 text-green-500 mx-auto mb-4" />
<h2 className="text-2xl font-bold mb-2 text-foreground">Reservation Received!</h2>
<p className="text-muted-foreground">We will confirm your table via phone or email. See you soon!</p>
<button onClick={() => { setSuccess(false); setForm({ customer_name: '', customer_phone: '', customer_email: '', booking_date: '', booking_time: '', party_size: '2', notes: '' }) }}
className="mt-6 text-sm text-primary hover:underline">Make another reservation</button>
</div>
</section>
)
return (
<section className="py-20 bg-muted/30" id="reservations">
<div className="container mx-auto px-4 max-w-2xl">
<div className="text-center mb-10">
<h2 className="text-3xl font-bold text-foreground mb-3">Reserve a Table</h2>
<p className="text-muted-foreground">Book in advance to secure your spot. We'll confirm within 2 hours.</p>
</div>
<form onSubmit={handleSubmit} className="bg-card border border-border rounded-2xl p-8 space-y-5 shadow-sm">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-foreground mb-1.5 flex items-center gap-1.5"><User className="h-3.5 w-3.5" />Your Name</label>
<input required value={form.customer_name} onChange={update('customer_name')} placeholder="Full name" className={inputCls} />
</div>
<div>
<label className="text-sm font-medium text-foreground mb-1.5 flex items-center gap-1.5"><Phone className="h-3.5 w-3.5" />Phone</label>
<input required value={form.customer_phone} onChange={update('customer_phone')} placeholder="+27 xx xxx xxxx" type="tel" className={inputCls} />
</div>
</div>
<div>
<label className="text-sm font-medium text-foreground mb-1.5 block">Email (optional)</label>
<input value={form.customer_email} onChange={update('customer_email')} placeholder="email@example.com" type="email" className={inputCls} />
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="sm:col-span-1">
<label className="text-sm font-medium text-foreground mb-1.5 flex items-center gap-1.5"><CalendarDays className="h-3.5 w-3.5" />Date</label>
<input required type="date" value={form.booking_date} onChange={update('booking_date')} min={new Date().toISOString().split('T')[0]} className={inputCls} />
</div>
<div>
<label className="text-sm font-medium text-foreground mb-1.5 flex items-center gap-1.5"><Clock className="h-3.5 w-3.5" />Time</label>
<input required type="time" value={form.booking_time} onChange={update('booking_time')} className={inputCls} />
</div>
<div>
<label className="text-sm font-medium text-foreground mb-1.5 flex items-center gap-1.5"><Users className="h-3.5 w-3.5" />Party Size</label>
<select value={form.party_size} onChange={update('party_size')} className={inputCls}>
{[1,2,3,4,5,6,7,8,10,12].map(n => <option key={n} value={n}>{n} {n === 1 ? 'guest' : 'guests'}</option>)}
</select>
</div>
</div>
<div>
<label className="text-sm font-medium text-foreground mb-1.5 flex items-center gap-1.5"><MessageSquare className="h-3.5 w-3.5" />Special Requests (optional)</label>
<textarea value={form.notes} onChange={update('notes')} placeholder="Dietary requirements, special occasion, seating preference..." rows={3} className={inputCls + ' resize-none'} />
</div>
{error && <p className="text-sm text-destructive bg-destructive/10 rounded-lg px-3 py-2">{error}</p>}
<button type="submit" disabled={submitting}
className="w-full bg-primary text-primary-foreground py-3 rounded-lg hover:bg-primary/90 transition-colors font-semibold disabled:opacity-60">
{submitting ? 'Submitting...' : 'Confirm Reservation'}
</button>
</form>
</div>
</section>
)
}