'use client'; import * as React from 'react'; import { Upload, X, File, FileVideo, FileAudio, FileImage, Clock, HardDrive, Film } from 'lucide-react'; import { cn } from '@/lib/utils/cn'; import type { ConversionFormat } from '@/types/media'; export interface FileUploadProps { onFileSelect: (files: File[]) => void; onFileRemove: (index: number) => void; selectedFiles?: File[]; accept?: string; maxSizeMB?: number; disabled?: boolean; inputRef?: React.RefObject; inputFormat?: ConversionFormat; } function CategoryIcon({ format, className }: { format?: ConversionFormat; className?: string }) { const cls = cn('text-primary', className); if (!format) return ; switch (format.category) { case 'video': return ; case 'audio': return ; case 'image': return ; default: return ; } } export function FileUpload({ onFileSelect, onFileRemove, selectedFiles = [], accept, maxSizeMB = 500, disabled = false, inputRef, inputFormat, }: FileUploadProps) { const [isDragging, setIsDragging] = React.useState(false); const [fileMetadata, setFileMetadata] = React.useState>>({}); const localRef = React.useRef(null); const fileInputRef = inputRef || localRef; React.useEffect(() => { const extract = async () => { if (selectedFiles.length === 0 || !inputFormat) { setFileMetadata({}); return; } const out: Record> = {}; for (let i = 0; i < selectedFiles.length; i++) { const file = selectedFiles[i]; const base = { size: file.size < 1024 * 1024 ? `${(file.size / 1024).toFixed(1)} KB` : `${(file.size / (1024 * 1024)).toFixed(1)} MB`, type: inputFormat.name, }; if (inputFormat.category === 'video' && file.type.startsWith('video/')) { const video = document.createElement('video'); video.preload = 'metadata'; out[i] = await new Promise((res) => { video.onloadedmetadata = () => { const d = video.duration, m = Math.floor(d / 60), s = Math.floor(d % 60); res({ ...base, duration: `${m}:${s.toString().padStart(2, '0')}`, dimensions: `${video.videoWidth}×${video.videoHeight}` }); URL.revokeObjectURL(video.src); }; video.onerror = () => { res(base); URL.revokeObjectURL(video.src); }; video.src = URL.createObjectURL(file); }); } else if (inputFormat.category === 'audio' && file.type.startsWith('audio/')) { const audio = document.createElement('audio'); audio.preload = 'metadata'; out[i] = await new Promise((res) => { audio.onloadedmetadata = () => { const d = audio.duration, m = Math.floor(d / 60), s = Math.floor(d % 60); res({ ...base, duration: `${m}:${s.toString().padStart(2, '0')}` }); URL.revokeObjectURL(audio.src); }; audio.onerror = () => { res(base); URL.revokeObjectURL(audio.src); }; audio.src = URL.createObjectURL(file); }); } else if (inputFormat.category === 'image' && file.type.startsWith('image/')) { const img = new Image(); out[i] = await new Promise((res) => { img.onload = () => { res({ ...base, dimensions: `${img.width}×${img.height}` }); URL.revokeObjectURL(img.src); }; img.onerror = () => { res(base); URL.revokeObjectURL(img.src); }; img.src = URL.createObjectURL(file); }); } else { out[i] = base; } } setFileMetadata(out); }; extract(); }, [selectedFiles, inputFormat]); const handleFiles = (files: File[]) => { const maxBytes = maxSizeMB * 1024 * 1024; const valid = files.filter((f) => { if (f.size > maxBytes) { alert(`${f.name} exceeds ${maxSizeMB}MB limit.`); return false; } return true; }); if (valid.length > 0) onFileSelect(valid); if (fileInputRef.current) fileInputRef.current.value = ''; }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); if (!disabled) handleFiles(Array.from(e.dataTransfer.files)); }; const triggerInput = () => { if (!disabled) fileInputRef.current?.click(); }; return (
handleFiles(Array.from(e.target.files || []))} disabled={disabled} /> {selectedFiles.length === 0 ? ( /* ── Drop zone ─────────────────────────────────────── */
{ e.preventDefault(); if (!disabled) setIsDragging(true); }} onDragLeave={(e) => { e.preventDefault(); setIsDragging(false); }} onDragOver={(e) => e.preventDefault()} onDrop={handleDrop} className={cn( 'flex-1 flex flex-col items-center justify-center rounded-xl border-2 border-dashed transition-all cursor-pointer', 'text-center select-none', isDragging ? 'border-primary bg-primary/10 scale-[0.99]' : 'border-border/35 hover:border-primary/40 hover:bg-primary/5', disabled && 'opacity-50 cursor-not-allowed pointer-events-none' )} >

{isDragging ? 'Drop to upload' : 'Drop files or click to browse'}

Video · Audio · Image · Max {maxSizeMB}MB

) : ( /* ── File list ─────────────────────────────────────── */
{selectedFiles.map((file, idx) => { const meta = fileMetadata[idx]; return (

{file.name}

{meta && (
{meta.size} {meta.duration && {meta.duration}} {meta.dimensions && {meta.dimensions}}
)}
); })}
{/* Add more */}
)}
); }