veza/apps/web/src/components/upload/FileUploadZone.tsx

121 lines
3.7 KiB
TypeScript
Raw Normal View History

import React, { useCallback, useState } from 'react';
import { UploadCloud } from 'lucide-react';
import { useToast } from '../../components/feedback/ToastProvider';
interface FileUploadZoneProps {
onFilesSelected: (files: File[]) => void;
acceptedFormats?: string[];
maxSizeInMB?: number;
}
export const FileUploadZone: React.FC<FileUploadZoneProps> = ({
onFilesSelected,
acceptedFormats = ['.wav', '.mp3', '.aiff', '.flac', '.zip'],
maxSizeInMB = 500,
}) => {
const { addToast } = useToast();
const [isDragging, setIsDragging] = useState(false);
const validateFile = (file: File): boolean => {
// Check size
if (file.size > maxSizeInMB * 1024 * 1024) {
addToast(`File ${file.name} exceeds ${maxSizeInMB}MB limit`, 'error');
return false;
}
// Check extension (simple check)
const ext = `.${file.name.split('.').pop()?.toLowerCase()}`;
if (!acceptedFormats.includes(ext)) {
addToast(`Format ${ext} not supported for ${file.name}`, 'error');
return false;
}
return true;
};
const handleDrop = useCallback(
(e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
const droppedFiles = Array.from(e.dataTransfer.files);
const validFiles = droppedFiles.filter(validateFile);
if (validFiles.length > 0) {
onFilesSelected(validFiles);
}
},
[onFilesSelected],
);
const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
const selectedFiles = Array.from(e.target.files);
const validFiles = selectedFiles.filter(validateFile);
if (validFiles.length > 0) {
onFilesSelected(validFiles);
}
}
};
return (
<div
onDragOver={(e) => {
e.preventDefault();
setIsDragging(true);
}}
onDragLeave={() => setIsDragging(false)}
onDrop={handleDrop}
className={`
border-2 border-dashed rounded-xl p-12 flex flex-col items-center justify-center text-center transition-all duration-300 group cursor-pointer
${
isDragging
? 'border-kodo-cyan bg-kodo-cyan/10 scale-[1.02]'
: 'border-kodo-steel/50 bg-kodo-ink/30 hover:bg-kodo-ink/50 hover:border-kodo-steel/50'
}
`}
>
<input
type="file"
multiple
className="hidden"
id="file-upload-input"
onChange={handleFileInput}
accept={acceptedFormats.join(',')}
/>
<label
htmlFor="file-upload-input"
className="cursor-pointer w-full flex flex-col items-center"
>
<div
className={`
w-20 h-20 rounded-full flex items-center justify-center mb-6 transition-all duration-300 shadow-lg
${isDragging ? 'bg-kodo-cyan text-black' : 'bg-kodo-slate text-kodo-cyan group-hover:bg-kodo-steel'}
`}
>
<UploadCloud className="w-10 h-10" />
</div>
<h3 className="text-xl font-display font-bold text-white mb-2">
{isDragging ? 'Drop Files Here' : 'Drag & Drop or Click'}
</h3>
<p className="text-kodo-content-dim mb-6 max-w-sm">
Upload audio stems, project archives, or sample packs.
</p>
<div className="flex flex-wrap justify-center gap-2 max-w-md">
{acceptedFormats.map((fmt) => (
<span
key={fmt}
className="px-2 py-1 bg-black/30 rounded text-[10px] font-mono text-kodo-content-dim border border-white/5 uppercase"
>
{fmt.replace('.', '')}
</span>
))}
</div>
<p className="mt-4 text-[10px] text-kodo-content-dim">
Max file size: {maxSizeInMB}MB
</p>
</label>
</div>
);
};