-
Notifications
You must be signed in to change notification settings - Fork 0
Frontend
The frontend is a React 19 single-page application in frontend/, built with Vite and TypeScript. It renders all UI; the backend only supplies JSON.
| Concern | Library |
|---|---|
| Framework | React 19 (with the React Compiler babel plugin) |
| Build | Vite 8, TypeScript 6 |
| Routing | react-router-dom v7 |
| Styling | Tailwind CSS 4 |
| Components | shadcn components on Base UI (@base-ui/react, not Radix; props differ) |
| Icons | Phosphor Icons |
| Data grid | TanStack Table (admin dashboard) |
| Animations | GSAP + @gsap/react (landing page scroll animations) |
| Maps | Leaflet + react-leaflet + leaflet.markercluster |
| Fonts | Bricolage Grotesque (display), Instrument Sans (body), self-hosted woff2 |
BrowserRouter runs with a basename derived from import.meta.env.BASE_URL (/public in production builds, empty in dev).
| Route | Page | Guard |
|---|---|---|
/ |
home/HomePage (standalone marketing landing, own layout) |
public |
/meets |
meets/MeetsPage |
public |
/meets/:id |
meets/MeetDetailPage |
public |
/meets/new |
meets/MeetCreatePage |
RequireAuth |
/meets/:id/edit |
meets/MeetEditPage |
RequireAuth |
/groups |
groups/GroupsPage |
public |
/groups/:id |
groups/GroupDetailPage |
public |
/chat |
ChatPage (3s polling) |
RequireAuth |
/weather |
WeatherPage |
public |
/pilots/:username |
pilots/PilotProfilePage |
public |
/login, /register
|
LoginPage, RegisterPage
|
public |
/profile |
ProfilePage |
RequireAuth |
/admin/dashboard |
AdminDashboardPage |
RequireAdmin |
* |
redirect to /
|
Every route except the landing page renders inside AppChrome: the floating NavIsland navigation pill, a centered main column, and the SiteFooter (hills illustration). The ChatLauncher floating bubble mounts globally and hides itself on /chat, /login, /register, and when logged out.
All server communication goes through the api<T>(path, options) helper. It:
- Prefixes paths with
BASE_URL(so/api/...becomes/public/api/...in production; without the prefix the root.htaccessredirect would turn a POST into a GET). - Sends
credentials: 'same-origin'so the Shield session cookie travels along. - Attaches the CSRF token (kept in module memory) as a header on every non-GET request.
- Captures a fresh CSRF token from any response that carries a
csrffield (login regenerates the session). - Throws
ApiErroron non-2xx, exposingstatusand parsedfieldErrorsfrom422responses so forms can show per-field messages.
Never call fetch directly; always go through this wrapper.
AuthProvider wraps the app and exposes useAuth():
- On mount it calls
/api/auth/meto restore the session (and pick up the CSRF token);loadingis true until that resolves. -
login(login, password, remember)andlogout()update the context. Logout re-fetches/meto get a fresh CSRF token for the new anonymous session. -
isAdminis derived fromuser.permissions['admin.access'].
RequireAuth and RequireAdmin (in src/components/) redirect unauthenticated/unauthorized visitors; they are UX only, the backend filters remain authoritative.
pages/
├── home/ # GSAP landing: Hero, Story, Features, Meets, Gallery,
│ # LiveFeed (activity), Testimonials, Steps, Cta, ...
├── meets/ # MeetsPage (cards + search/filters), MeetDetailPage,
│ # MeetCreatePage / MeetEditPage (shared MeetForm with
│ # LocationPickerMap), MeetWeatherPanel
├── groups/ # GroupsPage, GroupDetailPage, group-image mapping
├── pilots/ # PilotProfilePage (public profile)
├── weather/ # ForecastStrip, ReportDetail, WindArrow, WMO code map,
│ # wind/format helpers (used by WeatherPage)
├── ChatPage.tsx # full-page chat, 3s polling with the `after` cursor
├── LoginPage / RegisterPage / ProfilePage / AdminDashboardPage / WeatherPage
components/
├── chrome/ # NavIsland (floating nav pill), SiteFooter
├── chat/ # ChatPanel (shared message list + composer), ChatLauncher
├── map/ # BaseMap, LocationPickerMap, MarkerClusterLayer, markers
├── ui/ # shadcn-on-Base-UI primitives (button, dialog, select, ...)
├── users/ # UserFormDialog (admin create/edit)
└── RequireAuth / RequireAdmin / UserMenu
lib/
├── api.ts # fetch wrapper (CSRF, base path, ApiError)
├── auth.tsx # AuthProvider / useAuth
├── types.ts # TypeScript mirrors of the API shapes
└── utils.ts # cn() etc.
- Search and filters combine (AND) and update the list immediately, client-side.
- Join/leave/create/send show instant feedback; mutating responses return the fresh resource, which replaces local state (no page reloads).
- User-facing success/error messages appear for every state-changing action.
-
baseis/public/for builds,/for the dev server. -
build.outDiris../publicwithemptyOutDir: false(so CodeIgniter'sindex.phpsurvives). - Dev server proxies
/apiand/mediatoCI_BACKEND_URL(defaulthttp://localhost:8080). -
@aliasessrc/.
-
Base UI, not Radix: the shadcn components in
src/components/ui/sit on Base UI; prop names and composition differ from Radix-based shadcn. The Base UISelectneeds anitemsprop. - Dialog exit animations: Base UI dialogs wait for the exit animation before unmounting. In a backgrounded/headless browser tab animations are paused, so dialogs can look "stuck". Not a bug in foreground browsers.
-
pnpm buildis required before a frontend change shows up in the production (public/) app.
FlightMeet
Code
Reference