276 lines
11 KiB
TypeScript
276 lines
11 KiB
TypeScript
'use client'
|
|
|
|
import React, { useState } from 'react';
|
|
import { Calendar, Clock, User, Mail, Users } from 'lucide-react';
|
|
|
|
const BookingInterface = () => {
|
|
const [selectedDate, setSelectedDate] = useState('');
|
|
const [selectedTimeSlot, setSelectedTimeSlot] = useState('');
|
|
const [selectedBookingType, setSelectedBookingType] = useState('');
|
|
const [customerName, setCustomerName] = useState('');
|
|
const [customerEmail, setCustomerEmail] = useState('');
|
|
const [participantsCount, setParticipantsCount] = useState(1);
|
|
|
|
// Mock data - will be replaced with API calls
|
|
const bookingTypes = [
|
|
{ id: '1', name: 'wheel_rental', display_name: 'Wheel Rental', requires_payment: false, price_per_person: 0 },
|
|
{ id: '2', name: 'hand_building', display_name: 'Hand Building Coworking', requires_payment: false, price_per_person: 0 },
|
|
{ id: '3', name: 'personal_workshop', display_name: 'Personal Workshop', requires_payment: true, price_per_person: 75 },
|
|
{ id: '4', name: 'group_workshop', display_name: 'Group Workshop', requires_payment: true, price_per_person: 50 }
|
|
];
|
|
|
|
// Mock available time slots
|
|
const timeSlots = [
|
|
{ id: '1', start_time: '09:00', end_time: '11:00', available: true },
|
|
{ id: '2', start_time: '11:30', end_time: '13:30', available: true },
|
|
{ id: '3', start_time: '14:00', end_time: '16:00', available: false },
|
|
{ id: '4', start_time: '16:30', end_time: '18:30', available: true },
|
|
{ id: '5', start_time: '19:00', end_time: '21:00', available: true }
|
|
];
|
|
|
|
const selectedBookingTypeData = bookingTypes.find(bt => bt.id === selectedBookingType);
|
|
|
|
const handleSubmit = () => {
|
|
console.log({
|
|
selectedBookingType,
|
|
selectedDate,
|
|
selectedTimeSlot,
|
|
customerName,
|
|
customerEmail,
|
|
participantsCount
|
|
});
|
|
};
|
|
|
|
const generateCalendarDays = () => {
|
|
const today = new Date();
|
|
const days = [];
|
|
|
|
for (let i = 0; i < 14; i++) {
|
|
const date = new Date(today);
|
|
date.setDate(today.getDate() + i);
|
|
days.push({
|
|
date: date.toISOString().split('T')[0],
|
|
day: date.getDate(),
|
|
month: date.toLocaleDateString('en', { month: 'short' }),
|
|
dayName: date.toLocaleDateString('en', { weekday: 'short' })
|
|
});
|
|
}
|
|
return days;
|
|
};
|
|
|
|
const calendarDays = generateCalendarDays();
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 p-4">
|
|
<div className="max-w-2xl mx-auto">
|
|
{/* Header */}
|
|
<div className="text-center mb-8">
|
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">Book Your Session</h1>
|
|
<p className="text-gray-600">Choose your pottery experience</p>
|
|
</div>
|
|
|
|
<div className="space-y-8">
|
|
{/* Booking Type Selection */}
|
|
<div className="bg-white rounded-xl p-6 shadow-sm">
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
|
<Calendar className="w-5 h-5" />
|
|
Select Experience
|
|
</h2>
|
|
<div className="grid gap-3">
|
|
{bookingTypes.map((type) => (
|
|
<label key={type.id} className="cursor-pointer">
|
|
<input
|
|
type="radio"
|
|
name="bookingType"
|
|
value={type.id}
|
|
checked={selectedBookingType === type.id}
|
|
onChange={(e) => setSelectedBookingType(e.target.value)}
|
|
className="sr-only"
|
|
/>
|
|
<div className={`p-4 rounded-lg border-2 transition-all ${
|
|
selectedBookingType === type.id
|
|
? 'border-blue-500 bg-blue-50'
|
|
: 'border-gray-200 hover:border-gray-300'
|
|
}`}>
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<h3 className="font-medium text-gray-900">{type.display_name}</h3>
|
|
{type.requires_payment && (
|
|
<p className="text-sm text-gray-600 mt-1">
|
|
${type.price_per_person} per person
|
|
</p>
|
|
)}
|
|
</div>
|
|
{type.requires_payment && (
|
|
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full">
|
|
Payment Required
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</label>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Date Selection */}
|
|
{selectedBookingType && (
|
|
<div className="bg-white rounded-xl p-6 shadow-sm">
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
|
<Calendar className="w-5 h-5" />
|
|
Choose Date
|
|
</h2>
|
|
<div className="grid grid-cols-7 gap-2 md:gap-3">
|
|
{calendarDays.map((day) => (
|
|
<button
|
|
key={day.date}
|
|
type="button"
|
|
onClick={() => setSelectedDate(day.date)}
|
|
className={`p-3 rounded-lg text-center transition-all ${
|
|
selectedDate === day.date
|
|
? 'bg-blue-500 text-white'
|
|
: 'bg-gray-100 hover:bg-gray-200 text-gray-700'
|
|
}`}
|
|
>
|
|
<div className="text-xs font-medium">{day.dayName}</div>
|
|
<div className="text-sm font-semibold">{day.day}</div>
|
|
<div className="text-xs">{day.month}</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Time Slot Selection */}
|
|
{selectedDate && (
|
|
<div className="bg-white rounded-xl p-6 shadow-sm">
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
|
<Clock className="w-5 h-5" />
|
|
Available Times
|
|
</h2>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
|
{timeSlots.map((slot) => (
|
|
<button
|
|
key={slot.id}
|
|
type="button"
|
|
disabled={!slot.available}
|
|
onClick={() => setSelectedTimeSlot(slot.id)}
|
|
className={`p-3 rounded-lg text-center transition-all ${
|
|
!slot.available
|
|
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
|
: selectedTimeSlot === slot.id
|
|
? 'bg-blue-500 text-white'
|
|
: 'bg-gray-100 hover:bg-gray-200 text-gray-700'
|
|
}`}
|
|
>
|
|
<div className="font-medium">
|
|
{slot.start_time} - {slot.end_time}
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Customer Details */}
|
|
{selectedTimeSlot && (
|
|
<div className="bg-white rounded-xl p-6 shadow-sm">
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
|
<User className="w-5 h-5" />
|
|
Your Details
|
|
</h2>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Full Name *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={customerName}
|
|
onChange={(e) => setCustomerName(e.target.value)}
|
|
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="Enter your full name"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Email Address *
|
|
</label>
|
|
<input
|
|
type="email"
|
|
value={customerEmail}
|
|
onChange={(e) => setCustomerEmail(e.target.value)}
|
|
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="Enter your email"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2 flex items-center gap-2">
|
|
<Users className="w-4 h-4" />
|
|
Number of Participants
|
|
</label>
|
|
<select
|
|
value={participantsCount}
|
|
onChange={(e) => setParticipantsCount(parseInt(e.target.value))}
|
|
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
>
|
|
{[1, 2, 3, 4, 5, 6, 7, 8].map(num => (
|
|
<option key={num} value={num}>{num} participant{num > 1 ? 's' : ''}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Summary & Submit */}
|
|
{selectedTimeSlot && customerName && customerEmail && (
|
|
<div className="bg-white rounded-xl p-6 shadow-sm">
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-4">Booking Summary</h2>
|
|
|
|
<div className="space-y-3 mb-6">
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Experience:</span>
|
|
<span className="font-medium">{selectedBookingTypeData?.display_name}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Date:</span>
|
|
<span className="font-medium">{selectedDate}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Time:</span>
|
|
<span className="font-medium">
|
|
{timeSlots.find(s => s.id === selectedTimeSlot)?.start_time} -
|
|
{timeSlots.find(s => s.id === selectedTimeSlot)?.end_time}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Participants:</span>
|
|
<span className="font-medium">{participantsCount}</span>
|
|
</div>
|
|
{selectedBookingTypeData?.requires_payment && (
|
|
<div className="flex justify-between text-lg font-semibold border-t pt-3">
|
|
<span>Total:</span>
|
|
<span>${selectedBookingTypeData.price_per_person * participantsCount}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<button
|
|
onClick={handleSubmit}
|
|
className="w-full bg-blue-600 text-white py-4 px-6 rounded-lg font-semibold hover:bg-blue-700 transition-colors"
|
|
>
|
|
{selectedBookingTypeData?.requires_payment ? 'Continue to Payment' : 'Confirm Booking'}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default BookingInterface; |