# 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 `` (réutiliser depuis api-keys/)
- [x] 4.3 Si `tier === "pro"`: afficher ``
- [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" → ││
│ │ └─ tier === "pro" → ││
│ └─────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 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