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

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

  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

  • Task 1: Créer les types TypeScript (AC: #3, #4)

    • 1.1 Créer types.ts dans frontend/src/app/auth/register/
    • 1.2 Définir RegisterRequest, RegisterResponse, RegisterFormData
  • Task 2: Créer le hook useRegister (AC: #3, #4, #5, #8)

    • 2.1 Créer useRegister.ts dans frontend/src/app/auth/register/
    • 2.2 Utiliser useMutation pour POST /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)
  • Task 3: Créer RegisterForm.tsx (AC: #2, #6, #7, #9)

    • 3.1 Créer RegisterForm.tsx dans frontend/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 error séparé — utiliser mutation.isError)
    • 3.7 Branding: Languages icône + "Office Translator"
  • 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 RegisterForm colocated avec Suspense
    • 4.3 Conserver le fond dégradé (copier le pattern de auth/login/page.tsx)
  • Task 5: Vérifier le build et tester (AC: Tous)

    • 5.1 npm run build dans frontend/ → 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)

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:

  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:

{
  "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 (+ 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:

// ❌ 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 SuspenseuseSearchParams() 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)

// 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.tsRegisterRequest, 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.