import { useState, useCallback, useRef } from 'react'; import { Upload, FileText, X } from 'lucide-react'; interface FileUploadProps { onUpload: (file: File, source?: string) => Promise; loading?: boolean; } const ACCEPTED_TYPES = [ '.csv', '.json', '.xlsx', '.xls', '.zip', 'text/csv', 'application/json', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel', 'application/zip', ]; export function FileUpload({ onUpload, loading }: FileUploadProps) { const [dragOver, setDragOver] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const [source, setSource] = useState(''); const [error, setError] = useState(null); const inputRef = useRef(null); const validateFile = (file: File): boolean => { const ext = '.' + file.name.split('.').pop()?.toLowerCase(); if (!['.csv', '.json', '.xlsx', '.xls', '.zip'].includes(ext)) { setError(`Unsupported format: ${ext}. Use CSV, JSON, Excel, or ZIP.`); return false; } if (file.size > 500 * 1024 * 1024) { setError('File too large. Maximum 500MB.'); return false; } setError(null); return true; }; const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); setDragOver(false); const file = e.dataTransfer.files[0]; if (file && validateFile(file)) { setSelectedFile(file); } }, []); const handleFileSelect = useCallback((e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file && validateFile(file)) { setSelectedFile(file); } }, []); const handleSubmit = async () => { if (!selectedFile) return; try { await onUpload(selectedFile, source || undefined); setSelectedFile(null); setSource(''); } catch (err) { setError(err instanceof Error ? err.message : 'Upload failed'); } }; 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 (
{ e.preventDefault(); setDragOver(true); }} onDragLeave={() => setDragOver(false)} onDrop={handleDrop} onClick={() => inputRef.current?.click()} role="button" tabIndex={0} aria-label="Upload file" onKeyDown={(e) => e.key === 'Enter' && inputRef.current?.click()} >

Drop files here or click to browse

Supports CSV, JSON, Excel (.xlsx/.xls), and ZIP files up to 500MB

Files >10MB will be uploaded in chunks automatically

{error && (
{error}
)} {selectedFile && (
{selectedFile.name}
{formatSize(selectedFile.size)}
setSource(e.target.value)} />
)}
); }