import React, { useState, useCallback } from 'react'; import { useDropzone } from 'react-dropzone'; // File type with additional metadata interface FileWithMeta extends File { id: string; preview?: string; progress: number; status: 'pending' | 'uploading' | 'success' | 'error'; error?: string; } // File Dropzone Component export function FileDropzone() { const [files, setFiles] = useState([]); // Handle file drop const onDrop = useCallback((acceptedFiles: File[], rejectedFiles: any[]) => { // Process accepted files const newFiles: FileWithMeta[] = acceptedFiles.map((file) => ({ ...file, id: `${file.name}-${Date.now()}`, preview: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined, progress: 0, status: 'pending' as const, } as FileWithMeta)); setFiles((prev) => [...prev, ...newFiles]); // Handle rejected files if (rejectedFiles.length > 0) { const errors = rejectedFiles.map((rejection) => { const errors = rejection.errors.map((e: any) => e.message).join(', '); return `${rejection.file.name}: ${errors}`; }); alert(`Some files were rejected:\n${errors.join('\n')}`); } // Start upload simulation newFiles.forEach((file) => { simulateUpload(file); }); }, []); // Configure dropzone const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject, open, } = useDropzone({ onDrop, accept: { 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.webp'], 'application/pdf': ['.pdf'], 'text/*': ['.txt', '.md', '.csv'], 'application/vnd.openxmlformats-officedocument.*': ['.docx', '.xlsx'], }, maxSize: 10 * 1024 * 1024, // 10MB maxFiles: 10, multiple: true, }); // Simulate file upload with progress const simulateUpload = (file: FileWithMeta) => { // Update status to uploading updateFileStatus(file.id, 'uploading'); let progress = 0; const interval = setInterval(() => { progress += Math.random() * 30; if (progress >= 100) { progress = 100; clearInterval(interval); // Randomly succeed or fail (90% success rate) if (Math.random() > 0.1) { updateFileStatus(file.id, 'success'); } else { updateFileStatus(file.id, 'error', 'Upload failed'); } } updateFileProgress(file.id, Math.min(progress, 100)); }, 500); }; // Update file status const updateFileStatus = ( id: string, status: FileWithMeta['status'], error?: string ) => { setFiles((prev) => prev.map((file) => file.id === id ? { ...file, status, error } : file ) ); }; // Update file progress const updateFileProgress = (id: string, progress: number) => { setFiles((prev) => prev.map((file) => file.id === id ? { ...file, progress } : file ) ); }; // Remove file const removeFile = (id: string) => { setFiles((prev) => { const file = prev.find((f) => f.id === id); if (file?.preview) { URL.revokeObjectURL(file.preview); } return prev.filter((f) => f.id !== id); }); }; // Retry failed upload const retryUpload = (file: FileWithMeta) => { updateFileStatus(file.id, 'pending'); updateFileProgress(file.id, 0); simulateUpload(file); }; // Clear all files const clearAll = () => { files.forEach((file) => { if (file.preview) { URL.revokeObjectURL(file.preview); } }); setFiles([]); }; return (

File Upload

