uploadv2 / components /FileUploader.tsx
Twan07's picture
Upload 5 files
540f559 verified
raw
history blame
4.66 kB
import React, { useCallback, useState } from 'react';
import { UploadCloud, FileUp } from 'lucide-react';
import { FileItem, UploadStatus } from '../types';
const generateId = () => Math.random().toString(36).substring(2, 15);
interface FileUploaderProps {
onFilesAdded: (files: FileItem[]) => void;
disabled: boolean;
}
const sanitizeFileName = (fileName: string): string => {
const timestamp = Date.now();
const lastDotIndex = fileName.lastIndexOf('.');
const name = lastDotIndex !== -1 ? fileName.substring(0, lastDotIndex) : fileName;
const ext = lastDotIndex !== -1 ? fileName.substring(lastDotIndex) : '';
let cleanName = name;
cleanName = cleanName.replace(/^\d+[-_.\s]*/, '');
cleanName = cleanName.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
.replace(/đ/g, 'd').replace(/Đ/g, 'D');
cleanName = cleanName.replace(/[^a-zA-Z0-9]/g, '-');
cleanName = cleanName.replace(/-+/g, '-').replace(/^-|-$/g, '');
if (cleanName.length === 0) cleanName = 'file';
return `${timestamp}-${cleanName}${ext}`.toLowerCase();
};
export const FileUploader: React.FC<FileUploaderProps> = ({ onFilesAdded, disabled }) => {
const [isDragging, setIsDragging] = useState(false);
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault();
if (!disabled) setIsDragging(true);
}, [disabled]);
const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
}, []);
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
if (disabled) return;
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
processFiles(e.dataTransfer.files);
}
}, [disabled]);
const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
processFiles(e.target.files);
e.target.value = ''; // Reset input
}
};
const processFiles = (fileList: FileList) => {
const newFiles: FileItem[] = Array.from(fileList).map(file => {
const cleanPath = sanitizeFileName(file.name);
return {
id: generateId(),
file,
path: cleanPath,
status: UploadStatus.IDLE
};
});
onFilesAdded(newFiles);
};
return (
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={`
relative group border-2 border-dashed rounded-2xl p-10 text-center transition-all duration-300 ease-out overflow-hidden
${disabled ? 'opacity-60 cursor-not-allowed border-gray-200 bg-gray-50' : 'cursor-pointer'}
${isDragging
? 'border-indigo-500 bg-indigo-50/50 scale-[1.01] shadow-lg'
: 'border-gray-200 hover:border-indigo-400 hover:bg-gray-50'}
`}
>
<input
type="file"
multiple
onChange={handleFileInput}
disabled={disabled}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer disabled:cursor-not-allowed z-20"
/>
{/* Decorative Background Glow */}
<div className={`absolute inset-0 bg-gradient-to-tr from-indigo-100/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none`} />
<div className="relative z-10 flex flex-col items-center justify-center space-y-4">
<div className={`
p-5 rounded-2xl shadow-sm transition-all duration-300
${isDragging ? 'bg-indigo-100 text-indigo-600 scale-110' : 'bg-white border border-gray-100 text-gray-400 group-hover:text-indigo-500 group-hover:scale-105 group-hover:shadow-md'}
`}>
<UploadCloud className="w-10 h-10" strokeWidth={1.5} />
</div>
<div>
<p className="text-xl font-semibold text-gray-700 group-hover:text-indigo-900 transition-colors">
{isDragging ? 'Drop files instantly' : 'Click or Drag files here'}
</p>
<p className="text-sm text-gray-400 mt-2 max-w-sm mx-auto">
Supports images, JSON, CSV, and Parquet.
<span className="block mt-1 text-xs text-indigo-400 opacity-80">Files are auto-renamed with timestamps</span>
</p>
</div>
<div className="pt-2">
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-md bg-gray-100 text-gray-500 text-xs font-medium group-hover:bg-indigo-50 group-hover:text-indigo-600 transition-colors">
<FileUp className="w-3 h-3" />
Bulk Upload Supported
</span>
</div>
</div>
</div>
);
};