## Error handling
Requested: a centralized error-handling service on the API, custom error
codes shared across apps, and a client-side error service for i18n labels.
- packages/shared/src/errors/error-codes.ts — ErrorCode enum + ApiErrorResponse
contract. Single source of truth: neither side hardcodes a raw error string
the other has to guess at.
- apps/api: HttpError now carries an ErrorCode (not just a message).
ErrorHandlerService (new) centralizes every "how do we turn a thrown error
into an HTTP response" decision — app.ts's error middleware is now a thin
adapter calling into it. API messages reverted to English/dev-facing (they
were French from an earlier pass) since user-facing text is now generated
client-side from the code.
- apps/web: ApiClient (class, singleton instance) throws ApiError carrying
the code. ErrorMessageService (new) maps every ErrorCode to a localized
label, structured with a Locale type from the start (only "fr" exists, but
adding a language later is "add a locale to the map", not "hunt down every
hardcoded string"). LoginPage/SignupPage now display
errorMessageService.getLabel(err.code), never err.message directly.
- Tests strengthened to assert on `code`, not just HTTP status (Mocha +
Cucumber, new "the response error code should be" step). Cypress mocks
updated to the new {code, message} response shape.
## Code quality pass
Per explicit feedback: heavy JSDoc on every interface/type/class/function/
method/member touched in this PR, explicit public/private visibility on
every class member (ApiClient, ErrorMessageService, ErrorHandlerService,
HttpError), no HTML/logic mixing (styling extracted out of components
entirely, never inline).
ApiClient/ErrorMessageService were initially written as static-only classes;
switched to instance-based singletons (matching ErrorHandlerService's
existing pattern) after Biome's noStaticOnlyClass rule flagged the
static-only shape as an anti-pattern — same "class with visibility
modifiers" outcome, without fighting the linter.
## SCSS + theming
- apps/web/src/styles/_theme.scss — design tokens as CSS custom properties
on :root (colors, spacing, typography), not plain Sass variables — makes
them available at runtime, not just compile time, so a future theme
switch (e.g. dark mode) is "redefine these variables" rather than
rebuilding stylesheets.
- apps/web/src/styles/global.scss replaces the old single index.css:
reset + theme import only, loaded once from main.tsx.
- Per-page/component styles colocated (HomePage.tsx + HomePage.scss);
styles shared by multiple pages within one feature live in that feature's
folder (features/auth/auth-form.scss, used by both Login/SignupPage) —
not duplicated per page, not dumped in the global stylesheet either.
- Component-level .scss files intentionally don't `@use` the theme
partial: they only consume CSS custom properties (global at runtime via
global.scss), not Sass-level symbols, so importing it would do nothing —
documented inline rather than left as a silently-redundant import.
- vite.config.ts opts into Sass's modern compiler API to silence a
legacy-js-api deprecation warning on every build.
## specs/ updates
- New specs/error-handling.md — the ErrorCode/ApiErrorResponse contract,
both services, with a flow diagram.
- New specs/frontend-architecture.md — apps/web folder structure, routing/
auth-guard flow, SCSS/theming conventions.
- specs/batch-cooking-architecture.md links to both (original doc content
otherwise untouched — it's the user's own hand-authored source doc).
## Verification
Full lint/mocha/cucumber/build green. Manually re-verified the whole auth
flow in a real browser against native dev servers (not just the automated
suites): signup, the EMAIL_ALREADY_IN_USE → "Cet email est déjà utilisé"
translation end-to-end (confirmed the raw API response carries the English
dev message + code, and the UI shows the French label), wrong-password
INVALID_CREDENTIALS → its label, and confirmed the theme tokens actually
apply (computed button background-color matches --color-primary, card
max-width matches the token value) rather than trusting the build succeeding.
110 lines
5.3 KiB
Markdown
110 lines
5.3 KiB
Markdown
# Architecture frontend — Projet Batch-cooking
|
|
|
|
> Documentation de l'organisation d'`apps/web` : structure des dossiers, routing,
|
|
> gestion des erreurs, et conventions de style (SCSS/theming).
|
|
|
|
---
|
|
|
|
## Structure des dossiers
|
|
|
|
```
|
|
apps/web/src/
|
|
├── api/
|
|
│ └── client.ts # ApiClient — appels fetch vers l'API (voir error-handling.md)
|
|
├── services/
|
|
│ └── error-message.service.ts # ErrorMessageService — libellés d'erreur i18n
|
|
├── features/
|
|
│ └── auth/ # tout ce qui concerne l'authentification
|
|
│ ├── AuthContext.tsx # état global (profil connecté, login/signup/logout)
|
|
│ ├── RequireAuth.tsx # garde de route : redirige vers /login si non connecté
|
|
│ ├── RedirectIfAuthenticated.tsx # garde de route inverse (pour /login, /signup)
|
|
│ └── auth-form.scss # styles partagés par LoginPage et SignupPage
|
|
├── pages/
|
|
│ ├── LoginPage.tsx / .scss (via auth-form.scss, partagé)
|
|
│ ├── SignupPage.tsx / .scss (via auth-form.scss, partagé)
|
|
│ └── HomePage.tsx + HomePage.scss
|
|
├── styles/
|
|
│ ├── _theme.scss # tokens de design (couleurs, espacements, typographie)
|
|
│ └── global.scss # reset minimal + import du theme — importé une seule fois (main.tsx)
|
|
├── lib/
|
|
│ └── zod-errors.ts # utilitaire : erreurs zod → { champ: message }
|
|
├── App.tsx # table de routes
|
|
└── main.tsx # point d'entrée : providers (Router, AuthProvider) + import du CSS global
|
|
```
|
|
|
|
**Règle de placement des styles** : un style spécifique à un seul composant/page vit
|
|
dans un fichier `.scss` au même niveau que ce composant (`HomePage.tsx` +
|
|
`HomePage.scss`). Un style partagé par plusieurs composants d'une même feature vit
|
|
dans le dossier de la feature (`features/auth/auth-form.scss`, utilisé par
|
|
`LoginPage` et `SignupPage`). Seuls le reset et les tokens globaux vivent dans
|
|
`styles/`.
|
|
|
|
---
|
|
|
|
## Routing et gardes d'authentification
|
|
|
|
```mermaid
|
|
flowchart TB
|
|
START(("Visite de l'app"))
|
|
CHECK{"AuthProvider :<br/>GET /auth/me"}
|
|
START --> CHECK
|
|
|
|
CHECK -->|"200 (session valide)"| AUTHED["user défini"]
|
|
CHECK -->|"401 (pas de session)"| ANON["user = null"]
|
|
|
|
AUTHED --> ROUTE_HOME["/ → HomePage"]
|
|
AUTHED --> ROUTE_LOGIN_A["/login ou /signup"]
|
|
ROUTE_LOGIN_A -->|"RedirectIfAuthenticated"| ROUTE_HOME
|
|
|
|
ANON --> ROUTE_HOME_A["/"]
|
|
ROUTE_HOME_A -->|"RequireAuth"| ROUTE_LOGIN["/login"]
|
|
ANON --> ROUTE_LOGIN2["/login ou /signup → rendu normal"]
|
|
```
|
|
|
|
- `AuthContext` (`features/auth/AuthContext.tsx`) appelle `GET /auth/me` une seule
|
|
fois au montage pour restaurer la session depuis le cookie httpOnly — c'est ce qui
|
|
permet à un rechargement de page de garder l'utilisateur connecté.
|
|
- `RequireAuth` et `RedirectIfAuthenticated` sont deux gardes de route
|
|
(`react-router-dom`) qui lisent cet état : la première protège `/`, la seconde
|
|
protège `/login` et `/signup` (redirige un utilisateur déjà connecté vers `/`).
|
|
Les deux affichent `null` tant que la vérification initiale est en cours, pour
|
|
éviter un flash de contenu suivi d'une redirection.
|
|
|
|
---
|
|
|
|
## Client API et gestion des erreurs
|
|
|
|
Voir [error-handling.md](./error-handling.md) pour le détail du contrat d'erreurs
|
|
partagé avec l'API. En résumé côté frontend :
|
|
|
|
- `ApiClient` (`api/client.ts`) — classe avec instance unique exportée
|
|
(`apiClient`), enveloppe `fetch` avec `credentials: "include"` (requis pour que
|
|
le cookie de session httpOnly parte/revienne, l'API et le web étant sur des
|
|
origines différentes). Lève `ApiError` (porteuse du `code` d'erreur) pour toute
|
|
réponse non-2xx.
|
|
- `ErrorMessageService` (`services/error-message.service.ts`) — traduit un `code`
|
|
d'erreur en libellé affichable, avec support de locale (`fr` uniquement pour
|
|
l'instant).
|
|
|
|
---
|
|
|
|
## SCSS et theming
|
|
|
|
- **`sass`** (Dart Sass) est utilisé via le support natif de Vite — aucune config
|
|
supplémentaire needed au-delà d'avoir le package installé (`vite.config.ts` fixe
|
|
juste l'API moderne de Sass pour éviter un warning de dépréciation).
|
|
- **`styles/_theme.scss`** — tokens de design exposés en **custom properties CSS**
|
|
sur `:root` (`--color-primary`, `--space-md`, etc.), pas en simples variables
|
|
SCSS : ça les rend disponibles au runtime, pas seulement à la compilation — ce qui
|
|
permettrait un futur switch de thème (ex. mode sombre) en redéfinissant juste ces
|
|
variables, sans reconstruire les feuilles de style. Toute nouvelle règle CSS doit
|
|
référencer `var(--token)`, jamais une couleur/valeur en dur.
|
|
- **`styles/global.scss`** — importé une seule fois, dans `main.tsx`. Contient
|
|
uniquement le reset minimal et l'import du thème (`@use "./theme"`). Rien de
|
|
spécifique à une page/un composant n'y va.
|
|
- Les tokens étant des **custom properties CSS** (pas des variables Sass), ils sont
|
|
disponibles globalement au runtime dès que `global.scss` a été chargé une fois —
|
|
un fichier `.scss` de composant/page les consomme directement via `var(--token)`,
|
|
sans avoir besoin de `@use` le partiel theme (ce serait un import sans effet,
|
|
puisqu'aucun symbole Sass n'en est consommé). Chaque fichier documente en
|
|
commentaire à quoi correspond chaque règle un peu non-triviale.
|