import { useState } from "react"; import { Calendar } from "@/components/ui/calendar"; import { Class, insertBookingSchema } from "@/lib/schema"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { useMutation } from "@tanstack/react-query"; import { useToast } from "@/hooks/use-toast"; import { Button } from "@/components/ui/button"; import { format, addDays, isAfter, isBefore, addMinutes, startOfDay } from "date-fns"; import { Loader2 } from "lucide-react"; interface BookingCalendarProps { selectedClass: Class; } export function BookingCalendar({ selectedClass }: BookingCalendarProps) { const { toast } = useToast(); const [selectedDate, setSelectedDate] = useState(addDays(new Date(), 1)); const [selectedTime, setSelectedTime] = useState(null); // Generate time slots for the selected date const timeSlots = generateTimeSlots(selectedDate); const bookMutation = useMutation({ mutationFn: async (bookingData: { classId: number, date: Date }) => { const res = await apiRequest("POST", "/api/bookings", bookingData); return await res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/bookings"] }); toast({ title: "Booking successful", description: "Your class has been booked successfully!", }); // Reset selected time setSelectedTime(null); }, onError: (error: Error) => { toast({ title: "Booking failed", description: error.message, variant: "destructive", }); }, }); const handleConfirmBooking = () => { if (!selectedDate || !selectedTime) { toast({ title: "Booking error", description: "Please select both a date and time", variant: "destructive", }); return; } // Combine date and time const [hours, minutes] = selectedTime.split(":").map(Number); const bookingDateTime = new Date(selectedDate); bookingDateTime.setHours(hours, minutes, 0, 0); bookMutation.mutate({ classId: selectedClass.id, date: bookingDateTime }); }; const formatPrice = (price: number) => { return `$${(price / 100).toFixed(2)}`; }; return (

Book Your Next Class

isBefore(date, startOfDay(new Date())) || isAfter(date, addDays(new Date(), 60))} />
{selectedDate && (
Available Times for {format(selectedDate, "MMMM d, yyyy")}
{timeSlots.map((time, index) => (
setSelectedTime(time)} > {formatTimeDisplay(time)}
))}
)}
Booking Summary
Class: {selectedClass.name}
Date: {selectedDate ? format(selectedDate, "MMMM d, yyyy") : "Select a date"}
Time: {selectedTime ? formatTimeDisplay(selectedTime) : "Select a time"}
Duration: {selectedClass.duration} minutes
Price: {formatPrice(selectedClass.price)}
Total: {formatPrice(selectedClass.price)}

You'll receive a confirmation email with details and calendar invite.

); } // Helper functions function generateTimeSlots(date?: Date): string[] { if (!date) return []; const slots = []; const startTime = 9; // 9 AM const endTime = 20; // 8 PM for (let hour = startTime; hour < endTime; hour++) { slots.push(`${hour}:00`); slots.push(`${hour}:30`); } // Add a random selection of unavailable slots const availableSlots = [...slots]; const numberOfUnavailableSlots = Math.floor(Math.random() * 4) + 2; // 2-5 unavailable slots for (let i = 0; i < numberOfUnavailableSlots; i++) { const randomIndex = Math.floor(Math.random() * availableSlots.length); availableSlots.splice(randomIndex, 1); } return availableSlots; } function formatTimeDisplay(time: string): string { const [hours, minutes] = time.split(":").map(Number); const period = hours < 12 ? "AM" : "PM"; const displayHours = hours % 12 || 12; return `${displayHours}:${minutes.toString().padStart(2, "0")} ${period}`; }