import React, { useState, useRef, useCallback } from "react"; import { Upload, File, X, FilePlus } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; interface FileUploadZoneProps { onFileUpload: (file: File) => void; isUploading: boolean; acceptedFileTypes?: string; // OPTIONAL: fallback max file size MB maxFileSizeMB?: number; // OPTIONAL: per-type size map in MB, e.g. { "application/pdf": 10, "image/*": 2 } maxFileSizeByType?: Record; } export function FileUploadZone({ onFileUpload, isUploading, acceptedFileTypes = "application/pdf", maxFileSizeMB = 10, // default 10mb maxFileSizeByType, }: FileUploadZoneProps) { const { toast } = useToast(); const [isDragging, setIsDragging] = useState(false); const [uploadedFile, setUploadedFile] = useState(null); const fileInputRef = useRef(null); // helpers const mbToBytes = (mb: number) => Math.round(mb * 1024 * 1024); const humanSize = (bytes: number) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1024 / 1024).toFixed(2)} MB`; }; const parsedAccept = acceptedFileTypes .split(",") .map((s) => s.trim().toLowerCase()) .filter(Boolean); const allowedBytesForMime = (mime: string | undefined) => { if (!mime) return mbToBytes(maxFileSizeMB); if (maxFileSizeByType && maxFileSizeByType[mime] != null) { return mbToBytes(maxFileSizeByType[mime]!); } const parts = mime.split("/"); if (parts.length === 2) { const wildcard = `${parts[0]}/*`; if (maxFileSizeByType && maxFileSizeByType[wildcard] != null) { return mbToBytes(maxFileSizeByType[wildcard]!); } } return mbToBytes(maxFileSizeMB); }; const isMimeAllowed = (fileType: string | undefined) => { if (!fileType) return false; const ft = fileType.toLowerCase(); for (const a of parsedAccept) { if (a === ft) return true; if (a === "*/*") return true; if (a.endsWith("/*")) { const major = a.split("/")[0]; if (ft.startsWith(`${major}/`)) return true; } } return false; }; const validateFile = (file: File) => { // <<< CHANGED: use isMimeAllowed instead of strict include if (!isMimeAllowed(file.type)) { toast({ title: "Invalid file type", description: "Please upload a supported file type.", variant: "destructive", }); return false; } const allowedBytes = allowedBytesForMime(file.type); if (file.size > allowedBytes) { toast({ title: "File too large", description: `${file.name} is ${humanSize(file.size)} — max for this type is ${humanSize( allowedBytes )}`, variant: "destructive", }); return false; } return true; }; const handleDragEnter = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); }, []); const handleDragOver = useCallback( (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); if (!isDragging) { setIsDragging(true); } }, [isDragging] ); const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); if (e.dataTransfer.files && e.dataTransfer.files[0]) { const file = e.dataTransfer.files[0]; if (validateFile(file)) { setUploadedFile(file); onFileUpload(file); } } }, [onFileUpload, acceptedFileTypes, toast] ); const handleFileSelect = useCallback( (e: React.ChangeEvent) => { if (e.target.files && e.target.files[0]) { const file = e.target.files[0]; if (validateFile(file)) { setUploadedFile(file); onFileUpload(file); } } }, [onFileUpload, acceptedFileTypes, toast] ); const handleBrowseClick = () => { if (fileInputRef.current) { fileInputRef.current.click(); } }; const handleRemoveFile = () => { setUploadedFile(null); }; const typeBadges = parsedAccept.map((t) => { const display = t === "image/*" ? "Images" : t.includes("/") ? t!.split("/")[1]!.toUpperCase() : t.toUpperCase(); const mb = (maxFileSizeByType && (maxFileSizeByType[t] ?? maxFileSizeByType[`${t.split("/")[0]}/*`])) ?? maxFileSizeMB; return { key: t, label: `${display} ≤ ${mb} MB`, mb }; }); return (
{isUploading ? (

Uploading file...

) : uploadedFile ? (

{uploadedFile.name}

{humanSize(uploadedFile.size)} • allowed{" "} {humanSize(allowedBytesForMime(uploadedFile.type))} {" • "} {uploadedFile.type || "unknown"}

File ready to process

) : (

Drag and drop a PDF file here

{typeBadges.map((b) => ( {b.label} ))}

Or click to browse files

Accepts {acceptedFileTypes} — max {maxFileSizeMB} MB (default)

)}
); }