veza/apps/web/src/components/upload/FileUploadZone.tsx
senke ccb0169622 aesthetic-improvements: apply design direction to components batch 1 (Action 11.5.1.6)
- UI components: FAB (removed scale transforms), tabs (removed decorative shadow)
- Upload components: FileUploadZone, MetadataEditor, BulkUploadModal (removed scale transforms and decorative gradient)
- Search: SearchBar (removed decorative shadow)
- PWA: PWAInstallBanner (removed decorative shadow)
- Social: ExploreView (removed decorative shadow and image zoom)
- Live: LiveStreamDetailView (removed decorative shadow)
- Player: VisualizerSettingsModal (removed scale transform)
- Marketplace: ReviewProductModal, ProductDetailView (removed scale transforms)
- Library: PlaylistsView (removed scale transform)
- Settings: AppearanceSettingsView (removed scale transform)
- Theme: ThemeSwitcher (removed scale transform)
- Studio: ProjectsManager, CloudFileBrowser (removed scale transforms)
- Social: GroupCard (removed image zoom)
- Seller: CreateProductView (removed scale transform)
- Modals: CreatorModal (removed scale transform)
- Replaced decorative scale transforms with subtle opacity changes or removed entirely
- Action 11.5.1.6: Apply direction to all components - Batch 1 complete (17 components)
2026-01-16 12:06:00 +01:00

120 lines
3.7 KiB
TypeScript

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;
}
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>
);
};