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>
This commit is contained in:
362
_bmad-output/implementation-artifacts/4-4-page-register.md
Normal file
362
_bmad-output/implementation-artifacts/4-4-page-register.md
Normal file
@@ -0,0 +1,362 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user