# 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 storage.
- `session/` — `resolveTokenFromStorage()` reads the `auth-token` cookie first, falling back to `localStorage` for legacy pre-migration sessions. `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 a cookie (both apps call `configureAuthStorage(prefix, true)`).
4. `baseApi`'s `prepareHeaders` reads the token via `resolveSessionContext(getState())` and attaches `Authorization: Bearer <token>` to every RTK Query request.
5. `ProtectedRoute` checks `authStorage`/the token cookie on every navigation — if absent, redirects to `/login`.
6. `logout` action clears Redux state and calls `authStorage.clear()` to remove all auth cookies.

---

## 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 |
| `VITE_PAYMENT_API_URL` | Yes (portal) | — | Base URL for the payment service (portal `payment` feature). Can be a same-origin path if a backend proxy is later placed in front of it |
| `VITE_PAYMENT_SERVICE_TOKEN` | Yes (portal) | — | Sent as `x-service-token` on every payment request. Note: bundled `VITE_*` values are public in the built app, not secret |
| `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/<feature-name>/
   ├── 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 `<feature-name>/api/<feature>-api.ts` using `baseApi.injectEndpoints(...)`.

3. Add a route in `apps/<app>/src/app/router/index.tsx` (backoffice) or `apps/<app>/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`.
