Frontend Architecture
Aero2's frontend is a single-page application (SPA) built with React 19 and Vite, served by the Cloudflare Worker.
Tech Stack
| Technology | Purpose |
|---|---|
| React 19 | UI framework |
| Vite | Build tool and dev server |
| React Router | Client-side routing |
| Tailwind CSS v4 | Utility-first styling |
| shadcn/ui | Component library (Button, Card, Dialog, etc.) |
| Sonner | Toast notifications |
| lucide-react | Icons |
| React Context | State management (AuthContext, AppContext, SidebarContext, ThemeContext) |
Layout: AppShell + Sidebar
When an authenticated user navigates to /apps/:id/*, the app renders inside AppShell — a layout with a persistent sidebar and a content area.
Sidebar Components
┌──────────────────────────────┐
│ SidebarHeader (logo + brand) │
│ AppSwitcher (app dropdown) │
│ SidebarNav (nav groups) │
│ SidebarFooter (user menu) │
└──────────────────────────────┘SidebarHeader— Aero2 logo, collapse toggleAppSwitcher— Dropdown to switch between apps. Shows "Platform" with a shield icon and indigo accent when in the dashboard app. Operators see the dashboard entry at the top of the list.SidebarNav— Renders nav groups. UsesdashboardNavGroupsfor the dashboard app andappNavGroupsfor regular apps (defined innav-config.ts).SidebarFooter— Avatar, user name, settings link, sign-outSidebarContext— Manages collapsed/expanded state and mobile drawer
Regular App Nav
For per-app management, the sidebar shows:
- Overview — App metrics
- Authentication — Sign-in Methods, Identity Providers, Sessions
- User Management — Users, Roles & Permissions, Organizations
- Developer — API Keys, OAuth Clients
- Configure — Branding, Settings, Custom Domains
- Monitor — Audit Log
Dashboard / Platform Nav
When viewing the dashboard app, the sidebar switches to a platform-focused structure with indigo accent colors:
- Overview — Platform metrics (app count, user count)
- Applications — All Applications
- Authentication — Identity Providers (platform IdPs)
- Users & Access — Users, Roles & Permissions
- Configure — Platform Settings
- Monitor — Audit Log
Dashboard Routes
dashboard.aero2.dev/
/login -> Developer sign-in
/signup -> Developer sign-up
/ -> Application list (after login)
/apps/:id/overview -> App or platform overview
/apps/:id/users -> App user management
/apps/:id/auth -> Auth method configuration
/apps/:id/idps -> Identity provider management
/apps/:id/sessions -> Session management
/apps/:id/roles -> Roles and permissions
/apps/:id/clients -> OAuth client management
/apps/:id/api-keys -> API key management
/apps/:id/branding -> Login page customization
/apps/:id/settings -> App settings
/apps/:id/domains -> Custom domain configuration
/apps/:id/organizations -> Organization management
/apps/:id/audit -> Audit logs
/apps/:id/applications -> All Applications (dashboard only)
/apps/:id/platform/users -> Platform users (dashboard only)
/apps/:id/platform/idps -> Platform IdPs (dashboard only)
/apps/:id/platform/roles -> Platform roles (dashboard only)
/apps/:id/platform/settings -> Platform settings (dashboard only)
/apps/:id/platform/audit -> Platform audit log (dashboard only)App Layout (Per-App Subdomain)
When a user visits an application subdomain (e.g., swift-maple.aero2.dev), the SPA renders branded auth pages for end users:
- Login form (email/password + social IdP buttons)
- Signup form with email verification
- MFA verification page
- Password reset flow
- OAuth2 authorization consent
Per-App Routes
swift-maple.aero2.dev/
/login -> End-user sign-in (branded)
/signup -> End-user sign-up
/oauth2/authorize -> OAuth2 authorization endpoint
/oauth2/token -> Token endpoint
/oauth2/userinfo -> UserInfo endpoint
/oauth2/jwks.json -> JWKS endpoint
/.well-known/openid-config.. -> OIDC discoveryComponent Library
UI components come from shadcn/ui in src/frontend/components/ui/, styled with Tailwind CSS v4 utility classes:
- Button — Multiple variants (default, secondary, destructive, outline, ghost)
- Card — Container with Card.Content sections
- Input — Form inputs with label and validation states
- Dialog / Modal — Overlay dialogs with confirm/cancel
- ConfirmDialog — Destructive action confirmation
- Spinner — Loading indicator
- Badge — Status labels
- Pagination — Page navigation
- Tooltip — Hover tooltips (used in collapsed sidebar)
- Sonner Toaster — Toast notification system
Use cn() from @/lib/utils for conditional class merging. The @/ path alias maps to src/frontend/.
Dark Mode
Dark mode is toggled via ThemeContext, which adds/removes the .dark class on <html>. Tailwind's dark: variant handles all color switching. An anti-FOUC script in index.html applies the class before first paint.
State Management
State is managed through React Context:
- AuthContext —
user,isAuthenticated,isLoading,signOut(), session info - AppContext —
appId,appName,slug,isDashboardfor the current app - SidebarContext — Collapsed/expanded state, mobile drawer open/close
- ThemeContext — Light/dark mode toggle
No external state management library — React Context is sufficient for the current scope.
API Client
All API calls go through utils/api.ts:
import { api, ApiError } from "../utils/api";
const data = await api.get<{ users: User[] }>("/api/users");
await api.post("/api/clients", { name: "My Client" });
await api.put("/api/settings", updatedSettings);
await api.del("/api/sessions/123");- Auto-includes
credentials: "include"and JSON content type - Redirects to
/loginon 401 (suppressible with{ noAuthRedirect: true }) - Throws
ApiErrorwith.status,.error,.details - Never use raw
fetch()for internal API calls