/** * Typed API client for the FamilySync backend. * * credentials: 'include' is required so the OIDC session cookie is sent with * every cross-origin request (Vite dev proxy routes to :3000; production is * same-origin via Pangolin). * * 401 responses mean the session has expired — the browser will follow the * 302 redirect to Authelia on the next API call automatically (full-page nav). */ // ── /api/me ──────────────────────────────────────────────────────────────── export interface MeUser { id: number displayName: string | null color: string } export interface MeResponse { user: MeUser } export async function fetchMe(): Promise { const res = await fetch('/api/me', { credentials: 'include', }) if (!res.ok) { // 302 → browser follows redirect to Authelia automatically. // For 4xx/5xx, throw so React Query can surface the error. throw new Error(`GET /api/me failed: ${res.status}`) } return res.json() as Promise } // ── /api/events (windowed — Phase 2) ───────────────────────────────────── /** * A single concrete occurrence of a calendar event. * * Mirrors the CalendarOccurrence shape produced by the backend's * expandOccurrences() helper (apps/api/src/broker/expand.ts). * * calendarId and ownerUserId are both present: * calendarId — DB calendar-row id (do NOT use for Schedule-X routing) * ownerUserId — DB user id (use for Schedule-X calendarId routing) * isShared — true when this event belongs to the shared-family calendar * * hydrateEvents() uses isShared/ownerUserId — never String(calendarId) — * to build the Schedule-X calendarId that keys into buildCalendarConfig(). */ export interface CalendarOccurrence { id: string // `${uid}::${dtstart_iso}` — stable identity uid: string calendarId: number // DB calendar-row id — do NOT use for SX calendarId routing calendarName: string ownerUserId: number // DB user id — the correct Schedule-X routing key color: string // hex from users.color or shared-family constant isShared: boolean // true → 'shared' slot; false → String(ownerUserId) slot title: string start: string // 'YYYY-MM-DD' for allDay:true; ISO 8601 with IANA tz for timed end: string allDay: boolean location: string | null description: string | null } export interface OccurrencesResponse { occurrences: CalendarOccurrence[] } /** * Fetch windowed calendar occurrences. * * The ?start=&end= window is mandatory — an unwindowed call would expand 500+ * cached events with all their recurring occurrences (RESEARCH.md Pitfall 5). * * @param start ISO date string 'YYYY-MM-DD' — window start (inclusive) * @param end ISO date string 'YYYY-MM-DD' — window end (exclusive) */ export async function fetchEvents( start: string, end: string, ): Promise { const res = await fetch(`/api/events?start=${start}&end=${end}`, { credentials: 'include', }) if (!res.ok) { throw new Error(`GET /api/events failed: ${res.status}`) } return res.json() as Promise } // Phase 1 legacy types (CalendarEvent, EventsResponse, fetchEventsLegacy) removed in Plan 05 // when the Phase 1 broker-proof component was retired.