# EMA Platform — Nx Monorepo Frontend A fully scaffolded Nx monorepo housing two Vite + React 19 SPAs (Backoffice and Portal) with shared libraries for API integration, UI components, and theming. --- ## Tech Stack | Tool | Version | |------|---------| | React | 19 | | Nx | 22 | | Vite | 7 | | TypeScript | 5.9 | | Redux Toolkit | 2.11 | | Mantine | 8.3 | | React Router | 7 | | TanStack Query | 5 | | React Hook Form | 7 | | Zod | 4 | | Tailwind CSS | 3.4 | | Vitest | 4 | --- ## Monorepo Structure ``` emaui/ ├── apps/ │ ├── backoffice/ # Admin/operator SPA — port 4201 │ └── portal/ # End-user SPA — port 4200 └── libs/ ├── api/ # RTK Query baseApi, session resolution, generic query/mutation hooks ├── ui/ # Shared Mantine components (ConfirmModal, ApiErrorAlert, notify) └── shared/ # Mantine theme (emaTheme), design tokens ``` ### libs/api - `base-api/` — RTK Query `createApi` instance with `prepareHeaders` that injects the Bearer token from Redux state or localStorage. - `session/` — `resolveTokenFromStorage()` reads from `localStorage` keys or `auth-token` cookie. `resolveSessionContext()` merges Redux state token with storage fallback. - `query-and-mutation/` — Generic `useApiQuery` / `useApiMutation` wrappers for one-off API calls without defining a dedicated endpoint file. ### libs/ui - `ConfirmModal` — Reusable Mantine modal for destructive-action confirmation. - `ApiErrorAlert` — Extracts a human-readable message from RTK Query error shapes or Error objects. - `notify` — Thin wrapper around `@mantine/notifications` with `.success`, `.error`, `.info`, `.warning` helpers. ### libs/shared - `ema-theme` — Mantine v8 `createTheme()` with `emaPrimary` (blue) and `emaSecondary` (warm) color tuples, Inter font, and custom shadow scale. --- ## Auth Flow 1. User submits the login form (LoginForm / LoginPage). 2. The form calls the `login` RTK Query mutation (backoffice) or a plain `fetch` (portal). 3. On success, `loginSuccess` action is dispatched → Redux `auth` slice stores `token` and `user`; `authStorage.setToken()` persists the token to `localStorage`. 4. `baseApi`'s `prepareHeaders` reads the token via `resolveSessionContext(getState())` and attaches `Authorization: Bearer ` to every RTK Query request. 5. `ProtectedRoute` checks `localStorage` for the token key on every navigation — if absent, redirects to `/login`. 6. `logout` action clears Redux state and calls `authStorage.clear()` to remove all localStorage keys. --- ## Local Setup ```bash # 1. Install dependencies npm install # 2. Copy environment config cp .env.example .env # Edit VITE_BASE_API_URL to point at your running backend # 3. Start the backoffice (port 4201) npm run backoffice # 4. Start the portal (port 4200) npm run portal # 5. Or start both in parallel npm run dev:all ``` --- ## Environment Variables | Variable | Required | Default | Description | |---|---|---|---| | `VITE_BASE_API_URL` | Yes | `http://localhost:3000` | Base URL for all API requests | | `VITE_ENABLE_DEVELOPER_TOOLS` | No | `true` | Toggle Redux DevTools | | `PORTAL_PORT` | No | `4200` | Docker host port for portal | | `BACKOFFICE_PORT` | No | `4201` | Docker host port for backoffice | --- ## Docker ```bash # Build and run both apps via Docker Compose cp .env.example .env docker compose up --build ``` The `Dockerfile` uses multi-stage builds with named targets (`portal` / `backoffice`). Each stage produces an nginx image serving the built SPA. --- ## Adding a New Feature 1. Create the feature folder under the relevant app: ``` apps/backoffice/src/app/features// ├── types/ # TypeScript interfaces ├── api/ # RTK Query injectEndpoints ├── store/ # Redux slice (if local state needed) ├── hooks/ # Custom hooks wrapping store/api ├── components/ # Presentational React components └── pages/ # Route-level components ``` 2. Wire up the API endpoint in `/api/-api.ts` using `baseApi.injectEndpoints(...)`. 3. Add a route in `apps//src/app/router/index.tsx` (backoffice) or `apps//src/app/router.tsx` (portal). 4. Add a sidebar entry in `AppSidebar.tsx` (backoffice only) for the new route. 5. If the feature needs shared UI, add components to `libs/ui/` and export from `libs/ui/src/index.ts`.