Frontend
A single React SPA that serves three experiences — the authenticated ERP app, the public marketing site, and the customer portal storefront (see ../architecture.md §"One frontend, three experiences"). This page is the map; the deep dives are:
| Doc | What it covers |
|---|---|
| routing.md | The three route trees, lazy loading, guards |
| state.md | Redux (auth) / React Query (server) / Zustand (islands) split |
| api-layer.md | axios client, interceptors, silent refresh |
| auth-and-permissions.md | useAuth, active role, permission gating |
| ag-grid.md | The enterprise grid subsystem |
| forms.md | React Hook Form + Zod patterns |
| styling.md | Chakra v3 theme, dark mode, conventions |
Stack
Versions are ranges from frontend/package.json;
check there for the authoritative list.
| Library | Version | Role |
|---|---|---|
| React + ReactDOM | ^19.2 | UI runtime |
| TypeScript | ~6.0 | Language (tsc -b runs before every build) |
| Vite | ^8.0 | Dev server + bundler (vite.config.ts: @ → src/ alias, port 5173) |
| Chakra UI | ^3.36 | Component system + theming (styling.md) |
| AG Grid Community + ag-grid-react | ^33.3 | Every data table, via the in-house wrapper (ag-grid.md) |
| Redux Toolkit + react-redux | ^2.12 / ^9.3 | Auth state only (state.md) |
| TanStack React Query | ^5.101 | All other server state |
| Zustand | ^5.0 | Small client-state islands (portal auth/cart/wishlist, sidebar, breadcrumb) |
| react-router | ^7.17 | Routing — the unified react-router package, not react-router-dom (routing.md) |
| axios | ^1.17 | HTTP, with auth interceptors (api-layer.md) |
| react-hook-form + zod + @hookform/resolvers | ^7.79 / ^3.25 / ^3.10 | Forms + validation (forms.md) |
| recharts | ^3.8 | Analytics charts |
| three + @react-three/fiber | ^0.185 / ^9.6 | 3D scenes (marketing pages, HR analytics), always code-split |
| altcha | ^3.2 | Proof-of-work CAPTCHA widget on login (altcha-captcha.tsx) |
| @stomp/stompjs + sockjs-client | ^7.3 / ^1.6 | WebSocket clients for notifications/messages/audit-log live updates |
| next-themes | ^0.4 | Light/dark color mode |
| vitest | ^3.0 | Unit tests — currently exactly one test file (see ../testing/README.md) |
Also in the tree: TipTap (rich text), FullCalendar (holiday calendar), @pdfme + jspdf/jspdf-autotable (payslip + grid PDF export), dnd-kit (Kanban), html5-qrcode (inventory scan station), motion, react-icons/lucide-react.
src/ directory map
frontend/src/
├── api/ ~60 thin axios modules, one per backend domain (employees.ts, payroll.ts, …)
│ plus client.ts / portalClient.ts / errors.ts / types.ts → api-layer.md
├── hooks/ Cross-cutting hooks: useAuth, the three STOMP WebSocket hooks, SEO, scanner
│ └── api/ ~52 React Query hooks wrapping api/ modules (query keys + invalidation) → state.md
├── pages/ Route-level components, one folder per domain: auth, billing, crm, hr,
│ inventory, marketing, payroll, portal, recruitment, self-service
├── components/ Feature components grouped per domain (~27 folders) — form drawers,
│ detail drawers, cards, import modals → forms.md
├── ui/ The design-system layer:
│ ├── ag-grid/ Enterprise grid subsystem (ServerDataGrid, useEnterpriseGrid, …) → ag-grid.md
│ ├── components/ Shared primitives: route guards, toaster, color mode, ALTCHA, rich text
│ ├── layout/ AppLayout / PublicLayout / PortalLayout, nav-config, header, profile menu
│ └── theme.ts Chakra v3 system (brand tokens, recipes) → styling.md
├── redux/ The auth slice + store — the ONLY Redux state → state.md
├── store/ Five Zustand stores (portal auth/cart/wishlist, sidebar, breadcrumb)
├── constants/ routes.ts (path constants), permissions.ts (backend permission mirror),
│ role-permissions.ts (static role → permission map) → auth-and-permissions.md
├── lib/ payslip-template/ — @pdfme template registry + builders for payslip PDFs
├── utils/ currency.ts, date.ts formatters
└── assets/ Static assets
Build commands
All from frontend/ (scripts in package.json):
| Command | What it does |
|---|---|
npm run dev | Vite dev server with HMR (see the caveat below before using it) |
npm run build | tsc -b && vite build — type-check then bundle to dist/ |
npm run lint | ESLint (flat config, typescript-eslint + react-hooks v7) |
npm test | Vitest, single run |
npm run test:watch | Vitest in watch mode |
npm run preview | Serve the production dist/ build locally |
The static-Docker-build reality
In the Docker stack, the frontend container is an nginx image serving a static production build — there is no Vite process and no hot reload. Two consequences:
VITE_API_URLis baked in at image build time. It defaults to empty, which means relative URLs proxied by nginx to the backend on the same origin (api-layer.md). Changing it requires rebuilding the image, not restarting the container.- Editing source does nothing to a running stack. To see a change at
http://localhost:5173you must rebuild:docker compose up -d --build frontend.
For day-to-day work use the HMR loop instead: keep the stack running for the
backend/DB, then run npm run dev with VITE_API_URL=http://localhost:8081 in
frontend/.env.local and that origin CORS-whitelisted. The exact recipe is in
../onboarding.md §4 "Pick your dev loop".