# 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<LoginResponse>('/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<RegisterResponse>('/api/v1/auth/register', data);

      // Étape 2: Auto-login pour obtenir les tokens
      const loginResponse = await apiClient.post<LoginResponse>(
        '/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):
<div className="relative">
  <Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
  <Input className="pl-10" ... />
</div>
```

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