Files
office_translator/_bmad-output/implementation-artifacts/4-4-page-register.md
Sepehr Ramezani 26bd096a06 feat: production deployment - full update with providers, admin, glossaries, pricing, tests
Major changes across backend, frontend, infrastructure:
- Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud)
- Admin panel: user management, pricing, settings
- Glossary system with CSV import/export
- Subscription and tier quota management
- Security hardening (rate limiting, API key auth, path traversal fixes)
- Docker compose for dev, prod, and IONOS deployment
- Alembic migrations for new tables
- Frontend: dashboard, pricing page, landing page, i18n (en/fr)
- Test suite and verification scripts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-25 15:01:47 +02:00

363 lines
16 KiB
Markdown

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