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 ( +
+
+ {/* Header */} +
+
+
+ +
+

Cancel Booking

+
+ +
+ + {/* Warning */} +
+

+ This action cannot be undone +

+

+ Cancelling this booking will free up the slot and notify all attendees. +

+
+ + {/* Grace Period Info */} +
+

Policy

+

+ Changes allowed until {graceMinutes} minutes after slot{' '} + {graceAnchor === 'end' ? 'ends' : 'starts'} +

+
+ + {/* Booking Details */} +
+

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} +

+
+
+ + {/* Reason (Optional) */} +
+ +