Frontend
The /frontend package is the Athlora React single-page application. It is a separate deployment from the Express API and communicates only through authenticated HTTP/JSON requests. The shipped live-logging UI covers the legacy 100m timeline slice plus catalogue-backed multi-discipline sessions (timed, measured, vertical, relay).
Requirements
- Node.js 22 LTS recommended (Node.js 20 or later supported)
- npm
- A running backend and Auth0 SPA application for authenticated features
Run locally
cd frontend
cp .env.example .env.local
npm install
npm run dev
Vite serves the app at http://localhost:5173. Start the backend separately on http://localhost:4000 before signing in.
Environment
.env.local is ignored by Git. Set these Vite variables:
VITE_API_BASE_URL=http://localhost:4000
VITE_AUTH0_DOMAIN=your-tenant.eu.auth0.com
VITE_AUTH0_CLIENT_ID=your-spa-client-id
VITE_AUTH0_AUDIENCE=https://api.example.com
VITE_REALTIME_URL=http://localhost:4000
VITE_REALTIME_URL is optional: it points at the API's Socket.IO endpoint, and without it the app stays fully functional using HTTP refresh (src/features/realtime/useRealtimeRoom.ts).
VITE_* values are embedded in the browser build. They must contain only public configuration, never Management API credentials or database URLs. In Auth0, register http://localhost:5173 as an allowed callback URL, logout URL, and web origin.
The Playwright E2E suite boots a separate Vite dev server on http://localhost:5174 (strict port) with VITE_API_BASE_URL=http://localhost:4100 and the same VITE_AUTH0_* variables — see getting-started/scripts.md.
Scripts
| Command | Purpose |
|---|---|
npm run dev | Start the Vite development server with HMR. |
npm run build | Type-check and create dist/. |
npm run preview | Serve the production build locally. |
npm run typecheck | Run strict TypeScript project checks. |
npm run test | Run Vitest and React Testing Library once. |
npm run lint | Run ESLint. |
Application structure
src/featurescontains feature-owned UI across 16 areas: assistant, athletes, auth, comparison, dashboard, events, fitness, fixtures, landing, publicLogger, publicSchedule, publicStats, realtime, reports, results, and timeline (live logging lives infeatures/timeline).src/componentscontains reusable accessible controls and async states.src/apicontains the typed fetch client and one module per API resource. It preserves the API error code, message, status, and validation details.src/typesmirrors the API's camel-case DTOs. The contract is catalogue-driven: units, precision, direction, and labels come fromdiscipline_definitions, so timed, measured, vertical, and relay entry shapes already share one DTO surface alongside the legacy 100m timeline types.src/styles/tokens.csscontains shared visual tokens. Component and feature styling uses CSS modules.
Implemented features
-
Authenticated coach console with roster, events, live logging, comparison, and account surfaces.
-
Club branding settings on the Account page: description, a single accessible primary color with a live WCAG contrast check against white/ink, and logo/cover upload (PNG/JPEG/WebP ≤5 MB). Branding is applied through the shared
ClubBadgecomponent on the console footer/switcher, fixture team lists, public stats cards, and comparison tables, with an initials fallback when no logo is set. -
Auth0 Universal Login, application-user synchronization, account password links, sign-out, and permanent account deletion.
-
API-backed dashboard with a fixed layout: a summary mode (the signature summary hero, an onboarding prompt when the roster is empty, the season selector and stat row, an upcoming-events panel, and a scrollable, evenly spaced roster snapshot) and a live mode (live-event hero and latest-entries feed) while an event is
in_progress, each with loading and recovery states. The layout is not user-configurable — the Customize dashboard dialog, card reordering and hidden-card controls, and saved views were removed in the dashboard simplification; the backendGET|PUT /api/v1/preferencesAPI still exists, but the dashboard UI no longer reads or writes it. -
Athlete roster management, archival/restoration, editable athlete profiles, current 100m performance statistics, PBs, and SBs. Lightweight SVG injury summaries show active count, highest severity, mapped body regions, and accessible text without loading the Three.js Fitness viewer.
-
Event creation, explicit OpenStreetMap venue search/pin-coordinate selection with manual fallback, lifecycle changes, participant RSVP management, results, manual corrections, and event-day GraySky forecasts.
-
Mobile-first live logging: the legacy timeline console records 100m finishes and incidents, while
SessionLivePanelhandles timed, measured, vertical, relay, guest, and incident observations for catalogue sessions — all with version-aware corrections, undo, derived standings, and lifecycle guards. Relay teams log one input per athlete under a visibleRelay splits - athlete namelabel (thearia-labelreadsRelay splits for athlete name (leg N)), each leg has its own entry list withMake official/Undo, and standings show Club, Relay team, Athletes, and per-leg values — the summed team total and place appear only once the session result state isfinal. -
Multi-discipline meet detail:
MeetRosterPanelpresents catalogue sessions as keyboard-accessible roster tabs with event-level RSVP controls and athlete cards. Coaches add active athletes through a compact picker; relay-team creation and editing appear only in a selected relay session. Direct guest-entry and session-creation controls are not displayed.SessionLivePanelprovides session-scoped logging with offline enqueue, attempt history, coach official-attempt selection for timed and measured disciplines, per-athlete relay split logging and official leg selection, full-result officialization for vertical sessions, read-time standings (relay rows includerelayLegsvalues and a final-only team total), and CSV export (relay columns: Place, Club, Relay team, Athletes, Legs, Result, Status); it renders only fordiscipline: nullmeets. Public Stats club mode includes published session/team standings viaPublicSessionResultswith safe member summaries. -
Roster performance: adding selected athletes to a host-owned session uses one bulk mutation and applies returned participants, entrants, and registrations to the visible tab; guest additions run their independent athlete operations concurrently and apply the returned state locally. Switching discipline tabs retains the event-wide catalogue, entrants, athletes, and participants and fetches only that session's registrations. Removing an entrant updates only that registration locally. The immutable discipline catalogue is shared in-flight across forms to avoid duplicate requests.
-
Logger performance: private session tabs retain the event-wide catalogue, sessions, and entrants while loading only the active session's entries, results, and registrations; recording, undo, official selection, and lifecycle changes refresh that active session rather than the full meet. Public meet logging persists one event snapshot asynchronously, then refreshes and polls only the active discipline session. Both logger surfaces use keyed entry/result lookups for large session rosters. Queueing finalization immediately disables write controls; completed sessions are read-only until a coach reopens them.
-
A responsive coach console with an optional weather-effects display (9 canvas particle presets: rain, snow, sparks, storm lightning, etc.), theme preference, live clock, and current local weather readout.
-
A shell-contained athlete Fitness & Injury Map sub-view with progressive body-region selection, live previews, persistent injury records and resolution, and an on-demand React Three Fiber anatomical body viewer.
-
Gemini Live voice assistant with microphone capture (PCM 16kHz), audio playback (PCM 24kHz), read-only analytics/report tools, confirmation-gated athlete-draft preparation, interruption handling, and session lifecycle management.
-
Cross-club fixture system: guest fixture management, team roster assignment, RSVP tracking, finish-time recording, result corrections, team withdrawal, fixture notifications with unread badges, and incoming invitation workflows (accept/decline/request changes).
-
Public logger links: coach-created shareable token links allowing external guests to start sessions, view event snapshots, and record results/incidents without Auth0.
-
Offline-first PWA with two Dexie/IndexedDB databases: the per-user store at
version(2)with five tables (offlineActions,cachedEvents,cachedParticipants,cachedTimeline,cachedSessions) and the public-logger store (publicOfflineActions,publicCachedSnapshots,publicCachedSessions), action queue (enqueue/pending/markSynced/markFailed/reset), batch sync engine, event data caching, offline designation guards, and data cleanup. -
Athlete and club comparison page (pair and multi) with per-discipline tabs, dual progression chart, metric comparison table (PB, latest, average, consistency, improvement), URL-param-driven selection, and two independent coach publication toggles (public results vs public schedule) that call
PUT /clubs/publicationwith both flags as a full replacement. -
Single-athlete all-time 100m progression chart with PB milestones, chart/table toggle, cursor-based pagination, and accessibility features.
-
Real-time Socket.IO event subscriptions for live invalidation notifications, with connection state management, deduplication, and workspace-aware authorization.
-
Club discovery and join-request workflow: search clubs, create clubs, send/withdraw/approve/reject join requests, and manage club membership.
The dashboard and other authenticated views wait for PUT /api/v1/auth/me to finish successfully. If synchronization fails, the UI provides a retry state instead of showing protected data.
Canonical console routes
/console— dashboard/console/stats— season stats (the same summary dashboard view)/console/athletesand/console/athletes/:athleteId— roster and athlete detail/console/comparison— athlete and club comparison (pair and multi, per-discipline tabs)/console/eventsand/console/events/:eventId— event list, cross-club fixture management, and direct-loadable event detail/console/liveand/console/live/:eventId— live logger and selected event/console/account— account management
/console/fixtures is no longer a view: the route redirects to /console/events (frontend/src/App.tsx), which now hosts the fixture surfaces, and the sidebar has no Fixtures entry.
Unauthenticated console visits return to the requested canonical path after Auth0 completes. Event list date, type, and status filters are retained in its query string when opening and returning from detail.
Public routes
Only the logger, stats, and schedule paths are short-circuited in src/main.tsx before the Auth0 provider mounts, so those surfaces work without Auth0 environment variables:
/log/:token— public logger session/stats— public performance index (gated bypublicResultsEnabled)/stats/leaderboard— public leaderboard/stats/standings— public standings/stats/report— detailed public statistics report/schedule— public schedule club index (gated bypublicScheduleEnabled)/schedule/:clubId— published club schedule showing only upcoming meets
Every other path — including / — falls through to the Auth0Provider, so the app will not boot without the VITE_AUTH0_* values; unauthenticated visitors then see the marketing landing page for any path, and / redirects to /console once authenticated. /invitations/:token (frontend/src/App.tsx) accepts a workspace invitation inside the app router after Auth0 loads.
The schedule pages live in src/features/publicSchedule and consume src/api/publicSchedule.ts (requestPublic, no auth headers). They render club identity via ClubBadge, an explicit search of published clubs on the index, a dedicated non-disclosing unavailable state for disabled/unknown clubs (both are the same generic 404), an empty state for clubs with no upcoming meets, accessible <time> date/time rendering, and a responsive single-column layout below 820px. Landing navigation, the public stats header, and the public stats club card link to these routes; no console-only routes or roster data are referenced.
Testing
Frontend tests cover UI behavior, accessibility interactions, API wrappers, authenticated state, dashboard states, athlete and event workflows, live logging, result corrections, and weather handling. Run them with:
npm run lint
npm run typecheck
npm run test
npm run build
For browser-level coverage of the full stack, use the E2E guide.
- The app shell renders with Athlora branding and an ink sidebar. The roster, event-management and dashboard views are live against the typed API.
- Weather presets use paired semantic surface, text, control, status and focus colors across the authenticated console.
nightandnight-rainkeep dashboards, forms, dialogs, badges, result boards, empty states and live-logging controls consistently dark and readable without changing the approved Athlora palette; disabling Weather FX removes the weather theme. - The console shell is a premium dark aurora redesign of
docs/docs/sprints/sprint-1/screenshots/00002803-Athlora_Premium_Dashboard.htmlwith a light "Aurora Mist/Ice" toggle (localStorageathlora-theme,theme-lightclass on<html>). The topbar shows a live weather readout for the coach's device location (geolocation with timezone-city fallback, refreshed every 10 minutes while visible) proxied throughGET /api/v1/weather/current, a weather-effects toggle with an animated scene, and a live clock. Dashboard, Athletes, Events, Live Logger and Account views all consume the same--console-*tokens; the mockup dashboard's fabricated numbers are replaced with real aggregate data. - Auth0 Universal Login is wired through
@auth0/auth0-reactfor sign-up, sign-in, password help and sign-out. After synchronization, the console resolves the user's club workspace and sends it centrally asX-Workspace-Id. The schema permits one current membership per user, so this header is an explicit authorization scope rather than evidence of multi-workspace switching. Account deletion removes the caller's identity and membership, not shared workspace data. - API failures use a typed
ApiErrorthat preserves the backend HTTP status, error code, message and details, includingAUTH_USER_NOT_SYNCHRONIZEDrecovery information. Account synchronization failures display the safe API error code and correlation reference so operators can match a failed sign-in to backend logs without collecting credentials. Single-resource response envelopes are unwrapped by the shared client and empty successful responses are handled without JSON parse failures. src/api/statistics.tsandsrc/api/dashboard.tsexpose the combined athlete statistics/history and dashboard summary resources through DTOs mirrored from the backend. Athlete performance detail consumes the statistics resource directly. The dashboard consumes the stable summary/live aggregate with loading and retry states, active-event progress and latest entries, onboarding, factual roster/event/result/PB panels, and targeted navigation to athlete details, event details and the selected live logger. Summary mode uses the approved signature hero with a live greeting/clock and reduced-motion-aware orbit animation, while every displayed count comes from the aggregate.- Design tokens from the approved mockups are encoded once in
src/styles/tokens.css;index.htmlloads Bebas Neue, Inter, Space Mono, and Space Grotesk from Google Fonts plus Satoshi from Fontshare (Satoshi is not a Google Font), and the tokens set Satoshi/Inter as the base family with Space Grotesk as the mono. The premium console's--console-*namespace (dark aurora default +html.theme-lightoverrides) lives on the shell inCoachConsole.module.css. src/features/athletes/AthletesPage.tsxconsumes the coach-owned UUID athlete DTO throughsrc/api/athletes.ts. It loads active, inactive, and archived athletes, provides immediate name/discipline/status filters plus status counts and badges, renders distinct loading/error-retry/empty/filter-empty states, and persists create, full-replacement edit, lifecycle transition, archive and restore operations. Assigned disciplines are the athlete's effective roster groups, so one athlete can appear under multiple discipline filters.AthleteFormis shared by roster and detail editing; archived athlete profiles are read-only until restored.AthleteDetailPagecombines the editable API profile with owner-scoped 100m statistics in a performance-first layout: a featured athlete identity panel, PB/calendar-year SB/count KPI strip, compact profile details and keyboard-accessible competition/training history tabs. Full-width result rows keep effective times, overrides, incidents, PB/SB and cancelled/non-scoring states explicit in text. Profile and statistics requests load and retry independently, while opening and returning move focus between the detail heading and the exact originating roster action without adding a routing dependency.- Archive confirmation explains that event assignments, timeline entries and results are preserved. Shared modal focus management supports Escape, Tab containment, focus restoration and blocked dismissal during submission; success/error feedback is announced and the responsive card grid collapses for mobile use.
src/features/events/EventsPage.tsxloads coach-owned 100m events throughsrc/api/events.ts, supports list and calendar views with date/type/status filters, and provides distinct loading, retry, empty and filter-empty states.EventDetailPage.tsxis a dedicated direct-loadable route with retry/not-found states; create/edit and lifecycle confirmations remain accessible modal overlays.- Add Event opens directly to its grouped catalogue choices: Track, Field, and Relays. It announces loading/retry state and creates the selected initial sessions, with
100mavailable as a normal session choice. Multi-discipline event detail uses arrow-key navigable session tabs with session-scoped roster cards. Each tab's Athlora-themed athlete picker shows only active athletes whosepreferredDisciplineIdsinclude that session definition; clubs can leave any tab empty. Hosts can invite clubs from the same event detail, and accepted guest clubs receive the tabbed roster while seeing and managing only their own entrants. Preserved 100m participant/RSVP controls render within a single100mtab. - Event detail uses the participant and active-roster APIs to list current assignments, retain inactive/archived historical participants, assign only active athletes with pending RSVP status, replace RSVP status, acknowledge independent lifecycle-review items, and confirm assignment removal. Participant and roster failures retry independently, persistence blocks modal-changing actions, and removal messaging explains that timeline entries and results remain intact.
- Event detail independently loads its GraySky event-day forecast when both coordinates are present. It shows string conditions, nullable Celsius range, rain chance, daily wind in km/h and available timezone. The existing card includes GraySky attribution. Missing coordinates/unsupported dates are guidance states; failures have an isolated retry. Requests abort when detail closes. The console retains device geolocation and uses an offline representative-city lookup for supported timezone fallbacks; unsupported zones remain unavailable. PWA weather reads are network-only, using the backend cache rather than silently displaying stale offline weather.
- Event forms search venues only after the coach activates Search venues; there is no keystroke autocomplete. Selecting a keyboard-accessible result writes the existing location and coordinate fields, which remain editable as the manual fallback/pin adjustment.
VenuePreviewrenders a responsive read-only OpenStreetMap iframe plus required contributor attribution, saved coordinates, and an external map link that remains usable if the iframe cannot load. The client calls onlysrc/api/venues.ts; tests mock that Athlora API boundary rather than public OSM. src/api/timeline.tsexposes typed active-list/create/version-aware PATCH/DELETE requests. Correction payloads requireexpectedVersion, limit edits to valid entry-type fields, and send the DELETE precondition in its JSON body.LiveLoggingPageconsumes fresh event detail, assignments and timeline data for finish/incident logging, correction and accessible confirmed undo. It distinguishes stale versions from event-closed conflicts, reloads before continuing, serializes mutations through standings refresh, exits when the event closes, and keeps core track-side logging open when secondary results/history fail. Decimal inputs and controls reflow without horizontal overflow on narrow phones, while modal completion restores focus to the originating action or timeline heading.src/features/results/EventResultsView.tsxis shared by event detail and Live Logging. It orders competition finishers by effective time while displaying backend placings (including ties), suppresses placing for training, distinguishes DQ/DNF/DNS from an unrecorded result, groups active false-start/lane penalties, and retains archived or removed athlete identity. Event detail loads these outcomes independently with refresh/retry states and permits time corrections only for materialized results on in-progress/completed events.- The athlete Performance view includes an on-demand Fitness & Injury Map sub-view.
src/features/fitnesskeeps the progressive region/area/side/severity draft, persistent injury records, verified topology-bound anatomy map and accessibility-first controls separate from the R3FBodyViewer. The viewer loads only when Fitness opens, presents the supplied static anatomy on a dark medical stage in both console themes, automatically frames it, supports rotate/zoom/reset, and maps saved/preview injuries directly onto the cyan material surface. - Manual correction keeps the timeline-derived outcome/value read-only beside the effective value. Set/update requires a positive hundredth-precision time and a non-blank reason; active audit metadata identifies the synchronized application user and timestamp. Clearing requires confirmation and sends paired nulls. Every successful set/clear re-fetches the complete result board because one override can change other placings and PB/SB flags.
- Tests: Vitest + React Testing Library cover the app shell and weather-theme state, shared Button, API response/error handling, authenticated synchronization, account API/password/deletion/sign-out/social-provider workflows, public sign-up/password help, athlete/event/participant/timeline/result/aggregate/weather wrappers, event forecast success/unavailable/retry/abort states, fixture invitation workspace recovery and guest-workspace selection, roster status filters/transitions, archived detail read-only behavior, event assignment review acknowledgment, live-logger mutation locking/lifecycle conflicts/stale recovery/entry-type payloads/secondary failures/mobile inputs/modal focus, dashboard onboarding/summary/live/error/navigation modes, signature-hero aggregate values/timer lifecycle and exact destination handoffs, stale live-event recovery, result ordering/ties, training, every incident, partial/history states, overrides, PB/SB, profile editing, set/clear/failure correction paths, persistent Fitness injury selection/preview/resolution, public logger session/entry creation, fixture notification and invitation workflows, two-athlete comparison, athlete progression chart, and offline sync queue behavior. Runs with
npm run test.
Deployment
The production SPA is deployed to Vercel:
https://athlora-deploy.vercel.app
Vercel runs npm ci and npm run build from /frontend, then publishes dist/. frontend/vercel.json rewrites direct SPA routes, including email invitation links, to index.html; retain this file when changing Vercel settings. Configure the same five VITE_* values in Vercel and register the production URL in Auth0's callback, logout, and web-origin settings.
AI declaration
This document was created or updated with the assistance of OpenCode[openai/gpt-5.6-terra].