2026-01-07 09:31:02 +00:00
|
|
|
import React, { useCallback, useState } from 'react';
|
|
|
|
|
import { UploadCloud } from 'lucide-react';
|
|
|
|
|
import { useToast } from '../../context/ToastContext';
|
|
|
|
|
|
|
|
|
|
interface FileUploadZoneProps {
|
|
|
|
|
onFilesSelected: (files: File[]) => void;
|
|
|
|
|
acceptedFormats?: string[];
|
|
|
|
|
maxSizeInMB?: number;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export const FileUploadZone: React.FC<FileUploadZoneProps> = ({
|
|
|
|
|
onFilesSelected,
|
|
|
|
|
acceptedFormats = ['.wav', '.mp3', '.aiff', '.flac', '.zip'],
|
|
|
|
|
maxSizeInMB = 500,
|
2026-01-07 09:31:02 +00:00
|
|
|
}) => {
|
|
|
|
|
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)
|
2026-01-13 18:47:57 +00:00
|
|
|
const ext = `.${file.name.split('.').pop()?.toLowerCase()}`;
|
2026-01-07 09:31:02 +00:00
|
|
|
if (!acceptedFormats.includes(ext)) {
|
|
|
|
|
addToast(`Format ${ext} not supported for ${file.name}`, 'error');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
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],
|
|
|
|
|
);
|
2026-01-07 09:31:02 +00:00
|
|
|
|
|
|
|
|
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 (
|
2026-01-13 18:47:57 +00:00
|
|
|
<div
|
|
|
|
|
onDragOver={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
setIsDragging(true);
|
|
|
|
|
}}
|
2026-01-07 09:31:02 +00:00
|
|
|
onDragLeave={() => setIsDragging(false)}
|
|
|
|
|
onDrop={handleDrop}
|
|
|
|
|
className={`
|
|
|
|
|
border-2 border-dashed rounded-xl p-10 flex flex-col items-center justify-center text-center transition-all duration-300 group cursor-pointer
|
2026-01-13 18:47:57 +00:00
|
|
|
${
|
|
|
|
|
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-cyan/50'
|
|
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
`}
|
|
|
|
|
>
|
2026-01-13 18:47:57 +00:00
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
multiple
|
|
|
|
|
className="hidden"
|
2026-01-07 09:31:02 +00:00
|
|
|
id="file-upload-input"
|
|
|
|
|
onChange={handleFileInput}
|
|
|
|
|
accept={acceptedFormats.join(',')}
|
|
|
|
|
/>
|
2026-01-13 18:47:57 +00:00
|
|
|
<label
|
|
|
|
|
htmlFor="file-upload-input"
|
|
|
|
|
className="cursor-pointer w-full flex flex-col items-center"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className={`
|
2026-01-07 09:31:02 +00:00
|
|
|
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 scale-110' : 'bg-kodo-slate text-kodo-cyan group-hover:scale-110 group-hover:bg-kodo-steel'}
|
2026-01-13 18:47:57 +00:00
|
|
|
`}
|
|
|
|
|
>
|
2026-01-07 09:31:02 +00:00
|
|
|
<UploadCloud className="w-10 h-10" />
|
|
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
<h3 className="text-2xl font-display font-bold text-white mb-2">
|
|
|
|
|
{isDragging ? 'Drop Files Here' : 'Drag & Drop or Click'}
|
|
|
|
|
</h3>
|
|
|
|
|
<p className="text-gray-400 mb-6 max-w-sm">
|
|
|
|
|
Upload audio stems, project archives, or sample packs.
|
|
|
|
|
</p>
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
<div className="flex flex-wrap justify-center gap-2 max-w-md">
|
2026-01-13 18:47:57 +00:00
|
|
|
{acceptedFormats.map((fmt) => (
|
|
|
|
|
<span
|
|
|
|
|
key={fmt}
|
|
|
|
|
className="px-2 py-1 bg-black/30 rounded text-[10px] font-mono text-gray-500 border border-white/5 uppercase"
|
|
|
|
|
>
|
2026-01-07 09:31:02 +00:00
|
|
|
{fmt.replace('.', '')}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<p className="mt-4 text-[10px] text-gray-600">
|
|
|
|
|
Max file size: {maxSizeInMB}MB
|
|
|
|
|
</p>
|
2026-01-07 09:31:02 +00:00
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2026-01-13 18:47:57 +00:00
|
|
|
};
|