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>
16 KiB
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
- Navigation: Page accessible via
/auth/register - Formulaire: Email + mot de passe + confirmation avec validation en temps réel
- API Integration: Utilise
apiClientpour appelerPOST /api/v1/auth/register - Auto-login: Après inscription réussie → appel automatique à
POST /api/v1/auth/loginpour obtenir les tokens - Success: Tokens stockés dans localStorage → redirection vers
/dashboard - Error Handling: Erreurs API affichées inline (EMAIL_EXISTS, INVALID_EMAIL, etc.)
- Branding: Logo "Office Translator" avec icône
Languagesde Lucide (PAS "文A" ni "Translate Co.") - TanStack Query: Utilise
useMutationpour les requêtes (PAS useState+fetch directement) - UX: Loading state, validation visuelle inline, toggle password visibility, password strength indicator
- Colocation: Composants, hooks et types dans
frontend/src/app/auth/register/
Tasks / Subtasks
-
Task 1: Créer les types TypeScript (AC: #3, #4)
- 1.1 Créer
types.tsdansfrontend/src/app/auth/register/ - 1.2 Définir
RegisterRequest,RegisterResponse,RegisterFormData
- 1.1 Créer
-
Task 2: Créer le hook useRegister (AC: #3, #4, #5, #8)
- 2.1 Créer
useRegister.tsdansfrontend/src/app/auth/register/ - 2.2 Utiliser
useMutationpourPOST /api/v1/auth/register - 2.3 Après succès inscription → appeler
apiClient.post('/api/v1/auth/login', {email, password}) - 2.4 Stocker tokens dans localStorage:
token,refresh_token,user - 2.5 Rediriger vers
/dashboard(ou?redirect=param)
- 2.1 Créer
-
Task 3: Créer RegisterForm.tsx (AC: #2, #6, #7, #9)
- 3.1 Créer
RegisterForm.tsxdansfrontend/src/app/auth/register/ - 3.2 Champs: email, password, confirmPassword (optionnel: name)
- 3.3 Validation inline: email format, password ≥ 8 chars, confirmation match
- 3.4 Password strength indicator (barres visuelles)
- 3.5 Toggle visibility pour les deux champs password
- 3.6 Afficher erreur API inline (pas de state
errorséparé — utilisermutation.isError) - 3.7 Branding:
Languagesicône + "Office Translator"
- 3.1 Créer
-
Task 4: Mettre à jour page.tsx (AC: #1, #10)
- 4.1 Remplacer le contenu de
frontend/src/app/auth/register/page.tsx - 4.2 Utiliser
RegisterFormcolocated avecSuspense - 4.3 Conserver le fond dégradé (copier le pattern de
auth/login/page.tsx)
- 4.1 Remplacer le contenu de
-
Task 5: Vérifier le build et tester (AC: Tous)
- 5.1
npm run builddansfrontend/→ 0 erreurs TypeScript ✓ Compilé avec succès - 5.2 Tester inscription avec email valide → redirection /dashboard
- 5.3 Tester email déjà existant → message d'erreur "Un compte existe déjà..."
- 5.4 Tester validation inline (email invalide, password trop court, confirmation mismatch)
- 5.1
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:
// 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:
apiClient.post('/api/v1/auth/register', {email, password, name?})- Si succès →
apiClient.post<LoginResponse>('/api/v1/auth/login', {email, password}) - Stocker les tokens → rediriger
POST /api/v1/auth/login — retourne:
{
"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
// 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:
import type { LoginResponse } from '../login/types';
🔧 Hook useRegister à Créer
// 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(+ optionnelname) - 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.isErroretmutation.error?.message
Composants UI disponibles dans @/components/ui/:
button.tsx— variants:default,premium,outline; sizes:default,lginput.tsx— supporteleftIcon,rightIconprops (voir usage dans ancien register)label.tsxcard.tsx— variants:elevated,outline; prophover={false}(pattern login)badge.tsx
⚠️ Anti-pattern à éviter avec Input:
// ❌ 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 parapiClient - ❌ Tout inline (pas de colocation) → À extraire dans composants colocated
- ❌
useStateloading manual → À remplacer paruseMutation - ✅ 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
- NE PAS hardcoder l'URL:
→ utiliserfetch("http://localhost:8000/api/auth/register")apiClient.post() - NE PAS utiliser l'ancienne API:
→ utiliser/api/auth/register/api/v1/auth/register - NE PAS utiliser
useState+fetchdirect → utiliseruseMutation - NE PAS appeler login endpoint avec
fetchdirect → chaîner dansmutationFnavecapiClient - NE PAS oublier le Suspense —
useSearchParams()nécessite Suspense boundary - NE PAS créer de logique dupliquée — réutiliser
LoginResponsetype depuis../login/types - NE PAS créer un fichier
Page.tsx— app router exigepage.tsxen minuscules - NE PAS mettre
namecomme champ obligatoire — le backend l'accepte mais ne l'exige pas (vérifier: champ optionnel)
📊 Token Storage (même pattern que login)
// 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.tslit depuislocalStorage.getItem('token')(ligne 35)- Cohérent avec
useLogin.tsexistant - 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.tsdansapp/auth/login/ - Page wrapper:
Suspense+ fond dégradé avec effets flottants - Hook:
useMutation+onSuccessqui stocke dans localStorage + redirect - Branding:
Languagesicô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
- Build:
npm run builddansfrontend/→ 0 erreurs TypeScript/ESLint - Inscription succès: Email valide + password ≥ 8 chars → redirection /dashboard avec tokens
- Email existant: Réponse 400 EMAIL_EXISTS → message "Un compte existe déjà..."
- Email invalide: Format incorrect → validation inline immédiate
- Password mismatch: Confirmation ≠ password → erreur inline
- Password trop court: < 8 chars → erreur inline
- Token storage: Vérifier localStorage après succès (
token,refresh_token) - 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, utiliserRegisterFormcolocated
Fichiers à créer:
frontend/src/app/auth/register/RegisterForm.tsx— Composant formulairefrontend/src/app/auth/register/useRegister.ts— Hook mutation avec auto-loginfrontend/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éutiliseLoginResponsedepuis../login/types) - Créé
useRegister.ts:useMutationqui 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 étattouched, password strength indicator (4 barres), toggle visibility (Eye/EyeOff), branding "Office Translator" + icôneLanguages, erreur API viamutation.isError/mutation.error?.message. - Remplacé
page.tsx(605 lignes) par version simplifiée : fond dégradé conservé,Suspenseboundary pouruseSearchParams(),RegisterFormcolocated. - Build Next.js 16.0.6 : ✓ Compilé sans erreurs TypeScript. Route
/auth/registerpré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.tsfrontend/src/app/auth/login/useLogin.tsfrontend/src/app/auth/register/useRegister.tsfrontend/src/app/auth/register/RegisterForm.tsx
Verdict
✅ APPROVED — Tous les AC implémentés, issues corrigées.