Files
office_translator/_bmad-output/implementation-artifacts/4-10-dashboard-glossaries-editor-pro.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

378 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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