Disable dates with no availability on booking page

- Fetch availability for all dates on page load
- Track which dates have available slots in state
- Disable date buttons that have no availability
- Add visual styling for disabled dates (reduced opacity, not-allowed cursor)
- Prevent clicking on dates with no availability
- Improves UX by showing which dates are bookable at a glance
This commit is contained in:
counterweight 2025-12-21 18:13:12 +01:00
parent 7926e3ae4c
commit 63f40433cc
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C

View file

@ -92,6 +92,12 @@ const pageStyles: Record<string, React.CSSProperties> = {
background: "rgba(167, 139, 250, 0.15)",
border: "1px solid #a78bfa",
},
dateButtonDisabled: {
opacity: 0.4,
cursor: "not-allowed",
background: "rgba(255, 255, 255, 0.01)",
border: "1px solid rgba(255, 255, 255, 0.04)",
},
dateWeekday: {
color: "#fff",
fontWeight: 500,
@ -224,6 +230,8 @@ export default function BookingPage() {
const [isBooking, setIsBooking] = useState(false);
const [error, setError] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [datesWithAvailability, setDatesWithAvailability] = useState<Set<string>>(new Set());
const [isLoadingAvailability, setIsLoadingAvailability] = useState(true);
const dates = getDateRange(minAdvanceDays, maxAdvanceDays);
@ -245,6 +253,36 @@ export default function BookingPage() {
}
}, []);
// Fetch availability for all dates on mount
useEffect(() => {
if (!user || !isAuthorized) return;
const fetchAllAvailability = async () => {
setIsLoadingAvailability(true);
const availabilitySet = new Set<string>();
// Fetch availability for all dates in parallel
const promises = dates.map(async (date) => {
try {
const dateStr = formatDate(date);
const data = await api.get<AvailableSlotsResponse>(`/api/booking/slots?date=${dateStr}`);
if (data.slots.length > 0) {
availabilitySet.add(dateStr);
}
} catch (err) {
// Silently fail for individual dates - they'll just be marked as unavailable
console.error(`Failed to fetch availability for ${formatDate(date)}:`, err);
}
});
await Promise.all(promises);
setDatesWithAvailability(availabilitySet);
setIsLoadingAvailability(false);
};
fetchAllAvailability();
}, [user, isAuthorized, dates]);
useEffect(() => {
if (selectedDate && user && isAuthorized) {
fetchSlots(selectedDate);
@ -252,8 +290,12 @@ export default function BookingPage() {
}, [selectedDate, user, isAuthorized, fetchSlots]);
const handleDateSelect = (date: Date) => {
setSelectedDate(date);
setSuccessMessage(null);
const dateStr = formatDate(date);
// Only allow selection if date has availability
if (datesWithAvailability.has(dateStr)) {
setSelectedDate(date);
setSuccessMessage(null);
}
};
const handleSlotSelect = (slot: BookableSlot) => {
@ -329,14 +371,20 @@ export default function BookingPage() {
<h2 style={styles.sectionTitle}>Select a Date</h2>
<div style={styles.dateGrid}>
{dates.map((date) => {
const isSelected = selectedDate && formatDate(selectedDate) === formatDate(date);
const dateStr = formatDate(date);
const isSelected = selectedDate && formatDate(selectedDate) === dateStr;
const hasAvailability = datesWithAvailability.has(dateStr);
const isDisabled = !hasAvailability || isLoadingAvailability;
return (
<button
key={formatDate(date)}
key={dateStr}
onClick={() => handleDateSelect(date)}
disabled={isDisabled}
style={{
...styles.dateButton,
...(isSelected ? styles.dateButtonSelected : {}),
...(isDisabled ? styles.dateButtonDisabled : {}),
}}
>
<div style={styles.dateWeekday}>