Skip to content

API Communication

The dashboard talks to the Flask API over HTTPS with the browser's HttpOnly session cookie. There is no token in the bundle, no Supabase data access from the browser, and no server-side rendering.

Request conventions

  • Base URL: VITE_DPS_API_BASE_URL when set (production: https://api.eccdps.org), otherwise /api (the Vite dev proxy rewrites /api → the configured DPS_API_BASE_URL).
  • Credentials: every request sends credentials: "include" — the session cookie rides along.
  • Content: Accept: application/json always; Content-Type: application/json when there is a body.
  • Typed wrappers: pages never call fetch directly. They import from src/lib/api/* — e.g. listReportsPage, getReport, reportAction, addNote, listQueueActions, listPunishments, getCurrentAgent. Each wrapper encodes the endpoint, method, params, and response type (src/lib/api/types.ts).

Authentication state

  • AuthProvider calls getCurrentAgent() (GET /auth/me) on mount and exposes agent, permissions, canHandleSupervisor, admin, can(), canAdmin(), refresh(), and signOut().
  • A 401 during session restore simply leaves the user signed out (redirected to /login by ProtectedRoutes); other failures are logged.
  • signInWithDiscord() is a full-page navigation to /auth/discord/login — the OAuth flow runs server-side and redirects back.
  • signOut() calls POST /auth/logout then clears local state.

Error handling

  • apiFetch (in client.ts) converts non-2xx responses into ApiError (with the server's error message and status); network failures become ApiError with status 0 ("Could not reach the DPS API…").
  • Pages catch ApiError and render ErrorState with the message and a retry action where appropriate.
  • Mutation failures are surfaced via ToastProvider (error toasts), and specific conflict statuses (409s) are usually worth re-fetching the resource because another actor may have changed it.

Loading states

  • Page-level fetches render LoadingState from components/DataState.tsx while in flight.
  • The auth gate renders a "Loading secure session…" screen until /auth/me resolves.
  • Inline mutations disable the triggering control while pending and restore it on failure (per-page pattern).

Mutation patterns

  1. Build the typed payload (e.g. { action: "validate", reason }).
  2. Call the wrapper (e.g. reportAction(id, "validate", reason)).
  3. On success: update local state from the returned payload (most report mutations return the full updated report) and/or re-fetch the resource; show a success toast.
  4. On ApiError: show an error toast / inline error; re-enable controls; re-fetch the resource when the error was a conflict.

The frontend never performs mutations optimistically against authoritative state. Server responses are the source of truth; Realtime (for training) only triggers authoritative re-fetches.

Development proxy

vite.config.ts proxies /api to DPS_API_BASE_URL (optionally adding DPS_API_KEY as the Authorization header for local bot-path testing). See Local setup for the exact commands.