"use client"; import React from "react"; import { useEffect, useState, useCallback } from "react"; import { Permission } from "../auth-context"; import { api } from "../api"; import { Header } from "../components/Header"; import { useRequireAuth } from "../hooks/useRequireAuth"; import { components } from "../generated/api"; import { formatDateTime } from "../utils/date"; import { getStatusDisplay } from "../utils/appointment"; import { layoutStyles, typographyStyles, bannerStyles, badgeStyles, buttonStyles, } from "../styles/shared"; type AppointmentResponse = components["schemas"]["AppointmentResponse"]; export default function AppointmentsPage() { const { user, isLoading, isAuthorized } = useRequireAuth({ requiredPermission: Permission.VIEW_OWN_APPOINTMENTS, fallbackRedirect: "/", }); const [appointments, setAppointments] = useState([]); const [isLoadingAppointments, setIsLoadingAppointments] = useState(true); const [cancellingId, setCancellingId] = useState(null); const [confirmCancelId, setConfirmCancelId] = useState(null); const [error, setError] = useState(null); const fetchAppointments = useCallback(async () => { try { const data = await api.get("/api/appointments"); setAppointments(data); } catch (err) { console.error("Failed to fetch appointments:", err); setError("Failed to load appointments"); } finally { setIsLoadingAppointments(false); } }, []); useEffect(() => { if (user && isAuthorized) { fetchAppointments(); } }, [user, isAuthorized, fetchAppointments]); const handleCancel = async (appointmentId: number) => { setCancellingId(appointmentId); setError(null); try { await api.post(`/api/appointments/${appointmentId}/cancel`, {}); await fetchAppointments(); setConfirmCancelId(null); } catch (err) { setError(err instanceof Error ? err.message : "Failed to cancel appointment"); } finally { setCancellingId(null); } }; if (isLoading) { return (
Loading...
); } if (!isAuthorized) { return null; } const upcomingAppointments = appointments.filter( (apt) => apt.status === "booked" && new Date(apt.slot_start) > new Date() ); const pastOrCancelledAppointments = appointments.filter( (apt) => apt.status !== "booked" || new Date(apt.slot_start) <= new Date() ); return (

My Appointments

View and manage your booked appointments

{error &&
{error}
} {isLoadingAppointments ? (
Loading appointments...
) : appointments.length === 0 ? (

You don't have any appointments yet.

Book an appointment
) : ( <> {/* Upcoming Appointments */} {upcomingAppointments.length > 0 && (

Upcoming ({upcomingAppointments.length})

{upcomingAppointments.map((apt) => { const status = getStatusDisplay(apt.status, true); return (
{formatDateTime(apt.slot_start)}
{apt.note &&
{apt.note}
} {status.text}
{apt.status === "booked" && (
{confirmCancelId === apt.id ? ( <> ) : ( )}
)}
); })}
)} {/* Past/Cancelled Appointments */} {pastOrCancelledAppointments.length > 0 && (

Past & Cancelled ({pastOrCancelledAppointments.length})

{pastOrCancelledAppointments.map((apt) => { const status = getStatusDisplay(apt.status, true); return (
{formatDateTime(apt.slot_start)}
{apt.note &&
{apt.note}
} {status.text}
); })}
)} )}
); } // Page-specific styles const styles: Record = { content: { flex: 1, padding: "2rem", maxWidth: "800px", margin: "0 auto", width: "100%", }, section: { marginBottom: "2rem", }, sectionTitle: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "1.1rem", fontWeight: 500, color: "#fff", marginBottom: "1rem", }, appointmentList: { display: "flex", flexDirection: "column", gap: "0.75rem", }, appointmentCard: { background: "rgba(255, 255, 255, 0.03)", border: "1px solid rgba(255, 255, 255, 0.08)", borderRadius: "12px", padding: "1.25rem", transition: "all 0.2s", }, appointmentCardPast: { opacity: 0.6, background: "rgba(255, 255, 255, 0.01)", }, appointmentHeader: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: "1rem", }, appointmentTime: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "1rem", fontWeight: 500, color: "#fff", marginBottom: "0.25rem", }, appointmentNote: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.875rem", color: "rgba(255, 255, 255, 0.5)", marginBottom: "0.5rem", }, buttonGroup: { display: "flex", gap: "0.5rem", }, confirmButton: { fontFamily: "'DM Sans', system-ui, sans-serif", padding: "0.35rem 0.75rem", fontSize: "0.75rem", background: "rgba(239, 68, 68, 0.2)", border: "1px solid rgba(239, 68, 68, 0.3)", borderRadius: "6px", color: "#f87171", cursor: "pointer", transition: "all 0.2s", }, emptyState: { fontFamily: "'DM Sans', system-ui, sans-serif", color: "rgba(255, 255, 255, 0.4)", textAlign: "center", padding: "3rem", }, emptyStateLink: { color: "#a78bfa", textDecoration: "none", }, };