import React, { useCallback, useState } from 'react'; import { useDropzone } from 'react-dropzone'; import { Box, Button, VStack, HStack, Text, Progress, Alert, AlertIcon, AlertTitle, AlertDescription, Icon, Input, Checkbox, Select, useToast, } from '@chakra-ui/react'; import { FiUpload, FiMusic, FiCheck, FiX } from 'react-icons/fi'; interface UploadProgress { fileName: string; progress: number; status: 'uploading' | 'success' | 'error'; error?: string; } interface MusicUploadProps { onUploadComplete?: (files: any[]) => void; } export const MusicUpload: React.FC = ({ onUploadComplete }) => { const [uploadProgress, setUploadProgress] = useState([]); const [isUploading, setIsUploading] = useState(false); const [targetFolder, setTargetFolder] = useState('uploads'); const [folders, setFolders] = useState([]); const [markForScan, setMarkForScan] = useState(true); const toast = useToast(); const onDrop = useCallback(async (acceptedFiles: File[]) => { setIsUploading(true); const newProgress: UploadProgress[] = acceptedFiles.map(file => ({ fileName: file.name, progress: 0, status: 'uploading', })); setUploadProgress(newProgress); const results = []; for (let i = 0; i < acceptedFiles.length; i++) { const file = acceptedFiles[i]; try { const formData = new FormData(); formData.append('file', file); formData.append('targetFolder', targetFolder); formData.append('markForScan', String(markForScan)); const response = await fetch('/api/music/upload', { method: 'POST', body: formData, }); if (response.ok) { const result = await response.json(); results.push(result.musicFile); // Update progress setUploadProgress(prev => prev.map((item, index) => index === i ? { ...item, progress: 100, status: 'success' as const } : item ) ); } else { const error = await response.json(); throw new Error(error.error || 'Upload failed'); } } catch (error) { console.error(`Error uploading ${file.name}:`, error); setUploadProgress(prev => prev.map((item, index) => index === i ? { ...item, progress: 0, status: 'error' as const, error: error instanceof Error ? error.message : 'Upload failed' } : item ) ); } } setIsUploading(false); if (results.length > 0) { toast({ title: 'Upload Complete', description: `Successfully uploaded ${results.length} file(s)`, status: 'success', duration: 5000, isClosable: true, }); onUploadComplete?.(results); } }, [onUploadComplete, toast]); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: { 'audio/*': ['.mp3', '.wav', '.flac', '.aac', '.m4a', '.ogg', '.wma', '.opus'], }, maxSize: 100 * 1024 * 1024, // 100MB multiple: true, }); // Load folders for dropdown React.useEffect(() => { (async () => { try { const res = await fetch('/api/music/folders'); if (!res.ok) throw new Error('Failed to load folders'); const data = await res.json(); const items = Array.isArray(data.folders) ? data.folders : []; setFolders(items); if (items.length > 0 && targetFolder === 'uploads') { // default target: root (empty) if available, else first item const defaultChoice = items.find((f: string) => f === '') || items[0]; setTargetFolder(defaultChoice.replace(/^\/+/, '')); } } catch (e) { // ignore, keep text input fallback } })(); }, []); const resetUploads = () => { setUploadProgress([]); }; return ( Target S3 folder {folders.length > 0 ? ( ) : ( setTargetFolder(e.target.value)} placeholder="e.g. Prep/ToScan" bg="gray.800" borderColor="gray.700" /> )} setMarkForScan(e.target.checked)} colorScheme="blue" alignSelf="end"> Add to "To Be Scanned" {isDragActive ? 'Drop the music files here...' : 'Drag & drop music files here, or click to select'} Supports MP3, WAV, FLAC, AAC, OGG, WMA, Opus (max 100MB per file) {uploadProgress.length > 0 && ( Upload Progress {uploadProgress.map((item, index) => ( {item.fileName} {item.status === 'error' ? ( Upload failed {item.error} ) : ( )} ))} )} {isUploading && ( Uploading files... Please wait while your music files are being uploaded and processed. )} ); };