Time and Date Selection for Booking
Time and date selection is two-step UI: first date in calendar, then available time slots. Key requirements: display only actually free time, real-time updates during concurrent bookings, correct timezone handling.
Time Slots
function TimeSlotPicker({ resourceId, date, onSelect }: Props) {
const { data: slots, isLoading } = useQuery({
queryKey: ['slots', resourceId, format(date, 'yyyy-MM-dd')],
queryFn: () => fetchTimeSlots(resourceId, date),
});
if (isLoading) return <SlotsSkeleton />;
const grouped = groupBy(slots, slot =>
slot.datetime.getHours() < 13 ? 'morning' : 'afternoon'
);
return (
<div className="space-y-4">
{grouped.morning && (
<div>
<p className="text-sm text-gray-500 mb-2">Morning</p>
<div className="grid grid-cols-4 gap-2">
{grouped.morning.map(slot => (
<button
key={slot.id}
disabled={!slot.available}
onClick={() => onSelect(slot)}
className={cn(
'py-2 rounded-lg text-sm border transition',
slot.available
? 'border-blue-200 hover:bg-blue-50 hover:border-blue-500'
: 'border-gray-100 text-gray-300 cursor-not-allowed'
)}
>
{format(slot.datetime, 'HH:mm')}
</button>
))}
</div>
</div>
)}
</div>
);
}
Timezones
Server stores time in UTC. Client displays in user's local time:
// Convert server UTC to user's local time
const localSlot = utcToZonedTime(slot.datetime_utc, userTimezone);
const displayTime = format(localSlot, 'HH:mm', { timeZone: userTimezone });
For multi-regional services — explicit timezone selection by client.
Double-Booking Protection
Pessimistic locking: slot temporarily reserved for 10 minutes during booking attempt, released on cancel or process completion.
Implementation time: 2–3 working days.







