Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 91 additions & 36 deletions apps/client/src/components/AudioUploaderMinimal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,78 @@ import { toast } from "sonner";
export const AudioUploaderMinimal = () => {
const [isDragging, setIsDragging] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const [fileName, setFileName] = useState<string | null>(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);
}
};

const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
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<HTMLDivElement>) => {
Expand All @@ -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 (
Expand All @@ -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}
Expand All @@ -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 ? (
Expand All @@ -121,22 +168,29 @@ export const AudioUploaderMinimal = () => {
</div>
<div className="flex-1 min-w-0">
<div className="text-xs font-medium text-white truncate">
{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"}
</div>
{!isUploading && !fileName && (
{!isUploading && !uploadProgress && (
<div
className={cn(
"text-xs truncate",
isDisabled ? "text-neutral-500" : "text-neutral-400"
isDisabled ? "text-neutral-500" : "text-neutral-400",
)}
>
{isDisabled
? "Must be an admin to upload"
: "Add music to queue"}
: "Add music to queue (multi-select supported)"}
</div>
)}
{isUploading && uploadProgress && (
<div className="text-xs text-neutral-400 truncate">
{trimFileName(uploadProgress.fileName)}
</div>
)}
</div>
Expand All @@ -150,6 +204,7 @@ export const AudioUploaderMinimal = () => {
onChange={onInputChange}
disabled={isUploading || isDisabled}
className="hidden"
multiple
/>
</div>
);
Expand Down