'use client'; import { useState, useCallback, useRef, type DragEvent } from 'react'; import { Button } from '@/components/ui/Button'; import { uploadFile, ALLOWED_MIME_TYPES, MAX_FILE_SIZE_MB, MAX_FILE_SIZE_BYTES, type FileResponse, } from '@/lib/files'; interface FileUploadProps { vehicleId: string; onUploadComplete?: (file: FileResponse) => void; onUploadError?: (error: string) => void; } interface UploadStatus { filename: string; status: 'pending' | 'uploading' | 'success' | 'error'; error?: string; } export function FileUpload({ vehicleId, onUploadComplete, onUploadError }: FileUploadProps) { const [isDragging, setIsDragging] = useState(false); const [uploadStatuses, setUploadStatuses] = useState([]); const [isUploading, setIsUploading] = useState(false); const inputRef = useRef(null); const validateFile = (file: File): string | null => { if (!ALLOWED_MIME_TYPES.includes(file.type)) { return `Unsupported file type: ${file.type || 'unknown'}`; } if (file.size > MAX_FILE_SIZE_BYTES) { return `File size exceeds ${MAX_FILE_SIZE_MB}MB limit`; } return null; }; const handleFiles = useCallback(async (files: FileList) => { setIsUploading(true); const fileArray = Array.from(files); const statuses: UploadStatus[] = fileArray.map((f) => ({ filename: f.name, status: 'pending' as const, })); setUploadStatuses(statuses); for (let i = 0; i < fileArray.length; i++) { const file = fileArray[i]; const validationError = validateFile(file); if (validationError) { setUploadStatuses((prev) => prev.map((s, idx) => idx === i ? { ...s, status: 'error', error: validationError } : s ) ); onUploadError?.(validationError); continue; } setUploadStatuses((prev) => prev.map((s, idx) => idx === i ? { ...s, status: 'uploading' } : s ) ); try { const result = await uploadFile(vehicleId, file); setUploadStatuses((prev) => prev.map((s, idx) => idx === i ? { ...s, status: 'success' } : s ) ); onUploadComplete?.(result); } catch (err: unknown) { const apiErr = err as { error?: { message?: string } }; const errorMsg = apiErr?.error?.message || 'Upload failed'; setUploadStatuses((prev) => prev.map((s, idx) => idx === i ? { ...s, status: 'error', error: errorMsg } : s ) ); onUploadError?.(errorMsg); } } setIsUploading(false); // Clear statuses after 3 seconds setTimeout(() => setUploadStatuses([]), 3000); }, [vehicleId, onUploadComplete, onUploadError]); const handleDragEnter = useCallback((e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(true); }, []); const handleDragLeave = useCallback((e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); }, []); const handleDragOver = useCallback((e: DragEvent) => { e.preventDefault(); e.stopPropagation(); }, []); const handleDrop = useCallback((e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { handleFiles(e.dataTransfer.files); } }, [handleFiles]); const handleInputChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { handleFiles(e.target.files); // Reset input so same file can be selected again e.target.value = ''; } }; return (
inputRef.current?.click()} className={` border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors ${isDragging ? 'border-primary bg-primary/5' : 'border-border hover:border-primary/50' } `} data-testid="dropzone" >

{isDragging ? 'Drop files here' : 'Drag & drop files or click to browse'}

Supports: JPG, PNG, WebP, PDF, DOC, DOCX (max {MAX_FILE_SIZE_MB}MB)

{uploadStatuses.length > 0 && (
{uploadStatuses.map((status, idx) => (
{status.filename} {status.status === 'pending' && 'Pending...'} {status.status === 'uploading' && 'Uploading...'} {status.status === 'success' && '✓ Uploaded'} {status.status === 'error' && `✗ ${status.error}`}
))}
)} {isUploading && (
)}
); }