Files
emaui/README.md
2026-05-29 15:23:46 +03:00

130 lines
4.4 KiB
Markdown

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