Frontend Architecture
apps/web is a single React app that serves four personas:
- consumer
- client
- pos_operator
- admin
Runtime Stack
- React 18
- Vite 5
- React Router 6
- Zustand for auth state
- Supabase JS with PKCE auth
- React Query is present but not the primary data layer
Bootstrap Sequence
src/main.tsx mounts the app through the router and query client. App.tsx then:
- Tries to load the current session profile.
- Listens for auth state changes.
- Redirects into the correct role shell once profile state is known.
Auth and Profile State
src/lib/auth-profile.ts calls GET /api/v1/auth/me and retries transient failures with backoff.
The auth store keeps:
- user identity
- role and status
- tenant IDs
- app access flags
- onboarding state
- profile loading and error state
Route Guarding
The app uses layered guards:
PkceRedirectLayoutpreserves auth callback codes.ProtectedRouterequires a session.CanUseAppRouteblocks suspended or pending users.RoleRouterestricts persona-specific pages.AuthRedirectorapplies top-level redirects.
Role Layouts
The shell changes by persona:
- clients get a collapsible merchant sidebar
- admins get a collapsible admin sidebar
- POS operators get a compact top bar
- consumers get a bottom tab bar
Route Map
Shared and status routes
/login/auth/callback/profile/profile-error/pending-approval/account-blocked
Consumer routes
/wallet/history/offers/offers/:campaignId/redeem/preferences/marketing
Merchant and admin routes
/dashboard/analytics/analytics/:tenantId/rewards/promotions/campaigns/campaigns/new/campaigns/:campaignId/pos/store-operators/transactions/admin/tenants/admin/users/admin/audit-logs/onboarding
API Client Contract
src/lib/api.ts wraps fetch and automatically attaches the Supabase bearer token.
It also turns API errors into a structured ApiRequestError so callers can handle retries and auth failures consistently.
Navigation and Presentation
The current UI uses CSS variables and shared primitives from src/components/ui.tsx.
Notable layout choices:
- merchant and admin shells use sidebars
- consumer navigation is mobile-first
- POS operators get a compact operational view
- tenant branding, including the program name and brand colors, is loaded from the API and rendered in the shell
What the Frontend Does Not Own
The frontend does not own any security boundary. It only reflects what the backend already decided:
- role
- tenant membership
- onboarding status
- account approval or block state
The backend remains the source of truth for every authorization decision.