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>
552 lines
19 KiB
Markdown
552 lines
19 KiB
Markdown
# Story 4.6: Page Translation - Upload
|
|
|
|
Status: done
|
|
|
|
## Story
|
|
|
|
En tant qu'**utilisateur**,
|
|
Je veux **uploader un document via drag-and-drop**,
|
|
de sorte que **je puisse démarrer une traduction**.
|
|
|
|
## Acceptance Criteria
|
|
|
|
1. **Route**: `/dashboard/translate` affiche la page d'upload
|
|
2. **Drop Zone**: Drag-and-drop fonctionnel + click to browse
|
|
3. **Validation Format**: Seuls .xlsx, .docx, .pptx acceptés (magic bytes check si possible, sinon extension)
|
|
4. **Erreur Format**: Message "Format non supporté. Formats acceptés : .xlsx, .docx, .pptx"
|
|
5. **File Preview**: Affiche nom du fichier + taille + icône selon type
|
|
6. **Remove File**: Bouton X pour supprimer le fichier et reset l'état
|
|
7. **Max Size**: 50 MB max → erreur "Fichier trop volumineux (max 50 MB)"
|
|
8. **Branding**: Utiliser icône `Languages` de Lucide + "Office Translator" (PAS "Translate Co." ni "文A")
|
|
9. **Merge Directive**: UI depuis `office-translator-landing-page/components/translation-card.tsx`, logique API depuis `frontend/src/components/file-uploader.tsx`
|
|
10. **Colocation**: Composants/hooks/types dans `frontend/src/app/dashboard/translate/`
|
|
|
|
## Tasks / Subtasks
|
|
|
|
- [x] **Task 1: Créer la structure de la page** (AC: #1, #10)
|
|
- [x] 1.1 Créer `frontend/src/app/dashboard/translate/page.tsx` — Client Component
|
|
- [x] 1.2 Créer `frontend/src/app/dashboard/translate/types.ts` — Types FileUpload
|
|
- [x] 1.3 Layout hérite de `frontend/src/app/dashboard/layout.tsx` (Story 4.5)
|
|
|
|
- [x] **Task 2: Créer FileDropZone.tsx** (AC: #2, #3, #4, #7)
|
|
- [x] 2.1 Créer `frontend/src/app/dashboard/translate/FileDropZone.tsx` (colocated)
|
|
- [x] 2.2 Implémenter drag-over state avec highlight visuel
|
|
- [x] 2.3 Implémenter file input hidden + click to browse
|
|
- [x] 2.4 Valider extension (.xlsx, .docx, .pptx)
|
|
- [x] 2.5 Valider taille max 50 MB
|
|
- [x] 2.6 Afficher erreur format/taille en dessous de la zone
|
|
- [x] 2.7 Copier styles depuis `translation-card.tsx` (drop zone section)
|
|
|
|
- [x] **Task 3: Créer FilePreview.tsx** (AC: #5, #6)
|
|
- [x] 3.1 Créer `frontend/src/app/dashboard/translate/FilePreview.tsx` (colocated)
|
|
- [x] 3.2 Afficher icône selon type (FileSpreadsheet/FileText/Presentation)
|
|
- [x] 3.3 Afficher nom du fichier (truncate si trop long)
|
|
- [x] 3.4 Afficher taille formatée (KB/MB)
|
|
- [x] 3.5 Bouton X pour remove → reset state
|
|
- [x] 3.6 Copier styles depuis `translation-card.tsx` (file preview section)
|
|
|
|
- [x] **Task 4: Créer useFileUpload.ts hook** (AC: #2, #3, #4, #7)
|
|
- [x] 4.1 Créer `frontend/src/app/dashboard/translate/useFileUpload.ts` (colocated)
|
|
- [x] 4.2 State: file, error, isDragOver
|
|
- [x] 4.3 Handlers: handleDrop, handleDragOver, handleDragLeave, handleFileSelect, removeFile
|
|
- [x] 4.4 Validation function: validateFile(file) → error | null
|
|
- [x] 4.5 Return: { file, error, isDragOver, handlers... }
|
|
|
|
- [x] **Task 5: Créer la page translate/page.tsx** (AC: #1, #8)
|
|
- [x] 5.1 Page heading: "Translate Document"
|
|
- [x] 5.2 Intégrer FileDropZone + FilePreview
|
|
- [x] 5.3 Afficher erreur si validation échoue
|
|
- [x] 5.4 Message helper: "Supported formats: Excel (.xlsx), Word (.docx), PowerPoint (.pptx)"
|
|
- [x] 5.5 Note privacy: "Files are automatically deleted after 60 minutes"
|
|
|
|
- [x] **Task 6: Vérifier le build** (AC: Tous)
|
|
- [x] 6.1 `npm run build` → 0 erreurs TypeScript
|
|
- [x] 6.2 Tester drag-and-drop
|
|
- [x] 6.3 Tester click to browse
|
|
- [x] 6.4 Tester validation format (essayer .pdf → erreur)
|
|
- [x] 6.5 Tester validation taille (fichier > 50 MB → erreur)
|
|
- [x] 6.6 Tester remove file → state reset
|
|
|
|
## 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, Card, Progress |
|
|
| react-dropzone | ⚠️ **DÉJÀ INSTALLÉ** dans frontend/ |
|
|
|
|
### 📁 Structure Cible (Colocation Pattern)
|
|
|
|
```
|
|
frontend/src/app/dashboard/translate/
|
|
├── page.tsx # 🆕 Page principale
|
|
├── FileDropZone.tsx # 🆕 Zone drag-and-drop
|
|
├── FilePreview.tsx # 🆕 Preview fichier uploadé
|
|
├── useFileUpload.ts # 🆕 Hook validation + state
|
|
└── types.ts # 🆕 Types FileUpload
|
|
```
|
|
|
|
**⚠️ Règle absolue (architecture.md):**
|
|
```
|
|
🚨 FICHIERS SPÉCIAUX: page.tsx, layout.tsx → TOUJOURS minuscules
|
|
🚨 COLOCATION: Components/hooks/types dans le dossier de leur page
|
|
```
|
|
|
|
### 🔗 MERGE DIRECTIVE — Sources à Fusionner
|
|
|
|
**UI Components depuis `office-translator-landing-page/`:**
|
|
|
|
| Source | Fichier | Usage |
|
|
|--------|---------|-------|
|
|
| Drop Zone UI | `office-translator-landing-page/components/translation-card.tsx` | Structure, styles, animation drag-over |
|
|
| File Preview | `translation-card.tsx` lignes 154-198 | Preview avec icône, nom, taille, bouton X |
|
|
| Error styling | `translation-card.tsx` | Style erreur format non supporté |
|
|
|
|
**Logique Validation depuis `frontend/`:**
|
|
|
|
| Source | Fichier | Usage |
|
|
|--------|---------|-------|
|
|
| Dropzone logic | `frontend/src/components/file-uploader.tsx` lignes 217-228 | react-dropzone config, acceptedTypes |
|
|
| File validation | `file-uploader.tsx` | Validation format, taille |
|
|
| FilePreview component | `file-uploader.tsx` lignes 56-184 | Preview avancé (à simplifier) |
|
|
|
|
### ⚠️ CRITIQUE: Formats Acceptés
|
|
|
|
**MIME Types (depuis file-uploader.tsx):**
|
|
```typescript
|
|
const ACCEPTED_TYPES = {
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"],
|
|
"application/vnd.ms-excel": [".xls"], // Optionnel: support ancien format
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": [".docx"],
|
|
"application/msword": [".doc"], // Optionnel
|
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation": [".pptx"],
|
|
"application/vnd.ms-powerpoint": [".ppt"], // Optionnel
|
|
};
|
|
```
|
|
|
|
**⚠️ PRD FR50:** Seuls .xlsx, .docx, .pptx sont requis. Les anciens formats (.xls, .doc, .ppt) sont optionnels.
|
|
|
|
### 🔧 Types TypeScript à Créer
|
|
|
|
```typescript
|
|
// frontend/src/app/dashboard/translate/types.ts
|
|
|
|
export type SupportedFormat = 'xlsx' | 'docx' | 'pptx';
|
|
|
|
export interface FileUploadState {
|
|
file: File | null;
|
|
error: string | null;
|
|
isDragOver: boolean;
|
|
}
|
|
|
|
export interface FileUploadActions {
|
|
handleDrop: (e: React.DragEvent) => void;
|
|
handleDragOver: (e: React.DragEvent) => void;
|
|
handleDragLeave: (e: React.DragEvent) => void;
|
|
handleFileSelect: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
removeFile: () => void;
|
|
}
|
|
|
|
export interface UseFileUploadReturn extends FileUploadState, FileUploadActions {}
|
|
```
|
|
|
|
### 🔧 useFileUpload.ts — Hook à Créer
|
|
|
|
```typescript
|
|
// frontend/src/app/dashboard/translate/useFileUpload.ts
|
|
'use client';
|
|
|
|
import { useState, useCallback } from 'react';
|
|
import type { UseFileUploadReturn } from './types';
|
|
|
|
const ACCEPTED_EXTENSIONS = ['xlsx', 'docx', 'pptx'];
|
|
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB
|
|
|
|
export function useFileUpload(): UseFileUploadReturn {
|
|
const [file, setFile] = useState<File | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
|
|
const validateFile = useCallback((file: File): string | null => {
|
|
const ext = file.name.split('.').pop()?.toLowerCase();
|
|
|
|
if (!ext || !ACCEPTED_EXTENSIONS.includes(ext)) {
|
|
return 'Format non supporté. Formats acceptés : .xlsx, .docx, .pptx';
|
|
}
|
|
|
|
if (file.size > MAX_FILE_SIZE) {
|
|
return 'Fichier trop volumineux (max 50 MB)';
|
|
}
|
|
|
|
return null;
|
|
}, []);
|
|
|
|
const handleDrop = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setIsDragOver(false);
|
|
|
|
const droppedFile = e.dataTransfer.files[0];
|
|
if (droppedFile) {
|
|
const validationError = validateFile(droppedFile);
|
|
if (validationError) {
|
|
setError(validationError);
|
|
setFile(null);
|
|
} else {
|
|
setFile(droppedFile);
|
|
setError(null);
|
|
}
|
|
}
|
|
}, [validateFile]);
|
|
|
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setIsDragOver(true);
|
|
}, []);
|
|
|
|
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setIsDragOver(false);
|
|
}, []);
|
|
|
|
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const selected = e.target.files?.[0];
|
|
if (selected) {
|
|
const validationError = validateFile(selected);
|
|
if (validationError) {
|
|
setError(validationError);
|
|
setFile(null);
|
|
} else {
|
|
setFile(selected);
|
|
setError(null);
|
|
}
|
|
}
|
|
}, [validateFile]);
|
|
|
|
const removeFile = useCallback(() => {
|
|
setFile(null);
|
|
setError(null);
|
|
}, []);
|
|
|
|
return {
|
|
file,
|
|
error,
|
|
isDragOver,
|
|
handleDrop,
|
|
handleDragOver,
|
|
handleDragLeave,
|
|
handleFileSelect,
|
|
removeFile,
|
|
};
|
|
}
|
|
```
|
|
|
|
### 🔧 FileDropZone.tsx — Pattern à Suivre
|
|
|
|
**Copier depuis:** `office-translator-landing-page/components/translation-card.tsx` lignes 141-199
|
|
|
|
```typescript
|
|
// frontend/src/app/dashboard/translate/FileDropZone.tsx
|
|
'use client';
|
|
|
|
import { useCallback, useRef } from 'react';
|
|
import { Upload, FileCheck, X } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { cn } from '@/lib/utils';
|
|
import type { UseFileUploadReturn } from './types';
|
|
|
|
interface FileDropZoneProps {
|
|
upload: UseFileUploadReturn;
|
|
}
|
|
|
|
export function FileDropZone({ upload }: FileDropZoneProps) {
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
const handleClick = () => {
|
|
inputRef.current?.click();
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"relative flex flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed px-6 py-10 transition-colors cursor-pointer",
|
|
upload.isDragOver
|
|
? "border-accent bg-accent/5"
|
|
: upload.file
|
|
? "border-success/40 bg-success/5"
|
|
: "border-border bg-muted/30 hover:border-muted-foreground/30"
|
|
)}
|
|
onDragOver={upload.handleDragOver}
|
|
onDragLeave={upload.handleDragLeave}
|
|
onDrop={upload.handleDrop}
|
|
onClick={handleClick}
|
|
>
|
|
{/* ... contenu similaire à translation-card.tsx */}
|
|
|
|
<input
|
|
ref={inputRef}
|
|
type="file"
|
|
accept=".xlsx,.docx,.pptx"
|
|
className="hidden"
|
|
onChange={upload.handleFileSelect}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 🔧 FilePreview.tsx — Pattern à Suivre
|
|
|
|
**Copier depuis:** `office-translator-landing-page/components/translation-card.tsx` lignes 154-176
|
|
|
|
```typescript
|
|
// frontend/src/app/dashboard/translate/FilePreview.tsx
|
|
'use client';
|
|
|
|
import { FileSpreadsheet, FileText, Presentation, X } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
const FILE_ICONS: Record<string, React.ElementType> = {
|
|
xlsx: FileSpreadsheet,
|
|
docx: FileText,
|
|
pptx: Presentation,
|
|
};
|
|
|
|
interface FilePreviewProps {
|
|
file: File;
|
|
onRemove: () => void;
|
|
}
|
|
|
|
export function FilePreview({ file, onRemove }: FilePreviewProps) {
|
|
const ext = file.name.split('.').pop()?.toLowerCase() || '';
|
|
const FileIcon = FILE_ICONS[ext] || FileText;
|
|
|
|
const formatSize = (bytes: number) => {
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
};
|
|
|
|
return (
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex size-10 items-center justify-center rounded-lg bg-secondary">
|
|
<FileIcon className="size-5 text-foreground" />
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<span className="text-sm font-medium text-foreground truncate max-w-xs">
|
|
{file.name}
|
|
</span>
|
|
<span className="text-xs text-muted-foreground">
|
|
{formatSize(file.size)} · .{ext}
|
|
</span>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
className="ml-2 text-muted-foreground hover:text-foreground"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onRemove();
|
|
}}
|
|
>
|
|
<X className="size-4" />
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 🔧 page.tsx — Structure de la Page
|
|
|
|
```typescript
|
|
// frontend/src/app/dashboard/translate/page.tsx
|
|
'use client';
|
|
|
|
import { FileText, ShieldCheck, Clock } from 'lucide-react';
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
|
import { FileDropZone } from './FileDropZone';
|
|
import { FilePreview } from './FilePreview';
|
|
import { useFileUpload } from './useFileUpload';
|
|
|
|
export default function TranslatePage() {
|
|
const upload = useFileUpload();
|
|
|
|
return (
|
|
<div className="mx-auto max-w-xl px-4 py-6 lg:px-8">
|
|
<Card className="border-border/70 shadow-lg">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<FileText className="size-5" />
|
|
Translate Document
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Upload an Excel, Word, or PowerPoint file to translate
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col gap-4">
|
|
{/* Drop Zone / Preview */}
|
|
{upload.file ? (
|
|
<FilePreview file={upload.file} onRemove={upload.removeFile} />
|
|
) : (
|
|
<FileDropZone upload={upload} />
|
|
)}
|
|
|
|
{/* Error Message */}
|
|
{upload.error && (
|
|
<p className="text-sm text-destructive">{upload.error}</p>
|
|
)}
|
|
|
|
{/* Helper Text */}
|
|
<p className="text-xs text-muted-foreground">
|
|
Supported formats: Excel (.xlsx), Word (.docx), PowerPoint (.pptx)
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Privacy Badge */}
|
|
<div className="flex items-center justify-center gap-4 mt-4 text-xs text-muted-foreground">
|
|
<div className="flex items-center gap-1.5">
|
|
<ShieldCheck className="size-3.5" />
|
|
<span>Zero Data Retention</span>
|
|
</div>
|
|
<div className="h-3 w-px bg-border" />
|
|
<div className="flex items-center gap-1.5">
|
|
<Clock className="size-3.5" />
|
|
<span>Files deleted after 60 min</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 🚨 Anti-Patterns à Éviter
|
|
|
|
1. **NE PAS utiliser react-dropzone** pour cette story — Utiliser les handlers natifs drag events (plus simple, moins de dépendances)
|
|
2. **NE PAS hardcoder** le message d'erreur → utiliser une constante
|
|
3. **NE PAS oublier** le `e.stopPropagation()` sur le bouton remove
|
|
4. **NE PAS créer** de fichiers globaux — tout est colocated
|
|
5. **NE PAS utiliser** "Translate Co." / "文A" → utiliser "Office Translator"
|
|
6. **NE PAS afficher** le preview si error est présent
|
|
|
|
### 🔗 Cohérence Cross-Story (Context Continuité)
|
|
|
|
**Story 4.5 (Dashboard Layout) a établi:**
|
|
- Layout dashboard avec sidebar
|
|
- Route `/dashboard/*`
|
|
- Auth check via `useUser()` hook
|
|
- Pattern colocation
|
|
|
|
**Cette story (4.6) DOIT:**
|
|
- Hériter du layout dashboard existant
|
|
- Suivre le même pattern colocation
|
|
- Utiliser les mêmes composants UI shadcn/ui
|
|
|
|
**Stories suivantes (4.7, 4.8) utiliseront:**
|
|
- Le fichier uploadé depuis cette page
|
|
- L'état de traduction persisté
|
|
|
|
### 📊 File Size Format
|
|
|
|
```typescript
|
|
const formatFileSize = (bytes: number): string => {
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
};
|
|
```
|
|
|
|
### 🔍 Composants Réutilisables Existants
|
|
|
|
| Composant | Chemin | Usage |
|
|
|-----------|--------|-------|
|
|
| `Button` | `@/components/ui/button` | variant="ghost", size="icon-sm" |
|
|
| `Card` | `@/components/ui/card` | Card, CardHeader, CardTitle, CardDescription, CardContent |
|
|
| `Lucide icons` | `lucide-react` | Upload, FileText, FileSpreadsheet, Presentation, X, ShieldCheck, Clock |
|
|
|
|
### 🧪 Tests à Effectuer
|
|
|
|
1. **Build**: `npm run build` dans `frontend/` → 0 erreurs TypeScript/ESLint
|
|
2. **Drag-and-drop**: Déposer un fichier .xlsx → preview s'affiche
|
|
3. **Click to browse**: Cliquer → file dialog s'ouvre
|
|
4. **Validation format**: Déposer un .pdf → erreur "Format non supporté"
|
|
5. **Validation taille**: Déposer un fichier > 50 MB → erreur "Fichier trop volumineux"
|
|
6. **Remove file**: Cliquer X → file reset, error cleared
|
|
7. **Navigation**: Sidebar link "Translate" → `/dashboard/translate`
|
|
|
|
### Project Structure Notes
|
|
|
|
**Fichiers à créer:**
|
|
- `frontend/src/app/dashboard/translate/page.tsx` — Page principale
|
|
- `frontend/src/app/dashboard/translate/FileDropZone.tsx` — Zone drag-and-drop
|
|
- `frontend/src/app/dashboard/translate/FilePreview.tsx` — Preview fichier
|
|
- `frontend/src/app/dashboard/translate/useFileUpload.ts` — Hook state + validation
|
|
- `frontend/src/app/dashboard/translate/types.ts` — Types TypeScript
|
|
|
|
**⚠️ Pas de fichiers globaux à créer** — tout est colocated.
|
|
|
|
**⚠️ Pas besoin de créer un layout** — hérite de `frontend/src/app/dashboard/layout.tsx` (Story 4.5).
|
|
|
|
### References
|
|
|
|
- [Source: _bmad-output/planning-artifacts/epics.md#Story-4.6] — 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: office-translator-landing-page/components/translation-card.tsx] — UI drop zone à copier
|
|
- [Source: frontend/src/components/file-uploader.tsx] — Logique validation, react-dropzone config
|
|
- [Source: frontend/src/lib/apiClient.ts] — Client API centralisé (pas utilisé dans cette story)
|
|
- [Source: _bmad-output/implementation-artifacts/4-5-dashboard-layout.md] — Pattern colocation, context dashboard
|
|
|
|
## Dev Agent Record
|
|
|
|
### Agent Model Used
|
|
|
|
Claude Sonnet 4 (claude-sonnet-4@20250514)
|
|
|
|
### Debug Log References
|
|
|
|
N/A
|
|
|
|
### Completion Notes List
|
|
|
|
- ✅ Créé types.ts avec FileUploadState, FileUploadActions, UseFileUploadReturn
|
|
- ✅ Créé useFileUpload.ts hook avec validation format (.xlsx, .docx, .pptx) et taille (50 MB max)
|
|
- ✅ Créé FileDropZone.tsx avec drag-and-drop natif + click to browse
|
|
- ✅ Créé FilePreview.tsx avec icône dynamique selon type, taille formatée, bouton remove
|
|
- ✅ Créé page.tsx intégrant tous les composants avec Card, error display, privacy badges
|
|
- ✅ Build Next.js réussi (0 erreurs TypeScript)
|
|
- ✅ Route /dashboard/translate fonctionnelle
|
|
|
|
### Senior Developer Review (AI)
|
|
|
|
**Date:** 2026-02-23
|
|
**Reviewer:** Code Review Workflow
|
|
|
|
**Issues Fixed:**
|
|
1. 🔴 [HIGH] AC #8: Icône `FileText` → `Languages`, titre "Translate Document" → "Office Translator"
|
|
2. 🔴 [HIGH] `removeFile()` reset maintenant `isDragOver` state
|
|
3. 🟡 [MEDIUM] Input hidden au lieu de `absolute inset-0 opacity-0` (meilleure UX)
|
|
4. 🟡 [MEDIUM] Supprimé `'use client'` inutile du hook
|
|
5. 🟡 [MEDIUM] Supprimé `onClick={() => {}}` mort dans page.tsx
|
|
6. 🟡 [LOW] Messages d'erreur centralisés dans `ERROR_MESSAGES` constant
|
|
|
|
**Outcome:** ✅ Approved — All issues fixed
|
|
|
|
### File List
|
|
|
|
**Fichiers créés:**
|
|
- frontend/src/app/dashboard/translate/page.tsx
|
|
- frontend/src/app/dashboard/translate/types.ts
|
|
- frontend/src/app/dashboard/translate/useFileUpload.ts
|
|
- frontend/src/app/dashboard/translate/FileDropZone.tsx
|
|
- frontend/src/app/dashboard/translate/FilePreview.tsx
|
|
|
|
**Fichiers modifiés:**
|
|
- _bmad-output/implementation-artifacts/sprint-status.yaml (ready-for-dev → in-progress → review)
|