Initial Commit
This commit is contained in:
commit
1a9c69fc9d
34 files changed
+7479
No files matched your search
@@ -0,0 +1,247 @@
|
||||
import React, { useState } from 'react';
|
||||
import { createReservation, type ReservationData } from '../lib/pocketbase';
|
||||
|
||||
interface ReservationModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function ReservationModal({ isOpen, onClose }: ReservationModalProps) {
|
||||
const [formData, setFormData] = useState<ReservationData>({
|
||||
name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
time: '19:30',
|
||||
guests: 2,
|
||||
location: 'Chandigarh (Sector 9)',
|
||||
special_requests: '',
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [confirmedId, setConfirmedId] = useState<string | null>(null);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setErrorMessage(null);
|
||||
|
||||
const res = await createReservation(formData);
|
||||
setLoading(false);
|
||||
|
||||
if (res.success && res.id) {
|
||||
setConfirmedId(res.id);
|
||||
} else {
|
||||
setErrorMessage(res.error || 'Failed to submit reservation. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setConfirmedId(null);
|
||||
setErrorMessage(null);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-md animate-fade-in">
|
||||
<div
|
||||
className="bg-[#1c1b1b] border border-[#ffb77b]/40 rounded-xl max-w-lg w-full p-6 sm:p-8 relative shadow-2xl overflow-y-auto max-h-[90vh]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={handleReset}
|
||||
className="absolute top-4 right-4 text-[#a08d80] hover:text-[#ffb77b] transition-colors p-2 rounded-full hover:bg-[#201f1f]"
|
||||
>
|
||||
<span className="material-symbols-outlined text-2xl">close</span>
|
||||
</button>
|
||||
|
||||
{confirmedId ? (
|
||||
<div className="text-center py-8 space-y-4">
|
||||
<div className="w-16 h-16 rounded-full bg-[#234f3b] text-[#ffb77b] flex items-center justify-center mx-auto border border-[#ffb77b]/30">
|
||||
<span className="material-symbols-outlined text-3xl">check_circle</span>
|
||||
</div>
|
||||
<h3 className="font-['Playfair_Display'] text-2xl font-bold text-[#e5e2e1]">
|
||||
Table Reserved!
|
||||
</h3>
|
||||
<p className="text-[#d8c3b4] text-sm max-w-sm mx-auto">
|
||||
We look forward to welcoming you to Hops n Grains ({formData.location}). A confirmation message will be sent to <strong>{formData.phone}</strong>.
|
||||
</p>
|
||||
<div className="bg-[#201f1f] p-3 rounded-lg border border-[#524439]/40 inline-block text-xs font-mono text-[#ffb77b]">
|
||||
Booking ID: #{confirmedId}
|
||||
</div>
|
||||
<div className="pt-4">
|
||||
<button
|
||||
onClick={handleReset}
|
||||
className="bg-[#ffb77b] text-[#4d2700] font-semibold px-6 py-2.5 rounded hover:bg-[#ffdcc2] transition-colors inner-shadow-copper"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="mb-6">
|
||||
<span className="text-xs uppercase tracking-widest text-[#ffb77b] font-semibold">
|
||||
Table Booking
|
||||
</span>
|
||||
<h2 className="font-['Playfair_Display'] text-2xl sm:text-3xl font-bold text-[#e5e2e1] mt-1">
|
||||
Reserve Your Experience
|
||||
</h2>
|
||||
<p className="text-[#d8c3b4] text-sm mt-1">
|
||||
Handcrafted beer & artisanal dining at Chandigarh & Panchkula.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{errorMessage && (
|
||||
<div className="mb-4 p-3 rounded bg-red-950/50 border border-red-800 text-red-300 text-xs">
|
||||
{errorMessage}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Location selector */}
|
||||
<div>
|
||||
<label className="block text-xs uppercase tracking-wider text-[#a08d80] font-semibold mb-1">
|
||||
Brewery Branch *
|
||||
</label>
|
||||
<select
|
||||
value={formData.location}
|
||||
onChange={(e) => setFormData({ ...formData, location: e.target.value })}
|
||||
className="w-full bg-[#201f1f] border border-[#524439]/60 rounded-md px-3.5 py-2.5 text-sm text-[#e5e2e1] focus:outline-none focus:border-[#ffb77b]"
|
||||
required
|
||||
>
|
||||
<option value="Chandigarh (Sector 9)">Hops n Grains — Sector 9, Chandigarh</option>
|
||||
<option value="Panchkula (Sector 5)">Hops n Grains — Sector 5, Panchkula</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Name & Phone */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs uppercase tracking-wider text-[#a08d80] font-semibold mb-1">
|
||||
Your Name *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
placeholder="e.g. Tanshu"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full bg-[#201f1f] border border-[#524439]/60 rounded-md px-3.5 py-2.5 text-sm text-[#e5e2e1] focus:outline-none focus:border-[#ffb77b]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs uppercase tracking-wider text-[#a08d80] font-semibold mb-1">
|
||||
Phone Number *
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
required
|
||||
placeholder="+91 98765 43210"
|
||||
value={formData.phone}
|
||||
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
|
||||
className="w-full bg-[#201f1f] border border-[#524439]/60 rounded-md px-3.5 py-2.5 text-sm text-[#e5e2e1] focus:outline-none focus:border-[#ffb77b]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div>
|
||||
<label className="block text-xs uppercase tracking-wider text-[#a08d80] font-semibold mb-1">
|
||||
Email Address *
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
placeholder="name@example.com"
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||
className="w-full bg-[#201f1f] border border-[#524439]/60 rounded-md px-3.5 py-2.5 text-sm text-[#e5e2e1] focus:outline-none focus:border-[#ffb77b]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Date, Time, Guests */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs uppercase tracking-wider text-[#a08d80] font-semibold mb-1">
|
||||
Date *
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
required
|
||||
value={formData.date}
|
||||
onChange={(e) => setFormData({ ...formData, date: e.target.value })}
|
||||
className="w-full bg-[#201f1f] border border-[#524439]/60 rounded-md px-3 py-2 text-sm text-[#e5e2e1] focus:outline-none focus:border-[#ffb77b]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs uppercase tracking-wider text-[#a08d80] font-semibold mb-1">
|
||||
Time *
|
||||
</label>
|
||||
<input
|
||||
type="time"
|
||||
required
|
||||
value={formData.time}
|
||||
onChange={(e) => setFormData({ ...formData, time: e.target.value })}
|
||||
className="w-full bg-[#201f1f] border border-[#524439]/60 rounded-md px-3 py-2 text-sm text-[#e5e2e1] focus:outline-none focus:border-[#ffb77b]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs uppercase tracking-wider text-[#a08d80] font-semibold mb-1">
|
||||
Guests *
|
||||
</label>
|
||||
<select
|
||||
value={formData.guests}
|
||||
onChange={(e) => setFormData({ ...formData, guests: Number(e.target.value) })}
|
||||
className="w-full bg-[#201f1f] border border-[#524439]/60 rounded-md px-3 py-2 text-sm text-[#e5e2e1] focus:outline-none focus:border-[#ffb77b]"
|
||||
>
|
||||
{[1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 16, 20].map((n) => (
|
||||
<option key={n} value={n}>
|
||||
{n} {n === 1 ? 'Guest' : 'Guests'}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Special Requests */}
|
||||
<div>
|
||||
<label className="block text-xs uppercase tracking-wider text-[#a08d80] font-semibold mb-1">
|
||||
Special Requests (Optional)
|
||||
</label>
|
||||
<textarea
|
||||
rows={2}
|
||||
placeholder="e.g., Table near copper brewing tanks, birthday anniversary celebration"
|
||||
value={formData.special_requests}
|
||||
onChange={(e) => setFormData({ ...formData, special_requests: e.target.value })}
|
||||
className="w-full bg-[#201f1f] border border-[#524439]/60 rounded-md px-3.5 py-2 text-sm text-[#e5e2e1] focus:outline-none focus:border-[#ffb77b]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pt-3">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-[#ffb77b] text-[#4d2700] font-semibold px-6 py-3.5 rounded-md hover:bg-[#ffdcc2] transition-colors inner-shadow-copper flex items-center justify-center gap-2 shadow-lg disabled:opacity-50 cursor-pointer"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span className="w-4 h-4 border-2 border-[#4d2700] border-t-transparent rounded-full animate-spin" />
|
||||
Securing Table...
|
||||
</>
|
||||
) : (
|
||||
'Confirm Table Reservation'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user