# 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`.
