Clickable past dates send doomed POST requests to the booking API, wasting backend cycles, polluting logs with 4xx errors, and surfacing generic server-side failures that erode user trust. Customers who manage to submit a past date hit a confusing rejection screen mid-flow, abandon the booking, and never return. This is a preventable user-experience regression that also inflates telemetry noise and masks real availability failures in your error-rate dashboards.
Low because server-side validation still blocks the booking, so the impact is UX friction and log pollution rather than data corruption or revenue loss.
In your calendar slot component, compute isPast from new Date(slot.time) < new Date() once per render, then apply both a disabled attribute and a visually distinct class (opacity-50 cursor-not-allowed) when true. Short-circuit the click handler before any fetch call fires. See src/components/CalendarSlot.tsx:
const isPast = new Date(slot.time) < new Date()
if (isPast) { toast.error('This date has passed'); return }
ID: booking-calendar-availability.calendar-display.past-date-protection
Severity: low
What to look for: Count all date comparison checks in the calendar component. Look for client-side logic that compares slot dates to new Date() or Date.now(). Verify both visual and interaction protection:
opacity-50, cursor-not-allowed, or different color).disabled attribute), show feedback ("This date has passed"), or prevent the booking form from opening.fetch/axios call fires.new Date(slot.time) < new Date(), isBefore(slotDate, now), or equivalent. Examine files matching src/components/*Slot*, src/components/*Calendar*, src/components/*calendar*.Pass criteria: At least 1 date comparison guard must exist in the calendar component preventing past-date selection. Past dates must have at least 2 visual distinctions (e.g., opacity-50 + cursor-not-allowed, or gray color + disabled attribute). No API call may fire when a past date is clicked. Report: "Past-date guard found: [yes/no]. Visual distinctions: [list]."
Fail criteria: Past dates look the same as available dates, or clicking them makes an API request, or no client-side date comparison exists.
Skip (N/A) when: Calendar does not display past dates at all, or does not allow date selection.
Detail on fail: Example: "Past dates are not visually different. Clicking yesterday's date opens the booking form and sends an API request, which fails server-side with a generic error."
Cross-reference: Check visual-distinction — past is one of the 4 required distinct slot states.
Cross-reference: Check unavailable-feedback — past date clicks should produce feedback, not silent no-ops.
Cross-reference: Check same-day-cutoff — past-date protection is client-side; cutoff must also be validated server-side.
Remediation: Disable and style past dates client-side:
export function CalendarSlot({ slot, onSelect }) {
const now = new Date()
const slotDate = new Date(slot.time)
const isPast = slotDate < now
const handleClick = () => {
if (isPast) {
toast.error('This date has passed')
return
}
onSelect(slot)
}
return (
<button
onClick={handleClick}
disabled={isPast}
className={`
p-2 rounded border
${isPast
? 'bg-gray-100 text-gray-400 cursor-not-allowed opacity-50'
: 'bg-green-100 text-green-900 cursor-pointer hover:bg-green-200'
}
`}
aria-label={isPast ? 'Past date, unavailable' : `Available: ${slot.time}`}
>
{isPast && '◄'} {format(slotDate, 'MMM d')}
</button>
)
}