{/* Dropzone Area */}
{isDragActive ? ( <> {isDragAccept && (
📥

Drop files here to upload

)} {isDragReject && (
🚫

Some files are not accepted

)} ) : (
☁️

Drag & drop files here

or click to browse

Accepted: Images, PDFs, Documents Max size: 10MB per file Max files: 10
)}
{/* File List */} {files.length > 0 && (

Files ({files.length})

{files.map((file) => ( removeFile(file.id)} onRetry={() => retryUpload(file)} /> ))}
)} {/* Upload Summary */} {files.length > 0 && ( )}
); } // Individual File Item function FileItem({ file, onRemove, onRetry, }: { file: FileWithMeta; onRemove: () => void; onRetry: () => void; }) { const formatFileSize = (bytes: number) => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; }; const getFileIcon = (type: string) => { if (type.startsWith('image/')) return '🖼️'; if (type === 'application/pdf') return '📄'; if (type.startsWith('text/')) return '📝'; return '📎'; }; return (
{/* Preview or Icon */}
{file.preview ? ( {file.name} ) : ( {getFileIcon(file.type)} )}
{/* File Info */}
{file.name}
{formatFileSize(file.size)} {file.status === 'success' && ( ✓ Uploaded )} {file.status === 'error' && ( ✗ {file.error} )}
{/* Progress Bar */} {file.status === 'uploading' && (
{Math.round(file.progress)}%
)}
{/* Actions */}
{file.status === 'error' && ( )}
); } // Upload Summary function UploadSummary({ files }: { files: FileWithMeta[] }) { const stats = { pending: files.filter((f) => f.status === 'pending').length, uploading: files.filter((f) => f.status === 'uploading').length, success: files.filter((f) => f.status === 'success').length, error: files.filter((f) => f.status === 'error').length, }; const totalSize = files.reduce((sum, file) => sum + file.size, 0); return (
Total Size: {(totalSize / (1024 * 1024)).toFixed(2)} MB
{stats.pending > 0 && (
Pending: {stats.pending}
)} {stats.uploading > 0 && (
Uploading: {stats.uploading}
)} {stats.success > 0 && (
Completed: {stats.success}
)} {stats.error > 0 && (
Failed: {stats.error}
)}
); } // Styles const styles = ` .dropzone-container { max-width: 800px; margin: 0 auto; padding: 2rem; } .dropzone { border: 2px dashed var(--color-border); border-radius: var(--radius-lg); padding: 3rem; text-align: center; cursor: pointer; transition: all 0.3s ease; background: var(--color-white); } .dropzone:hover { border-color: var(--color-primary); background: var(--color-primary-50); } .dropzone.drag-active { border-color: var(--color-primary); background: var(--color-primary-50); transform: scale(1.02); } .dropzone.drag-accept { border-color: var(--color-success); background: var(--color-success-50); } .dropzone.drag-reject { border-color: var(--color-danger); background: var(--color-danger-50); animation: shake 0.5s; } @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } .dropzone-content { pointer-events: none; } .drop-message { animation: fadeIn 0.3s; } .drop-icon { font-size: 3rem; display: block; margin-bottom: 1rem; } .upload-icon { font-size: 4rem; display: block; margin-bottom: 1rem; } .help-text { color: var(--color-text-secondary); margin: 0.5rem 0 1.5rem; } .file-info { display: flex; gap: 1rem; justify-content: center; margin: 1rem 0; font-size: 0.875rem; color: var(--color-text-tertiary); } .browse-btn { margin-top: 1rem; padding: 0.75rem 1.5rem; background: var(--color-primary); color: white; border: none; border-radius: var(--radius-md); cursor: pointer; transition: all 0.2s; } .browse-btn:hover { background: var(--color-primary-600); transform: translateY(-2px); } .file-list { margin-top: 2rem; background: var(--color-white); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 1rem; } .file-list-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 1rem; border-bottom: 1px solid var(--color-border); margin-bottom: 1rem; } .file-item { display: flex; align-items: center; gap: 1rem; padding: 0.75rem; background: var(--color-gray-50); border-radius: var(--radius-md); margin-bottom: 0.5rem; transition: all 0.2s; } .file-item:hover { background: var(--color-gray-100); } .file-preview { width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; background: var(--color-white); border-radius: var(--radius-sm); overflow: hidden; } .file-preview img { width: 100%; height: 100%; object-fit: cover; } .file-icon { font-size: 1.5rem; } .file-info { flex: 1; } .file-name { font-weight: 500; margin-bottom: 0.25rem; } .file-meta { display: flex; gap: 1rem; font-size: 0.875rem; color: var(--color-text-secondary); } .progress-bar { position: relative; height: 4px; background: var(--color-gray-200); border-radius: 2px; margin-top: 0.5rem; overflow: hidden; } .progress-fill { position: absolute; height: 100%; background: var(--color-primary); transition: width 0.3s ease; } .progress-text { position: absolute; right: 0; top: -20px; font-size: 0.75rem; color: var(--color-text-tertiary); } .file-actions { display: flex; gap: 0.5rem; } .action-btn { padding: 0.25rem 0.5rem; background: transparent; border: none; cursor: pointer; font-size: 1.25rem; transition: all 0.2s; } .action-btn:hover { transform: scale(1.1); } .upload-summary { display: flex; gap: 2rem; padding: 1rem; margin-top: 1rem; background: var(--color-gray-50); border-radius: var(--radius-md); font-size: 0.875rem; } .stat { display: flex; gap: 0.5rem; } .stat-label { color: var(--color-text-secondary); } .stat-value { font-weight: 600; } .stat.success .stat-value { color: var(--color-success); } .stat.error .stat-value { color: var(--color-danger); } `;