diff --git a/apps/client/src/components/AudioUploaderMinimal.tsx b/apps/client/src/components/AudioUploaderMinimal.tsx index b7978ccf..840bd16f 100644 --- a/apps/client/src/components/AudioUploaderMinimal.tsx +++ b/apps/client/src/components/AudioUploaderMinimal.tsx @@ -11,32 +11,47 @@ import { toast } from "sonner"; export const AudioUploaderMinimal = () => { const [isDragging, setIsDragging] = useState(false); const [isUploading, setIsUploading] = useState(false); - const [fileName, setFileName] = useState(null); + const [uploadProgress, setUploadProgress] = useState<{ + current: number; + total: number; + fileName: string; + } | null>(null); const canMutate = useCanMutate(); const roomId = useRoomStore((state) => state.roomId); const isDisabled = !canMutate; - const handleFileUpload = async (file: File) => { - if (isDisabled) return; - - // Store file name for display - setFileName(file.name); + const handleFileUpload = async (files: File[]) => { + if (isDisabled || files.length === 0) return; try { setIsUploading(true); - // Upload the file to the server as binary - await uploadAudioFile({ - file, - roomId, - }); - - setTimeout(() => setFileName(null), 3000); - } catch (err) { - console.error("Error during upload:", err); - toast.error("Failed to upload audio file"); - setFileName(null); + for (let i = 0; i < files.length; i++) { + const file = files[i]; + setUploadProgress({ + current: i + 1, + total: files.length, + fileName: file.name, + }); + + try { + // Upload the file to the server as binary + await uploadAudioFile({ + file, + roomId, + }); + } catch (err) { + console.error(`Error uploading ${file.name}:`, err); + toast.error(`Failed to upload ${file.name}`); + } + } + + if (files.length > 1) { + toast.success(`Successfully uploaded ${files.length} files`); + } + + setTimeout(() => setUploadProgress(null), 3000); } finally { setIsUploading(false); } @@ -44,9 +59,30 @@ export const AudioUploaderMinimal = () => { const onInputChange = (event: React.ChangeEvent) => { if (isDisabled) return; - const file = event.target.files?.[0]; - if (!file) return; - handleFileUpload(file); + const fileList = event.target.files; + if (!fileList || fileList.length === 0) return; + + // Filter to only audio files + const audioFiles = Array.from(fileList).filter((file) => + file.type.startsWith("audio/"), + ); + + if (audioFiles.length === 0) { + toast.error("Please select audio files"); + event.target.value = ""; + return; + } + + if (audioFiles.length < fileList.length) { + toast.warning( + `Only uploading ${audioFiles.length} audio file${audioFiles.length > 1 ? "s" : ""} (${fileList.length - audioFiles.length} non-audio file${fileList.length - audioFiles.length > 1 ? "s" : ""} skipped)`, + ); + } + + handleFileUpload(audioFiles); + + // Reset the input so the same files can be selected again + event.target.value = ""; }; const onDragOver = (event: React.DragEvent) => { @@ -69,15 +105,26 @@ export const AudioUploaderMinimal = () => { event.stopPropagation(); setIsDragging(false); - const file = event.dataTransfer?.files?.[0]; - if (!file) return; - // make sure we only allow audio files - if (!file.type.startsWith("audio/")) { - toast.error("Please select an audio file"); + const fileList = event.dataTransfer?.files; + if (!fileList || fileList.length === 0) return; + + // Filter to only audio files + const audioFiles = Array.from(fileList).filter((file) => + file.type.startsWith("audio/"), + ); + + if (audioFiles.length === 0) { + toast.error("Please select audio files"); return; } - handleFileUpload(file); + if (audioFiles.length < fileList.length) { + toast.warning( + `Only uploading ${audioFiles.length} audio file${audioFiles.length > 1 ? "s" : ""} (${fileList.length - audioFiles.length} non-audio file${fileList.length - audioFiles.length > 1 ? "s" : ""} skipped)`, + ); + } + + handleFileUpload(audioFiles); }; return ( @@ -89,7 +136,7 @@ export const AudioUploaderMinimal = () => { : "bg-neutral-800/30 hover:bg-neutral-800/50", isDragging && !isDisabled ? "outline outline-primary-400 outline-dashed" - : "outline-none" + : "outline-none", )} id="drop_zone" onDragOver={onDragOver} @@ -110,7 +157,7 @@ export const AudioUploaderMinimal = () => { "p-1.5 rounded-md flex-shrink-0", isDisabled ? "bg-neutral-600 text-neutral-400" - : "bg-primary-700 text-white" + : "bg-primary-700 text-white", )} > {isUploading ? ( @@ -121,22 +168,29 @@ export const AudioUploaderMinimal = () => {
- {isUploading - ? "Uploading..." - : fileName - ? trimFileName(fileName) - : "Upload audio"} + {isUploading && uploadProgress + ? `Uploading ${uploadProgress.current} of ${uploadProgress.total}...` + : uploadProgress + ? uploadProgress.total > 1 + ? `Uploaded ${uploadProgress.total} files` + : trimFileName(uploadProgress.fileName) + : "Upload audio"}
- {!isUploading && !fileName && ( + {!isUploading && !uploadProgress && (
{isDisabled ? "Must be an admin to upload" - : "Add music to queue"} + : "Add music to queue (multi-select supported)"} +
+ )} + {isUploading && uploadProgress && ( +
+ {trimFileName(uploadProgress.fileName)}
)}
@@ -150,6 +204,7 @@ export const AudioUploaderMinimal = () => { onChange={onInputChange} disabled={isUploading || isDisabled} className="hidden" + multiple /> );