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>
378 lines
19 KiB
Markdown
378 lines
19 KiB
Markdown
# Story 4.10: Dashboard - Glossaries Editor (Pro)
|
||
|
||
Status: done
|
||
|
||
## Story
|
||
|
||
En tant qu'**utilisateur Pro**,
|
||
Je veux **créer et éditer des glossaires dans mon dashboard**,
|
||
de sorte que **je puisse personnaliser les traductions LLM avec ma terminologie métier**.
|
||
|
||
## Acceptance Criteria
|
||
|
||
1. **Pro Access Only**: La page `/dashboard/glossaries` n'est accessible qu'aux utilisateurs avec `tier === "pro"`
|
||
2. **Free User Prompt**: Les utilisateurs Free voient un message d'upgrade avec CTA vers upgrade au lieu de l'éditeur
|
||
3. **List Glossaries**: Afficher la liste des glossaires existants avec: name, terms_count, created_at
|
||
4. **Create Glossary**: Au clic sur "Create New Glossary", ouvrir un dialog/form pour entrer le nom et les termes initiaux
|
||
5. **Edit Glossary**: Cliquer sur un glossaire pour l'éditer (modifier le nom, ajouter/supprimer/modifier des termes)
|
||
6. **Delete Glossary**: Supprimer un glossary avec confirmation dialog → DELETE `/api/v1/glossaries/{id}`
|
||
7. **Term Editor**: Interface pour gérer les paires source→target avec boutons Add/Remove
|
||
8. **CSV Import/Export**: Bouton pour exporter le glossaire en CSV et importer depuis un CSV
|
||
9. **Max Terms**: Si 500 termes atteint par glossaire, désactiver "Add Term" avec message
|
||
10. **Error Handling**: Afficher toast d'erreur si API retourne erreur (403, 404, etc.)
|
||
11. **Loading States**: Skeleton/loading pendant les appels API
|
||
12. **Colocation**: Tous les fichiers dans `frontend/src/app/dashboard/glossaries/`
|
||
|
||
## Tasks / Subtasks
|
||
|
||
- [x] **Task 1: Créer la structure de dossiers** (AC: #12)
|
||
- [x] 1.1 Créer `frontend/src/app/dashboard/glossaries/page.tsx` (fichier spécial: minuscules!)
|
||
- [x] 1.2 Créer `frontend/src/app/dashboard/glossaries/types.ts`
|
||
- [x] 1.3 Créer `frontend/src/app/dashboard/glossaries/useGlossaries.ts`
|
||
|
||
- [x] **Task 2: Définir les types TypeScript** (AC: #3)
|
||
- [x] 2.1 `GlossaryTerm` interface: id, source, target, created_at
|
||
- [x] 2.2 `Glossary` interface: id, name, terms (GlossaryTerm[]), created_at, updated_at
|
||
- [x] 2.3 `GlossaryListItem` interface: id, name, terms_count, created_at
|
||
- [x] 2.4 `GlossaryListResponse` interface: data (GlossaryListItem[]), meta (total, page, per_page, total_pages)
|
||
- [x] 2.5 `GlossaryDetailResponse` interface: data (Glossary), meta ({})
|
||
|
||
- [x] **Task 3: Créer useGlossaries.ts hook** (AC: #3, #4, #6, #10, #11)
|
||
- [x] 3.1 `useQuery` pour GET `/api/v1/glossaries` avec TanStack Query
|
||
- [x] 3.2 `createGlossary(name, terms[])`: mutation POST `/api/v1/glossaries`
|
||
- [x] 3.3 `updateGlossary(id, {name?, terms?})`: mutation PATCH `/api/v1/glossaries/{id}`
|
||
- [x] 3.4 `deleteGlossary(id)`: mutation DELETE `/api/v1/glossaries/{id}`
|
||
- [x] 3.5 Gestion erreurs: 403 → Pro requis, 404 → Non trouvé
|
||
- [x] 3.6 Loading states: isCreating, isUpdating, isDeleting
|
||
- [x] 3.7 Cache invalidation après create/update/delete
|
||
|
||
- [x] **Task 4: Créer la page principale** (AC: #1, #2, #3, #4, #5, #6, #11)
|
||
- [x] 4.1 Récupérer `user.tier` via `useUser()` hook existant
|
||
- [x] 4.2 Si `tier !== "pro"`: afficher `<ProUpgradePrompt />` (réutiliser depuis api-keys/)
|
||
- [x] 4.3 Si `tier === "pro"`: afficher `<GlossariesManager />`
|
||
- [x] 4.4 `GlossariesManager`: Grille/Liste de cartes avec glossaires
|
||
- [x] 4.5 Chaque carte: nom, nombre de termes, date création, boutons Edit/Delete
|
||
- [x] 4.6 "Create New Glossary" button → ouvre CreateGlossaryDialog
|
||
- [x] 4.7 Click sur carte → ouvre EditGlossaryDialog ou navigue vers édition
|
||
|
||
- [x] **Task 5: Créer les composants UI** (AC: #4, #5, #7, #9)
|
||
- [x] 5.1 `GlossaryCard.tsx`: Carte individuelle avec nom, terms_count, actions
|
||
- [x] 5.2 `CreateGlossaryDialog.tsx`: Dialog pour créer un nouveau glossaire
|
||
- [x] 5.3 `EditGlossaryDialog.tsx`: Dialog pour éditer nom + termes
|
||
- [x] 5.4 `TermEditor.tsx`: Éditeur de paires source→target avec add/remove
|
||
- [x] 5.5 `DeleteGlossaryDialog.tsx`: Confirmation dialog pour suppression
|
||
|
||
- [x] **Task 6: Implémenter CSV Import/Export** (AC: #8)
|
||
- [x] 6.1 `exportGlossaryToCsv(glossary)`: Génère fichier CSV et déclenche download
|
||
- [x] 6.2 `parseCsvToTerms(file)`: Parse fichier CSV uploadé en GlossaryTerm[]
|
||
- [x] 6.3 Bouton "Export CSV" dans EditGlossaryDialog
|
||
- [x] 6.4 Bouton "Import CSV" avec file input dans EditGlossaryDialog
|
||
- [x] 6.5 Format CSV: `source,target` (header optionnel)
|
||
|
||
- [x] **Task 7: Intégration et tests** (AC: Tous)
|
||
- [x] 7.1 `npm run build` → 0 erreurs TypeScript
|
||
- [x] 7.2 Tester création glossaire → apparaît dans la liste
|
||
- [x] 7.3 Tester édition termes → modifications sauvegardées
|
||
- [x] 7.4 Tester suppression → glossaire retiré de la liste
|
||
- [x] 7.5 Tester Free user → upgrade prompt affiché
|
||
- [x] 7.6 Tester CSV export → fichier téléchargé correct
|
||
- [x] 7.7 Tester CSV import → termes importés correct
|
||
|
||
## 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é |
|
||
| shadcn/ui | Card, Dialog, Button, Input, Badge, Toast, Separator |
|
||
| Lucide React | BookText, Plus, Trash2, ArrowRight, Download, Upload, Save |
|
||
|
||
### 📁 Structure Cible (Colocation Pattern)
|
||
|
||
```
|
||
frontend/src/app/dashboard/glossaries/
|
||
├── page.tsx # Page principale
|
||
├── types.ts # TypeScript interfaces
|
||
├── useGlossaries.ts # TanStack Query hook
|
||
├── GlossaryCard.tsx # Carte individuelle
|
||
├── CreateGlossaryDialog.tsx # Dialog création
|
||
├── EditGlossaryDialog.tsx # Dialog édition complète
|
||
├── TermEditor.tsx # Éditeur de paires source→target
|
||
├── DeleteGlossaryDialog.tsx # Confirmation suppression
|
||
├── csvUtils.ts # Fonctions CSV import/export
|
||
└── ProUpgradePrompt.tsx # Copié/adapté depuis api-keys/
|
||
```
|
||
|
||
**⚠️ Règle absolue (architecture.md):**
|
||
```
|
||
🚨 FICHIERS SPÉCIAUX: page.tsx → TOUJOURS minuscules
|
||
🚨 COLOCATION: Components/hooks/types dans le dossier de leur page
|
||
```
|
||
|
||
### 🔗 API Endpoints Backend (Déjà implémentés)
|
||
|
||
| Endpoint | Méthode | Request | Response |
|
||
|----------|---------|---------|----------|
|
||
| `/api/v1/glossaries` | GET | `?page=1&per_page=50` | 200: `{data: [GlossaryListItem...], meta: {total, page, per_page, total_pages}}` |
|
||
| `/api/v1/glossaries` | POST | `{name, terms: [{source, target}]}` | 201: `{data: Glossary, meta: {}}` |
|
||
| `/api/v1/glossaries/{id}` | GET | — | 200: `{data: Glossary, meta: {}}` |
|
||
| `/api/v1/glossaries/{id}` | PATCH | `{name?, terms?}` | 200: `{data: Glossary, meta: {}}` |
|
||
| `/api/v1/glossaries/{id}` | DELETE | — | 204: No Content |
|
||
|
||
**Codes erreur:**
|
||
- 401: Token invalide → rediriger vers login
|
||
- 403: `PRO_FEATURE_REQUIRED` → afficher upgrade prompt
|
||
- 404: `GLOSSARY_NOT_FOUND` → glossaire non trouvé
|
||
- 400: `TERMS_LIMIT_EXCEEDED` → max 500 termes
|
||
- 400: `INVALID_GLOSSARY_ID` → UUID invalide
|
||
|
||
### 📊 API Response Examples
|
||
|
||
**GET /api/v1/glossaries (200):**
|
||
```json
|
||
{
|
||
"data": [
|
||
{
|
||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"name": "Glossaire Technique FR-EN",
|
||
"terms_count": 25,
|
||
"created_at": "2026-02-19T10:30:00Z"
|
||
}
|
||
],
|
||
"meta": {
|
||
"total": 3,
|
||
"page": 1,
|
||
"per_page": 50,
|
||
"total_pages": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
**GET /api/v1/glossaries/{id} (200):**
|
||
```json
|
||
{
|
||
"data": {
|
||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"name": "Glossaire Technique FR-EN",
|
||
"terms": [
|
||
{
|
||
"id": "term-uuid-1",
|
||
"source": "serveur",
|
||
"target": "server",
|
||
"created_at": "2026-02-19T10:30:00Z"
|
||
},
|
||
{
|
||
"id": "term-uuid-2",
|
||
"source": "base de données",
|
||
"target": "database",
|
||
"created_at": "2026-02-19T10:31:00Z"
|
||
}
|
||
],
|
||
"created_at": "2026-02-19T10:30:00Z",
|
||
"updated_at": "2026-02-20T14:00:00Z"
|
||
},
|
||
"meta": {}
|
||
}
|
||
```
|
||
|
||
**POST /api/v1/glossaries (201):**
|
||
```json
|
||
{
|
||
"data": {
|
||
"id": "550e8400-e29b-41d4-a716-446655440001",
|
||
"name": "Mon Nouveau Glossaire",
|
||
"terms": [
|
||
{"id": "term-uuid-3", "source": "bonjour", "target": "hello", "created_at": "2026-02-23T14:30:00Z"}
|
||
],
|
||
"created_at": "2026-02-23T14:30:00Z",
|
||
"updated_at": "2026-02-23T14:30:00Z"
|
||
},
|
||
"meta": {}
|
||
}
|
||
```
|
||
|
||
### 🎨 Patterns UI à Réutiliser
|
||
|
||
**Depuis `glossary-context-card.tsx` (office-translator-landing-page):**
|
||
- Layout grille pour TermEditor: `grid-cols-[1fr_32px_1fr_36px]`
|
||
- ArrowRight icon entre source et target
|
||
- Input avec `font-mono text-xs`
|
||
- Button Trash2 avec opacity-0 group-hover:opacity-100
|
||
- Add Term button avec `variant="outline" border-dashed`
|
||
|
||
**Depuis `api-keys/page.tsx` (frontend):**
|
||
- Structure page: titre + description en haut
|
||
- Card avec header icône + titre + description
|
||
- ProUpgradePrompt component
|
||
- Loading skeleton avec animate-spin
|
||
- useToast pour les notifications
|
||
|
||
**Depuis `DashboardSidebar.tsx`:**
|
||
- Badge Pro avec style: `border border-accent/20 bg-accent/10 text-accent`
|
||
|
||
### 🔄 State Flow
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ page.tsx │
|
||
│ ┌─────────────────────────────────────────────────────┐│
|
||
│ │ useUser() → tier check ││
|
||
│ │ ├─ tier !== "pro" → <ProUpgradePrompt /> ││
|
||
│ │ └─ tier === "pro" → <GlossariesManager /> ││
|
||
│ └─────────────────────────────────────────────────────┘│
|
||
└─────────────────────────────────────────────────────────┘
|
||
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ GlossariesManager │
|
||
│ ┌─────────────────────────────────────────────────────┐│
|
||
│ │ useGlossaries() hook ││
|
||
│ │ ├─ glossaries: GlossaryListItem[] ││
|
||
│ │ ├─ isLoading, isCreating, isUpdating, isDeleting ││
|
||
│ │ ├─ createGlossary(name, terms) ││
|
||
│ │ ├─ updateGlossary(id, data) ││
|
||
│ │ └─ deleteGlossary(id) ││
|
||
│ └─────────────────────────────────────────────────────┘│
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────┐│
|
||
│ │ GlossaryGrid ││
|
||
│ │ └─ GlossaryCard × N ││
|
||
│ │ ├─ name, terms_count, created_at ││
|
||
│ │ └─ Actions: Edit, Delete ││
|
||
│ └─────────────────────────────────────────────────────┘│
|
||
│ │
|
||
│ [Create New Glossary] → opens CreateGlossaryDialog │
|
||
│ [Edit] → opens EditGlossaryDialog │
|
||
│ [Delete] → opens DeleteGlossaryDialog │
|
||
└─────────────────────────────────────────────────────────┘
|
||
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ EditGlossaryDialog │
|
||
│ ┌─────────────────────────────────────────────────────┐│
|
||
│ │ Name Input ││
|
||
│ └─────────────────────────────────────────────────────┘│
|
||
│ ┌─────────────────────────────────────────────────────┐│
|
||
│ │ TermEditor ││
|
||
│ │ ├─ Row: Source Input → Target Input × N ││
|
||
│ │ └─ [Add Term] button ││
|
||
│ └─────────────────────────────────────────────────────┘│
|
||
│ ┌─────────────────────────────────────────────────────┐│
|
||
│ │ CSV Actions ││
|
||
│ │ ├─ [Export CSV] button ││
|
||
│ │ └─ [Import CSV] file input ││
|
||
│ └─────────────────────────────────────────────────────┘│
|
||
│ [Save Changes] → PATCH /api/v1/glossaries/{id} │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### ⚠️ Points d'Attention Critiques
|
||
|
||
1. **Max 500 termes par glossaire**: Le backend renvoie 400 `TERMS_LIMIT_EXCEEDED`. Désactiver le bouton "Add Term" côté client quand on atteint 500.
|
||
|
||
2. **PATCH vs PUT**: Utiliser PATCH pour les updates (partiels). Le backend accepte `{name}` seul ou `{terms}` seul ou les deux.
|
||
|
||
3. **Cache invalidation**: Après create/update/delete, invalider le cache TanStack Query:
|
||
```typescript
|
||
queryClient.invalidateQueries({ queryKey: ['glossaries'] });
|
||
```
|
||
|
||
4. **CSV Format**:
|
||
- Export: Télécharger avec `text/csv` Content-Type et filename `{glossary_name}.csv`
|
||
- Import: Accepter header optionnel `source,target` ou données directes
|
||
|
||
5. **Optimistic Updates**: Optionnel - pour UX fluide, on peut mettre à jour la liste immédiatement après création/update.
|
||
|
||
6. **UUID validation côté client**: Le backend valide les UUID mais on peut pré-valider pour UX.
|
||
|
||
### 📋 CSV Import/Export Format
|
||
|
||
**Export (glossary_name.csv):**
|
||
```csv
|
||
source,target
|
||
serveur,server
|
||
base de données,database
|
||
client,client
|
||
```
|
||
|
||
**Import (accepte avec ou sans header):**
|
||
```csv
|
||
serveur,server
|
||
base de données,database
|
||
```
|
||
|
||
### 📋 Checklist de Validation Avant Dev
|
||
|
||
- [ ] Backend API `/api/v1/glossaries` est fonctionnel (testé via curl/Postman)
|
||
- [ ] useUser() hook retourne bien `tier` field
|
||
- [ ] shadcn/ui components installés: Card, Dialog, Button, Input, Badge, Toast, Separator
|
||
- [ ] apiClient.ts gère correctement les erreurs 403/404
|
||
- [ ] Navigation sidebar affiche déjà "Glossaries" pour users Pro
|
||
|
||
### References
|
||
|
||
- [Source: _bmad-output/planning-artifacts/epics.md#Story-4.10] — Story requirements
|
||
- [Source: _bmad-output/planning-artifacts/architecture.md#Frontend-Architecture] — TanStack Query + colocation
|
||
- [Source: _bmad-output/planning-artifacts/architecture.md#API-Response-Formats] — Format réponse API
|
||
- [Source: routes/glossary_routes.py] — Backend API implementation (GET, POST, PATCH, DELETE)
|
||
- [Source: schemas/glossary_schemas.py] — Pydantic schemas for validation
|
||
- [Source: office-translator-landing-page/components/glossary-context-card.tsx] — UI patterns TermEditor à réutiliser
|
||
- [Source: frontend/src/app/dashboard/api-keys/page.tsx] — Pattern page dashboard Pro
|
||
- [Source: frontend/src/app/dashboard/api-keys/useApiKeys.ts] — Pattern TanStack Query hook
|
||
- [Source: frontend/src/app/dashboard/useUser.ts] — Hook existant pour tier check
|
||
- [Source: frontend/src/lib/apiClient.ts] — API client existant
|
||
- [Source: frontend/src/app/dashboard/constants.ts] — Navigation items (Glossaries déjà présent pour Pro)
|
||
- [Source: _bmad-output/implementation-artifacts/4-9-dashboard-api-keys-management-pro.md] — Story précédente, patterns à réutiliser
|
||
|
||
## Dev Agent Record
|
||
|
||
### Agent Model Used
|
||
|
||
zai-anthropic/glm-5
|
||
|
||
### Debug Log References
|
||
|
||
### Completion Notes List
|
||
|
||
- 2026-02-23: Completed full implementation of Glossaries Editor dashboard page
|
||
- Created all TypeScript types matching backend API schemas
|
||
- Implemented TanStack Query hooks for CRUD operations with cache invalidation
|
||
- Built complete UI with Pro upgrade prompt for free users
|
||
- Implemented TermEditor with max 500 terms limit enforcement
|
||
- Added CSV import/export functionality with proper parsing
|
||
- Fixed TypeScript errors in api-keys/useApiKeys.ts and translate/page.tsx
|
||
- Build passes with 0 errors
|
||
- 2026-02-23: Code review fixes applied
|
||
- [FIX] Added proper API error code parsing (TERMS_LIMIT_EXCEEDED, PRO_FEATURE_REQUIRED, etc.)
|
||
- [FIX] Added CSV import validation for max 500 terms limit with user feedback
|
||
- [FIX] Added pagination support (page, perPage parameters)
|
||
- [FIX] Added automatic 401 redirect to login page
|
||
- [FIX] Improved React key stability in TermEditor using content-based keys
|
||
- [FIX] Added error handling for CSV file read failures
|
||
|
||
### File List
|
||
|
||
**Created files:**
|
||
- frontend/src/app/dashboard/glossaries/page.tsx
|
||
- frontend/src/app/dashboard/glossaries/types.ts
|
||
- frontend/src/app/dashboard/glossaries/useGlossaries.ts
|
||
- frontend/src/app/dashboard/glossaries/GlossaryCard.tsx
|
||
- frontend/src/app/dashboard/glossaries/CreateGlossaryDialog.tsx
|
||
- frontend/src/app/dashboard/glossaries/EditGlossaryDialog.tsx
|
||
- frontend/src/app/dashboard/glossaries/TermEditor.tsx
|
||
- frontend/src/app/dashboard/glossaries/DeleteGlossaryDialog.tsx
|
||
- frontend/src/app/dashboard/glossaries/csvUtils.ts
|
||
- frontend/src/app/dashboard/glossaries/ProUpgradePrompt.tsx
|
||
|
||
**Modified files:**
|
||
- frontend/src/app/dashboard/api-keys/useApiKeys.ts (fixed TypeScript error)
|
||
- frontend/src/app/dashboard/translate/page.tsx (fixed TypeScript error)
|
||
|
||
## Change Log
|
||
|
||
- 2026-02-23: Story created - ready for development
|
||
- 2026-02-23: Implementation complete - all ACs satisfied, ready for review
|
||
- 2026-02-23: Code review completed - 5 issues fixed (2 HIGH, 3 MEDIUM), story marked done
|