feat(auth): useAuth + ProtectedRoute + signUp dans auth-client (Sprint 1 étape 2)
This commit is contained in:
parent
107a37d197
commit
38777796aa
19 changed files with 2620 additions and 191 deletions
343
docs/DESIGN_SYSTEM.md
Normal file
343
docs/DESIGN_SYSTEM.md
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
# DESIGN_SYSTEM.md — Expria Frontend
|
||||
|
||||
> **Document de référence — Version 1.0 — Sprint 1**
|
||||
> Source de vérité unique pour l'identité visuelle, les tokens de design et les primitives UI.
|
||||
> Toute décision de DA doit être consignée ici avant d'être implémentée.
|
||||
|
||||
---
|
||||
|
||||
## 1. Direction artistique — verrouillée
|
||||
|
||||
**Nom :** Boréal
|
||||
**Positionnement :** institutionnel chaleureux, premium sans flashy, sérieux sans austère.
|
||||
**Référence mentale :** Stripe Dashboard, Linear, Notion Desktop — mais réchauffé d'un cran.
|
||||
|
||||
### Parti pris fondateurs
|
||||
|
||||
| Principe | Décision |
|
||||
|---|---|
|
||||
| Mode canonique Sprint 1 | **Clair uniquement** (light chaud) |
|
||||
| Mode sombre | Prévu Sprint 2+ (tokens écrits dual-theme-ready dès J1) |
|
||||
| Fond principal | `#F4F2EC` (off-white calibré, ni froid ni saturé) |
|
||||
| Surfaces élevées | Blanc pur `#FFFFFF` pour contraste subtil avec le fond |
|
||||
| Bleu de marque | `#1B4FD8` **sacro-saint** en mode clair — aucune variation |
|
||||
| Bleu mode sombre | `#7C9BFF` **prévu** pour Sprint 2+ (pattern Apple system colors) |
|
||||
| Accent chaleureux | Aucun en Sprint 1 — le bleu porte toute l'intentionnalité |
|
||||
| Angles | Rayons généreux mais retenus : 8 / 12 / 16 px |
|
||||
| Ombres | Minimales. 1 ombre-card unique, très subtile. Hairlines 1px privilégiées. |
|
||||
| Animations | 150–200 ms, `ease-out`, respect de `prefers-reduced-motion` |
|
||||
| Icônes | SVG inline dans `shared/ui/icons/` — aucune dépendance externe |
|
||||
| Typographie | Plus Jakarta Sans (via `font-family`, fallback système) |
|
||||
|
||||
### Ce qu'on refuse explicitement
|
||||
|
||||
- Gradients criards (le seul acceptable : aucun).
|
||||
- Glassmorphism ou `backdrop-filter` généralisé — réservé à la bottom nav mobile si besoin.
|
||||
- Emojis dans les éléments interactifs ou les labels fonctionnels.
|
||||
- Ombres lourdes, "drop shadows" style Material Design 2.
|
||||
- Plus de 2 niveaux d'élévation visuelle (fond → card → modal).
|
||||
- Toute police de display fantaisiste, serif décorative ou condensée.
|
||||
- Les motifs SaaS génériques : illustrations 3D, dégradés violet-rose, glass blobs.
|
||||
|
||||
---
|
||||
|
||||
## 2. Tokens — `src/index.css`
|
||||
|
||||
Remplacer intégralement le contenu actuel (`@import 'tailwindcss';`) par le bloc ci-dessous. Tailwind 4 lit automatiquement les tokens déclarés dans `@theme`.
|
||||
|
||||
```css
|
||||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
/* ----- Brand ------------------------------------------------------- */
|
||||
--color-brand: #1B4FD8;
|
||||
--color-brand-hover: #1744B8;
|
||||
--color-brand-active: #13379C;
|
||||
--color-brand-soft: #E7EDFC;
|
||||
--color-brand-ink: #FFFFFF;
|
||||
|
||||
/* ----- Surfaces (light — Sprint 1) --------------------------------- */
|
||||
--color-bg: #F4F2EC;
|
||||
--color-surface: #FBFAF6;
|
||||
--color-surface-raised: #FFFFFF;
|
||||
--color-surface-sunken: #EEECE4;
|
||||
|
||||
/* ----- Ink (texte) ------------------------------------------------- */
|
||||
--color-ink-primary: #0F1220;
|
||||
--color-ink-secondary: #4A4F5E;
|
||||
--color-ink-tertiary: #8A8F9E;
|
||||
--color-ink-inverse: #FBFAF6;
|
||||
|
||||
/* ----- Borders & dividers ------------------------------------------ */
|
||||
--color-border: #E3E0D6;
|
||||
--color-border-strong: #C9C5B7;
|
||||
--color-border-focus: #1B4FD8;
|
||||
|
||||
/* ----- Feedback ---------------------------------------------------- */
|
||||
--color-success: #1F7A4C;
|
||||
--color-success-soft: #E3F2EA;
|
||||
--color-warning: #B8741A;
|
||||
--color-warning-soft: #F7EEDF;
|
||||
--color-danger: #B8322D;
|
||||
--color-danger-soft: #F7E1DF;
|
||||
|
||||
/* ----- Typographie ------------------------------------------------- */
|
||||
--font-sans: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--font-mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;
|
||||
|
||||
/* Échelle : mobile-first, les tailles desktop se gèrent via utilities Tailwind */
|
||||
--text-xs: 11px;
|
||||
--text-sm: 13px;
|
||||
--text-base: 14px;
|
||||
--text-md: 15px;
|
||||
--text-lg: 17px;
|
||||
--text-xl: 20px;
|
||||
--text-2xl: 24px;
|
||||
--text-3xl: 32px;
|
||||
--text-display: 40px;
|
||||
|
||||
/* ----- Rayons ------------------------------------------------------ */
|
||||
--radius-xs: 6px;
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
--radius-xl: 20px;
|
||||
--radius-pill: 999px;
|
||||
|
||||
/* ----- Ombres ------------------------------------------------------ */
|
||||
--shadow-card: 0 1px 2px rgba(15, 18, 32, 0.04), 0 1px 8px rgba(15, 18, 32, 0.03);
|
||||
--shadow-raised: 0 4px 16px rgba(15, 18, 32, 0.06), 0 1px 2px rgba(15, 18, 32, 0.04);
|
||||
--shadow-focus: 0 0 0 3px rgba(27, 79, 216, 0.18);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
/* Globals — reset minimal, fond chaud par défaut */
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
html, body {
|
||||
background: var(--color-bg);
|
||||
color: var(--color-ink-primary);
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
*:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--shadow-focus);
|
||||
border-radius: var(--radius-xs);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0ms !important;
|
||||
transition-duration: 0ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
/* TODO Sprint 2+ — Dark theme (Cadence recalibré) */
|
||||
/* --------------------------------------------------------------------- */
|
||||
/*
|
||||
@theme {
|
||||
--color-bg-dark: #0F1320;
|
||||
--color-surface-dark: #171B2B;
|
||||
--color-surface-raised-dark: #1E2338;
|
||||
--color-ink-primary-dark: #E6E4DB;
|
||||
--color-ink-secondary-dark: #9CA0AC;
|
||||
--color-brand-dark: #7C9BFF;
|
||||
--color-brand-hover-dark: #9AB3FF;
|
||||
--color-border-dark: #2A3048;
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
### Règles d'usage des tokens
|
||||
|
||||
1. **Aucune valeur hexadécimale en dur** dans les composants. Toute couleur passe par un token.
|
||||
2. **Nommage sémantique obligatoire.** On écrit `bg-surface`, pas `bg-gray-50`.
|
||||
3. Si un cas d'usage exige une teinte hors charte, **le documenter ici avant de l'ajouter**. Pas de token orphelin.
|
||||
4. Les tokens marqués `*-dark` ne sont **pas utilisés en Sprint 1**. Leur présence en commentaire est intentionnelle pour faciliter la reprise Sprint 2+.
|
||||
|
||||
---
|
||||
|
||||
## 3. Typographie
|
||||
|
||||
| Usage | Taille | Poids | Tracking | Ligne | Token |
|
||||
|---|---|---|---|---|---|
|
||||
| Display (NCLC hero) | 40px | 700 | -0.02em | 1.0 | `text-display` |
|
||||
| H1 page | 32px | 700 | -0.02em | 1.1 | `text-3xl` |
|
||||
| H2 section | 24px | 700 | -0.015em | 1.2 | `text-2xl` |
|
||||
| H3 card title | 20px | 700 | -0.01em | 1.3 | `text-xl` |
|
||||
| Lead / intro | 17px | 500 | -0.005em | 1.5 | `text-lg` |
|
||||
| Body | 14px | 400 | 0 | 1.6 | `text-base` |
|
||||
| Body renforcé | 15px | 500 | 0 | 1.55 | `text-md` |
|
||||
| Small / meta | 13px | 400 | 0 | 1.5 | `text-sm` |
|
||||
| Eyebrow / label | 11px | 600 | 0.1em (uppercase) | 1.4 | `text-xs` |
|
||||
|
||||
**Règles :**
|
||||
- Tout nombre métier (score 16/20, NCLC 7,5, compteur 3/5) est rendu en `font-variant-numeric: tabular-nums`. Hérité par le body, mais à re-spécifier explicitement sur les tables et listes.
|
||||
- `Plus Jakarta Sans` est déclarée en `font-family` avec fallback système. **Aucune webfont chargée** tant qu'on n'a pas validé la stratégie self-hosting (décision reportée).
|
||||
- Les chiffres français utilisent la **virgule** comme séparateur décimal (`7,5`, jamais `7.5`).
|
||||
|
||||
---
|
||||
|
||||
## 4. Primitives UI — Sprint 1
|
||||
|
||||
À créer dans `src/shared/ui/` en FSD, une primitive par dossier (`button/`, `card/`, etc.) avec `index.ts` pour l'export.
|
||||
|
||||
### Inventaire minimal
|
||||
|
||||
| Composant | Variants | Usage dashboard |
|
||||
|---|---|---|
|
||||
| `Button` | `primary` / `secondary` / `ghost` / `upgrade` | CTA "Nouvelle simulation", "Passer au plan Standard", actions tertiaires |
|
||||
| `Card` | `default` / `raised` / `interactive` | Cadre métriques, item simulation, recommandation |
|
||||
| `MetricCard` | `default` / `hero` (pour le NCLC) | Bloc NCLC, compteur simulations, dernier score |
|
||||
| `ProgressBar` | `default` | Progression vers NCLC 9 |
|
||||
| `Badge` | `plan` / `nclc` / `neutral` | Plan actuel dans header, niveau NCLC par simulation |
|
||||
| `Sidebar` | — | Nav desktop (≥ 1024px) |
|
||||
| `BottomNav` | — | Nav mobile (< 1024px), 4 items max |
|
||||
| `PageHeader` | — | Greeting + plan pill |
|
||||
| `SectionHeader` | — | Titre de section + action optionnelle |
|
||||
|
||||
### Règles d'implémentation
|
||||
|
||||
- Chaque primitive **accepte `className`** en plus de ses props typées, pour overrides ponctuels.
|
||||
- Chaque primitive **expose ses props via un type exporté** (`ButtonProps`, `CardProps`, etc.).
|
||||
- Aucune primitive ne contient de logique métier ou d'appel API. Elles reçoivent tout par props.
|
||||
- Les icônes sont passées par une prop `icon` acceptant un `ReactNode`, jamais par nom de string.
|
||||
|
||||
---
|
||||
|
||||
## 5. Layout dashboard — spécification
|
||||
|
||||
Les primitives ci-dessus s'assemblent dans `src/features/dashboard/` et `src/pages/dashboard/`.
|
||||
|
||||
### Structure sémantique
|
||||
|
||||
```
|
||||
<body>
|
||||
<Sidebar /> (≥ 1024px)
|
||||
<main>
|
||||
<PageHeader /> (greeting + plan)
|
||||
<section>
|
||||
<MetricCard hero /> (NCLC estimé + progression)
|
||||
<MetricCard /> (simulations restantes)
|
||||
<MetricCard /> (dernier score)
|
||||
</section>
|
||||
<Button primary /> (Nouvelle simulation)
|
||||
<Button upgrade /> (Passer au plan Standard)
|
||||
<section>
|
||||
<SectionHeader /> (3 dernières simulations)
|
||||
<Card interactive /> × 3
|
||||
</section>
|
||||
<section>
|
||||
<SectionHeader /> (Prochaine étape recommandée)
|
||||
<Card raised />
|
||||
</section>
|
||||
</main>
|
||||
<BottomNav /> (< 1024px)
|
||||
</body>
|
||||
```
|
||||
|
||||
### Breakpoints
|
||||
|
||||
| Breakpoint | Comportement |
|
||||
|---|---|
|
||||
| `< 1024px` | Mono-colonne, `BottomNav` fixe en bas, padding horizontal 20px |
|
||||
| `≥ 1024px` | Sidebar 240px + contenu centré 860px max, padding horizontal 32px |
|
||||
| `≥ 1440px` | Contenu centré 920px max (pas d'élargissement excessif) |
|
||||
|
||||
### Densité verticale
|
||||
|
||||
- Padding vertical section : 24px mobile, 32px desktop.
|
||||
- Gap inter-cards : 12px mobile, 16px desktop.
|
||||
- Marge sous `PageHeader` : 20px mobile, 28px desktop.
|
||||
|
||||
---
|
||||
|
||||
## 6. Données mock — Sprint 1
|
||||
|
||||
Avant branchement API, fournir les données via `src/shared/api/mock/dashboard.ts`. Données crédibles, françaises, alignées sur l'audience réelle.
|
||||
|
||||
```typescript
|
||||
export const mockDashboard = {
|
||||
user: {
|
||||
firstName: 'Yacine',
|
||||
plan: 'decouverte' as const,
|
||||
planLabel: 'Plan Découverte',
|
||||
},
|
||||
metrics: {
|
||||
nclcEstimated: 7.5,
|
||||
nclcTarget: 9,
|
||||
simulationsUsed: 2,
|
||||
simulationsQuota: 5,
|
||||
lastScore: { value: 16, max: 20, type: 'ecrit' as const },
|
||||
},
|
||||
recentSimulations: [
|
||||
{ id: 's-001', type: 'ecrit', relativeDate: 'il y a 2 jours', score: 16, max: 20, nclc: 8 },
|
||||
{ id: 's-002', type: 'oral', relativeDate: 'il y a 5 jours', score: 14, max: 20, nclc: 7 },
|
||||
{ id: 's-003', type: 'ecrit', relativeDate: 'il y a 1 semaine', score: 15, max: 20, nclc: 7 },
|
||||
],
|
||||
nextStep: {
|
||||
title: 'Cible une simulation orale cette semaine',
|
||||
body: 'Ton écrit est solide (NCLC 8). L\'oral reste à consolider pour sécuriser ton NCLC 9.',
|
||||
action: { label: 'Démarrer Expression Orale', to: '/simulation/orale' },
|
||||
},
|
||||
} as const;
|
||||
```
|
||||
|
||||
**Règles contenu :**
|
||||
- Aucun "Lorem ipsum", aucune date absolue — relatif uniquement (`il y a X jours`).
|
||||
- Les prénoms mocks reflètent l'audience : Yacine, Aminata, Kenza, Bilal, Fatou, Kévin (Canada).
|
||||
- Les scores suivent une progression crédible (pas de 20/20 ni de 5/20).
|
||||
|
||||
---
|
||||
|
||||
## 7. Accessibilité — plancher Sprint 1
|
||||
|
||||
- Contraste minimum **WCAG AA** sur tous les couples texte/fond (vérifié pour la palette ci-dessus).
|
||||
- Tous les éléments interactifs ont un `:focus-visible` avec `--shadow-focus` (halo bleu 3px).
|
||||
- Les icônes purement décoratives portent `aria-hidden="true"`.
|
||||
- Les icônes fonctionnelles (sans label visible) portent `aria-label`.
|
||||
- Les landmarks sémantiques sont utilisés : `<header>`, `<nav>`, `<main>`, `<section>`.
|
||||
- Le `BottomNav` mobile respecte la hauteur minimale tap target : **44×44 px** par item.
|
||||
|
||||
---
|
||||
|
||||
## 8. Dépendances externes — Sprint 1
|
||||
|
||||
**Aucune nouvelle dépendance UI n'est ajoutée en Sprint 1.** On n'installe pas `shadcn/ui`, pas `lucide-react`, pas `radix-ui`. Les primitives sont écrites à la main pour deux raisons :
|
||||
|
||||
1. On veut maîtriser **exactement** chaque composant (pas d'override Tailwind contre Radix).
|
||||
2. Volume fonctionnel Sprint 1 minimal (~9 primitives) : pas d'économie à externaliser.
|
||||
|
||||
La porte reste ouverte Sprint 2+ pour `radix-ui` sur les primitives complexes (Dialog, Popover, Dropdown) si besoin justifié dans un ADR.
|
||||
|
||||
---
|
||||
|
||||
## 9. Journal des décisions DA
|
||||
|
||||
| Date | Décision | Contexte |
|
||||
|---|---|---|
|
||||
| 2026-04-17 | Direction A (Boréal) validée comme canonique | 5 directions explorées (A/B/E/F/G), A et B retenues, A choisie comme base |
|
||||
| 2026-04-17 | Fond `#F4F2EC` (V1 calibré) retenu | Test côte-à-côte contre `#F5F3ED` et `#F2EFE6` |
|
||||
| 2026-04-17 | Dark mode (Cadence) reporté Sprint 2+ | Tokens écrits dual-theme-ready dès J1 pour éviter réécriture |
|
||||
| 2026-04-17 | Bleu `#1B4FD8` sacro-saint en light, `#7C9BFF` prévu pour dark | Pattern Apple system colors |
|
||||
| 2026-04-17 | Pas de shadcn/ui en Sprint 1 | Volume faible, maîtrise totale souhaitée |
|
||||
|
||||
---
|
||||
|
||||
## 10. Hors périmètre Sprint 1
|
||||
|
||||
Éléments explicitement **reportés** :
|
||||
|
||||
- Dark mode applicatif.
|
||||
- Thème haut-contraste (WCAG AAA).
|
||||
- Internationalisation (i18n) — Sprint 1 monolingue FR.
|
||||
- Animations avancées (scroll-linked, shared element transitions).
|
||||
- Illustrations personnalisées / iconographie signature.
|
||||
- Self-hosting de la font Plus Jakarta Sans (on reste en fallback système Sprint 1).
|
||||
|
||||
Chacun de ces points mérite un ADR dédié quand il sera abordé.
|
||||
Loading…
Add table
Add a link
Reference in a new issue