# Story 4.4: Page Register Status: done ## Story En tant que **nouvel utilisateur**, Je veux **créer un compte via un formulaire clair et moderne**, de sorte que **je puisse commencer à traduire des documents**. ## Acceptance Criteria 1. **Navigation**: Page accessible via `/auth/register` 2. **Formulaire**: Email + mot de passe + confirmation avec validation en temps réel 3. **API Integration**: Utilise `apiClient` pour appeler `POST /api/v1/auth/register` 4. **Auto-login**: Après inscription réussie → appel automatique à `POST /api/v1/auth/login` pour obtenir les tokens 5. **Success**: Tokens stockés dans localStorage → redirection vers `/dashboard` 6. **Error Handling**: Erreurs API affichées inline (EMAIL_EXISTS, INVALID_EMAIL, etc.) 7. **Branding**: Logo "Office Translator" avec icône `Languages` de Lucide (PAS "文A" ni "Translate Co.") 8. **TanStack Query**: Utilise `useMutation` pour les requêtes (PAS useState+fetch directement) 9. **UX**: Loading state, validation visuelle inline, toggle password visibility, password strength indicator 10. **Colocation**: Composants, hooks et types dans `frontend/src/app/auth/register/` ## Tasks / Subtasks - [x] **Task 1: Créer les types TypeScript** (AC: #3, #4) - [x] 1.1 Créer `types.ts` dans `frontend/src/app/auth/register/` - [x] 1.2 Définir `RegisterRequest`, `RegisterResponse`, `RegisterFormData` - [x] **Task 2: Créer le hook useRegister** (AC: #3, #4, #5, #8) - [x] 2.1 Créer `useRegister.ts` dans `frontend/src/app/auth/register/` - [x] 2.2 Utiliser `useMutation` pour `POST /api/v1/auth/register` - [x] 2.3 Après succès inscription → appeler `apiClient.post('/api/v1/auth/login', {email, password})` - [x] 2.4 Stocker tokens dans localStorage: `token`, `refresh_token`, `user` - [x] 2.5 Rediriger vers `/dashboard` (ou `?redirect=` param) - [x] **Task 3: Créer RegisterForm.tsx** (AC: #2, #6, #7, #9) - [x] 3.1 Créer `RegisterForm.tsx` dans `frontend/src/app/auth/register/` - [x] 3.2 Champs: email, password, confirmPassword (optionnel: name) - [x] 3.3 Validation inline: email format, password ≥ 8 chars, confirmation match - [x] 3.4 Password strength indicator (barres visuelles) - [x] 3.5 Toggle visibility pour les deux champs password - [x] 3.6 Afficher erreur API inline (pas de state `error` séparé — utiliser `mutation.isError`) - [x] 3.7 Branding: `Languages` icône + "Office Translator" - [x] **Task 4: Mettre à jour page.tsx** (AC: #1, #10) - [x] 4.1 Remplacer le contenu de `frontend/src/app/auth/register/page.tsx` - [x] 4.2 Utiliser `RegisterForm` colocated avec `Suspense` - [x] 4.3 Conserver le fond dégradé (copier le pattern de `auth/login/page.tsx`) - [x] **Task 5: Vérifier le build et tester** (AC: Tous) - [x] 5.1 `npm run build` dans `frontend/` → 0 erreurs TypeScript ✓ Compilé avec succès - [x] 5.2 Tester inscription avec email valide → redirection /dashboard - [x] 5.3 Tester email déjà existant → message d'erreur "Un compte existe déjà..." - [x] 5.4 Tester validation inline (email invalide, password trop court, confirmation mismatch) ## Dev Notes ### 🏗️ Stack Technique | Technologie | Version | |-------------|---------| | Next.js | 16.0.6 (App Router) | | React | 19.2.0 | | TanStack Query | v5.90.21 | | Tailwind CSS | configuré | | Lucide React | disponible | | shadcn/ui | Button, Input, Label, Card via `@/components/ui/` | ### 📁 Structure Cible (Colocation Pattern) ``` frontend/src/app/auth/register/ ├── page.tsx # ✅ Déjà existant - À REMPLACER (garde structure Suspense) ├── RegisterForm.tsx # 🆕 Composant formulaire colocated ├── useRegister.ts # 🆕 Hook mutation colocated └── types.ts # 🆕 Types TypeScript colocated ``` **Règle absolue (architecture.md):** ``` 🚨 FICHIERS SPÉCIAUX: page.tsx, layout.tsx → TOUJOURS minuscules 🚨 COLOCATION: Components/hooks/types dans le dossier de leur page ``` ### ⚠️ CRITIQUE: Comportement du Backend **POST `/api/v1/auth/register`** — retourne **PAS de tokens**: ```json // Succès (201) { "data": { "id": "usr_xxx", "email": "user@example.com", "tier": "free" }, "meta": {} } ``` **DONC: Auto-login obligatoire après inscription**. Le hook doit chaîner: 1. `apiClient.post('/api/v1/auth/register', {email, password, name?})` 2. Si succès → `apiClient.post('/api/v1/auth/login', {email, password})` 3. Stocker les tokens → rediriger **POST `/api/v1/auth/login`** — retourne: ```json { "data": { "access_token": "eyJ...", "refresh_token": "eyJ...", "token_type": "bearer" }, "meta": {} } ``` **Codes d'erreur registration:** | Code erreur | HTTP | Message | |-------------|------|---------| | `EMAIL_EXISTS` | 400 | "Un compte existe déjà avec cette adresse email" | | `INVALID_EMAIL` | 400 | "Format d'email invalide" | | `INVALID_REQUEST` | 400 | "Données d'inscription invalides" | ### 🔧 Types TypeScript à Créer ```typescript // frontend/src/app/auth/register/types.ts export interface RegisterRequest { email: string; password: string; name?: string; } export interface RegisterResponse { id: string; email: string; tier: 'free' | 'pro'; } ``` **Note:** Réutiliser `LoginResponse` depuis `../login/types` pour l'auto-login: ```typescript import type { LoginResponse } from '../login/types'; ``` ### 🔧 Hook useRegister à Créer ```typescript // frontend/src/app/auth/register/useRegister.ts 'use client'; import { useMutation } from '@tanstack/react-query'; import { useRouter, useSearchParams } from 'next/navigation'; import { apiClient } from '@/lib/apiClient'; import type { RegisterRequest, RegisterResponse } from './types'; import type { LoginResponse } from '../login/types'; export function useRegister() { const router = useRouter(); const searchParams = useSearchParams(); const redirect = searchParams.get('redirect') || '/dashboard'; return useMutation({ mutationFn: async (data: RegisterRequest) => { // Étape 1: Inscription await apiClient.post('/api/v1/auth/register', data); // Étape 2: Auto-login pour obtenir les tokens const loginResponse = await apiClient.post( '/api/v1/auth/login', { email: data.email, password: data.password } ); return loginResponse.data; }, onSuccess: (data) => { // Même pattern que useLogin localStorage.setItem('token', data.access_token); localStorage.setItem('refresh_token', data.refresh_token); // Note: register ne retourne pas user complet, construire depuis loginResponse router.push(redirect); }, }); } ``` ### 🔧 RegisterForm.tsx à Créer **Pattern à suivre: `frontend/src/app/auth/login/LoginForm.tsx`** (déjà refactorisé) **Différences par rapport à LoginForm:** - Champ `email` + `password` + `confirmPassword` (+ optionnel `name`) - Validation inline: format email, longueur password ≥ 8, passwords match - Password strength indicator (barres visuelles — code existant dans l'ancien register) - Bouton: `UserPlus` + "Create Account" → `ArrowRight` - Lien bas: "Vous avez déjà un compte?" → `/auth/login` - Après soumission: utiliser `mutation.isError` et `mutation.error?.message` **Composants UI disponibles dans `@/components/ui/`:** - `button.tsx` — variants: `default`, `premium`, `outline`; sizes: `default`, `lg` - `input.tsx` — supporte `leftIcon`, `rightIcon` props (voir usage dans ancien register) - `label.tsx` - `card.tsx` — variants: `elevated`, `outline`; prop `hover={false}` (pattern login) - `badge.tsx` **⚠️ Anti-pattern à éviter avec Input:** ```typescript // ❌ MAUVAIS - l'ancien register utilise leftIcon/rightIcon props // Vérifier si ces props existent dans frontend/src/components/ui/input.tsx // Si NON → utiliser div relative + positionnement absolu (comme LoginForm) // ✅ Pattern LoginForm (sûr):
``` ### 📁 Fichier Existant à Remplacer `frontend/src/app/auth/register/page.tsx` (605 lignes) contient: - ❌ Branding "Translate Co." + logo "文A" → **À remplacer par "Office Translator" + `Languages`** - ❌ `fetch("http://localhost:8000/api/auth/register")` hardcodé → **À remplacer par `apiClient`** - ❌ Tout inline (pas de colocation) → **À extraire dans composants colocated** - ❌ `useState` loading manual → **À remplacer par `useMutation`** - ✅ Conserver: fond dégradé avec effets flottants, structure Suspense - ✅ Conserver: password strength indicator (réutiliser la logique) - ✅ Conserver: validation inline avec `CheckCircle`/`AlertTriangle` ### 🚨 Anti-Patterns à Éviter 1. **NE PAS hardcoder l'URL**: ~~`fetch("http://localhost:8000/api/auth/register")`~~ → utiliser `apiClient.post()` 2. **NE PAS utiliser l'ancienne API**: ~~`/api/auth/register`~~ → utiliser `/api/v1/auth/register` 3. **NE PAS utiliser `useState` + `fetch` direct** → utiliser `useMutation` 4. **NE PAS appeler login endpoint avec `fetch` direct** → chaîner dans `mutationFn` avec `apiClient` 5. **NE PAS oublier le Suspense** — `useSearchParams()` nécessite Suspense boundary 6. **NE PAS créer de logique dupliquée** — réutiliser `LoginResponse` type depuis `../login/types` 7. **NE PAS créer un fichier `Page.tsx`** — app router exige `page.tsx` en minuscules 8. **NE PAS mettre `name` comme champ obligatoire** — le backend l'accepte mais ne l'exige pas (vérifier: champ optionnel) ### 📊 Token Storage (même pattern que login) ```typescript // Dans onSuccess après auto-login: localStorage.setItem('token', data.access_token); localStorage.setItem('refresh_token', data.refresh_token); // Note: login v1 ne retourne pas user dans data → ne pas stocker user si absent ``` **Pourquoi localStorage et pas httpOnly cookies:** - `apiClient.ts` lit depuis `localStorage.getItem('token')` (ligne 35) - Cohérent avec `useLogin.ts` existant - Migration vers httpOnly cookies = modification backend (hors scope) ### 🔍 Composants Réutilisables Existants | Composant | Chemin | Usage | |-----------|--------|-------| | `apiClient` | `@/lib/apiClient` | Toutes requêtes HTTP | | `ApiClientError` | `@/lib/apiClient` | Type pour `mutation.error` | | `Button` | `@/components/ui/button` | variant="premium" ou default | | `Input` | `@/components/ui/input` | Champs formulaire | | `Label` | `@/components/ui/label` | Labels formulaire | | `Card` | `@/components/ui/card` | Container formulaire | | `LoginResponse` | `../login/types` | Type réponse auto-login | ### 🔗 Cohérence Cross-Story (Context Continuité) **Story 4.3 (Page Login — terminée)** a établi ces patterns: - Route: `app/auth/login/` (sans route group `(auth)`) - Pattern colocation: `LoginForm.tsx`, `useLogin.ts`, `types.ts` dans `app/auth/login/` - Page wrapper: `Suspense` + fond dégradé avec effets flottants - Hook: `useMutation` + `onSuccess` qui stocke dans localStorage + redirect - Branding: `Languages` icône + "Office Translator" **Register DOIT suivre le même pattern** pour cohérence dans `app/auth/register/`. **Lien de navigation inter-pages:** - Login → Register: `/auth/register` (ajouter lien si absent) - Register → Login: `/auth/login` (existe déjà dans ancien page.tsx) ### 🧪 Tests à Effectuer 1. **Build**: `npm run build` dans `frontend/` → 0 erreurs TypeScript/ESLint 2. **Inscription succès**: Email valide + password ≥ 8 chars → redirection /dashboard avec tokens 3. **Email existant**: Réponse 400 EMAIL_EXISTS → message "Un compte existe déjà..." 4. **Email invalide**: Format incorrect → validation inline immédiate 5. **Password mismatch**: Confirmation ≠ password → erreur inline 6. **Password trop court**: < 8 chars → erreur inline 7. **Token storage**: Vérifier localStorage après succès (`token`, `refresh_token`) 8. **Branding**: Vérifier "Office Translator" et icône `Languages` (pas "Translate Co." / "文A") ### Project Structure Notes **Fichiers à modifier:** - `frontend/src/app/auth/register/page.tsx` — Simplifier, utiliser `RegisterForm` colocated **Fichiers à créer:** - `frontend/src/app/auth/register/RegisterForm.tsx` — Composant formulaire - `frontend/src/app/auth/register/useRegister.ts` — Hook mutation avec auto-login - `frontend/src/app/auth/register/types.ts` — `RegisterRequest`, `RegisterResponse` **Aucun fichier global à créer** — tout est colocated dans le dossier de la route. ### References - [Source: _bmad-output/planning-artifacts/epics.md#Story-4.4] — Story requirements - [Source: _bmad-output/planning-artifacts/architecture.md#Frontend-Architecture] — TanStack Query + colocation - [Source: _bmad-output/planning-artifacts/architecture.md#Naming-Patterns] — Conventions nommage - [Source: _bmad-output/planning-artifacts/architecture.md#API-Response-Formats] — Format réponse API - [Source: routes/auth_routes.py#register_v1] — Endpoint register (201, sans tokens) - [Source: routes/auth_routes.py#login_v1] — Endpoint login (tokens dans data) - [Source: _bmad-output/implementation-artifacts/4-3-page-login.md] — Pattern login (colocation, hook, branding) - [Source: frontend/src/app/auth/login/LoginForm.tsx] — Modèle composant formulaire - [Source: frontend/src/app/auth/login/useLogin.ts] — Modèle hook useMutation - [Source: frontend/src/app/auth/login/types.ts] — LoginResponse réutilisable - [Source: frontend/src/app/auth/register/page.tsx] — Fichier actuel à refactorer (conserve: fond, Suspense) - [Source: frontend/src/lib/apiClient.ts] — Client API centralisé (lecture token: localStorage) - [Source: frontend/src/components/ui/] — Composants shadcn/ui disponibles ## Dev Agent Record ### Agent Model Used openrouter/anthropic/claude-sonnet-4.6 ### Debug Log References ### Completion Notes List - Créé `types.ts` : `RegisterRequest`, `RegisterResponse` (optionnel: name; réutilise `LoginResponse` depuis `../login/types`) - Créé `useRegister.ts` : `useMutation` qui chaîne register → auto-login → localStorage → redirect. Endpoint `/api/v1/auth/register` (sans tokens) + `/api/v1/auth/login` (tokens). Pattern identique à `useLogin.ts`. - Créé `RegisterForm.tsx` : formulaire email + password + confirmPassword. Validation inline avec état `touched`, password strength indicator (4 barres), toggle visibility (Eye/EyeOff), branding "Office Translator" + icône `Languages`, erreur API via `mutation.isError` / `mutation.error?.message`. - Remplacé `page.tsx` (605 lignes) par version simplifiée : fond dégradé conservé, `Suspense` boundary pour `useSearchParams()`, `RegisterForm` colocated. - Build Next.js 16.0.6 : **✓ Compilé sans erreurs TypeScript**. Route `/auth/register` présente dans l'output. ### File List - `frontend/src/app/auth/register/types.ts` — Créé - `frontend/src/app/auth/register/useRegister.ts` — Créé - `frontend/src/app/auth/register/RegisterForm.tsx` — Créé - `frontend/src/app/auth/register/page.tsx` — Modifié (remplacé 605 lignes par version colocation) - `frontend/src/app/auth/login/types.ts` — Modifié (correction type LoginResponse: supprimé `user`, ajouté `token_type`) ## Senior Developer Review (AI) **Date:** 2026-02-23 **Reviewer:** Code Review Workflow ### Issues Found & Fixed | # | Sévérité | Issue | Action | |---|----------|-------|--------| | 1 | 🔴 CRITICAL | Type `LoginResponse` incorrect — contenait `user: User` mais l'API v1 login ne retourne pas user | Corrigé: supprimé `user`, ajouté `token_type` | | 2 | 🟡 MEDIUM | Pas de `onError` handler dans useMutation | Ajouté callback avec logging console | | 3 | 🟡 MEDIUM | Liens morts `/terms` et `/privacy` | Remplacé par texte simple | | 4 | 🟡 MEDIUM | Duplication logique password visibility | Refactorisé en composant `PasswordToggleIcon` | | 5 | 🟢 LOW | Couleurs `bg-warning`/`bg-success` non standard Tailwind | Remplacé par `bg-yellow-500`/`bg-green-500` | ### Files Modified by Review - `frontend/src/app/auth/login/types.ts` - `frontend/src/app/auth/login/useLogin.ts` - `frontend/src/app/auth/register/useRegister.ts` - `frontend/src/app/auth/register/RegisterForm.tsx` ### Verdict ✅ **APPROVED** — Tous les AC implémentés, issues corrigées.