diff --git a/src/components/bookings/CancelBookingModal.tsx b/src/components/bookings/CancelBookingModal.tsx new file mode 100644 index 0000000..b25fbd6 --- /dev/null +++ b/src/components/bookings/CancelBookingModal.tsx @@ -0,0 +1,144 @@ +/** + * Cancel Booking Modal Component + * + * Confirmation modal for cancelling bookings with optional reason. + * Displays grace period policy information. + */ + +'use client'; + +import { useState } from 'react'; +import { X, AlertTriangle, Loader2 } from 'lucide-react'; +import type { BookingDetail } from '@/src/types/bookings'; + +interface CancelBookingModalProps { + booking: BookingDetail; + onConfirm: (reason?: string) => void; + onCancel: () => void; + loading?: boolean; +} + +export default function CancelBookingModal({ + booking, + onConfirm, + onCancel, + loading = false, +}: CancelBookingModalProps) { + const [reason, setReason] = useState(''); + + function handleConfirm() { + onConfirm(reason.trim() || undefined); + } + + // Extract grace period from policies if available + const graceMinutes = booking.policies?.cancel_grace_minutes || 15; + const graceAnchor = booking.policies?.cancel_grace_anchor || 'start'; + + return ( +
+ This action cannot be undone +
++ Cancelling this booking will free up the slot and notify all attendees. +
+Policy
++ Changes allowed until {graceMinutes} minutes after slot{' '} + {graceAnchor === 'end' ? 'ends' : 'starts'} +
+Booking Details
++ Court: {booking.slot.court.name} +
++ Time:{' '} + {new Date(booking.slot.starts_at).toLocaleString('en-US', { + weekday: 'short', + month: 'short', + day: 'numeric', + hour: 'numeric', + minute: '2-digit', + })} +
++ Attendees: {booking.attendees.length} +
++ Select a slot below. We'll validate it in + real-time and show you if it's available for this booking. +
++ Move window: 14 days · Product type must match · Club must match +
+No available slots found
++ Try adjusting your filters or check back later +
+