Replace hardcoded time slots with dynamic API availability
Both BookingFlow and RescheduleFlow in Appointments.tsx now fetch from /api/book/availability when a date is selected, matching the public booking wizard behavior. Loading and error states shown. - Removed hardcoded availableTimes arrays from both flows - Added useEffect that fetches availability on date change - Shows "Checking availability…" while loading - Shows error message on fetch failure - Shows "No available slots" when API returns empty Added tests for RescheduleFlow dynamic slot fetching covering: loading, fetched slots, error, empty, API params, and re-fetch on date change. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -573,16 +573,26 @@ export function RescheduleFlow({
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [slotsLoading, setSlotsLoading] = useState(false);
|
||||
const [slotsError, setSlotsError] = useState<string | null>(null);
|
||||
const [availableTimes, setAvailableTimes] = useState<string[]>([]);
|
||||
|
||||
const availableTimes = [
|
||||
'9:00 AM',
|
||||
'10:00 AM',
|
||||
'11:00 AM',
|
||||
'1:00 PM',
|
||||
'2:00 PM',
|
||||
'3:00 PM',
|
||||
'4:00 PM',
|
||||
];
|
||||
useEffect(() => {
|
||||
if (!selectedDate || !sessionId) {
|
||||
setAvailableTimes([]);
|
||||
return;
|
||||
}
|
||||
const params = new URLSearchParams({ date: selectedDate });
|
||||
setSlotsLoading(true);
|
||||
setSlotsError(null);
|
||||
fetch(`/api/book/availability?${params.toString()}`, {
|
||||
headers: { "X-Impersonation-Session-Id": sessionId ?? "" },
|
||||
})
|
||||
.then((r) => r.json() as Promise<string[]>)
|
||||
.then(setAvailableTimes)
|
||||
.catch(() => setSlotsError('Failed to load time slots'))
|
||||
.finally(() => setSlotsLoading(false));
|
||||
}, [selectedDate, sessionId]);
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!selectedDate || !selectedTime) return;
|
||||
@@ -661,7 +671,12 @@ export function RescheduleFlow({
|
||||
/>
|
||||
{selectedDate && (
|
||||
<div className="grid grid-cols-3 gap-2 mb-4">
|
||||
{availableTimes.map((time) => (
|
||||
{slotsLoading && <p className="col-span-3 text-sm text-stone-500 py-2">Checking availability…</p>}
|
||||
{!slotsLoading && slotsError && <p className="col-span-3 text-sm text-red-500 py-2">{slotsError}</p>}
|
||||
{!slotsLoading && availableTimes.length === 0 && !slotsError && (
|
||||
<p className="col-span-3 text-sm text-stone-500 py-2">No available slots on this date.</p>
|
||||
)}
|
||||
{!slotsLoading && availableTimes.map((time) => (
|
||||
<button
|
||||
key={time}
|
||||
onClick={() => setSelectedTime(time)}
|
||||
@@ -723,16 +738,26 @@ function BookingFlow({ onClose, sessionId }: BookingFlowProps) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [slotsLoading, setSlotsLoading] = useState(false);
|
||||
const [slotsError, setSlotsError] = useState<string | null>(null);
|
||||
const [availableTimes, setAvailableTimes] = useState<string[]>([]);
|
||||
|
||||
const availableTimes = [
|
||||
'9:00 AM',
|
||||
'10:00 AM',
|
||||
'11:00 AM',
|
||||
'1:00 PM',
|
||||
'2:00 PM',
|
||||
'3:00 PM',
|
||||
'4:00 PM',
|
||||
];
|
||||
useEffect(() => {
|
||||
if (!selectedDate || !sessionId) {
|
||||
setAvailableTimes([]);
|
||||
return;
|
||||
}
|
||||
const params = new URLSearchParams({ date: selectedDate });
|
||||
setSlotsLoading(true);
|
||||
setSlotsError(null);
|
||||
fetch(`/api/book/availability?${params.toString()}`, {
|
||||
headers: { "X-Impersonation-Session-Id": sessionId ?? "" },
|
||||
})
|
||||
.then((r) => r.json() as Promise<string[]>)
|
||||
.then(setAvailableTimes)
|
||||
.catch(() => setSlotsError('Failed to load time slots'))
|
||||
.finally(() => setSlotsLoading(false));
|
||||
}, [selectedDate, sessionId]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@@ -1055,7 +1080,12 @@ function BookingFlow({ onClose, sessionId }: BookingFlowProps) {
|
||||
/>
|
||||
{selectedDate && (
|
||||
<div className="grid grid-cols-3 gap-2 mb-4">
|
||||
{availableTimes.map((time) => (
|
||||
{slotsLoading && <p className="col-span-3 text-sm text-stone-500 py-2">Checking availability…</p>}
|
||||
{!slotsLoading && slotsError && <p className="col-span-3 text-sm text-red-500 py-2">{slotsError}</p>}
|
||||
{!slotsLoading && availableTimes.length === 0 && !slotsError && (
|
||||
<p className="col-span-3 text-sm text-stone-500 py-2">No available slots on this date.</p>
|
||||
)}
|
||||
{!slotsLoading && availableTimes.map((time) => (
|
||||
<button
|
||||
key={time}
|
||||
onClick={() => setSelectedTime(time)}
|
||||
|
||||
Reference in New Issue
Block a user