Add ability to upload multiple images to the media library at once

Enable multiple file uploads in the media library component, allowing users to upload several images simultaneously. This commit modifies the `useDropzone` configuration to accept multiple files and updates the `onDrop` function to handle parallel uploads. It also introduces UI feedback for ongoing uploads.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/nlpElis
This commit is contained in:
michaelschal committed 2025-10-13 07:59:24 +00:00
1 parent 4ec1e3ef33
commit da2a1211d6
1 file changed
+78 -21
+78 -21
View File
@@ -19,6 +19,8 @@ export default function MediaUpload() {
queryKey: ["/api/media"],
});
const [uploadingCount, setUploadingCount] = useState(0);
const uploadMutation = useMutation({
mutationFn: async (file: File) => {
const formData = new FormData();
@@ -33,25 +35,61 @@ export default function MediaUpload() {
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["/api/media"] });
setSelectedFile(data);
toast({
title: "Succès",
description: "Média téléchargé et traité avec succès",
});
},
onError: () => {
toast({
title: "Erreur",
description: "Impossible de télécharger le média",
variant: "destructive",
});
},
});
const onDrop = useCallback((acceptedFiles: File[]) => {
if (acceptedFiles.length > 0) {
uploadMutation.mutate(acceptedFiles[0]);
const onDrop = useCallback(async (acceptedFiles: File[]) => {
if (acceptedFiles.length === 0) return;
setUploadingCount(acceptedFiles.length);
let successCount = 0;
let errorCount = 0;
// Upload tous les fichiers en parallèle
const uploadPromises = acceptedFiles.map(async (file) => {
try {
const formData = new FormData();
formData.append("file", file);
const response = await fetch("/api/media/upload", {
method: "POST",
body: formData,
});
if (!response.ok) throw new Error("Upload failed");
successCount++;
return response.json();
} catch (error) {
errorCount++;
throw error;
}
});
try {
await Promise.all(uploadPromises);
queryClient.invalidateQueries({ queryKey: ["/api/media"] });
toast({
title: "Succès",
description: `${successCount} média(s) téléchargé(s) avec succès`,
});
} catch (error) {
if (successCount > 0) {
queryClient.invalidateQueries({ queryKey: ["/api/media"] });
toast({
title: "Partiellement réussi",
description: `${successCount} média(s) téléchargé(s), ${errorCount} échec(s)`,
variant: "destructive",
});
} else {
toast({
title: "Erreur",
description: "Impossible de télécharger les médias",
variant: "destructive",
});
}
} finally {
setUploadingCount(0);
}
}, []);
}, [toast]);
const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
onDrop,
@@ -60,6 +98,7 @@ export default function MediaUpload() {
"video/*": [".mp4", ".mov"],
},
maxSize: 52428800,
multiple: true,
});
const deleteMutation = useMutation({
@@ -126,18 +165,36 @@ export default function MediaUpload() {
className={`
border-2 border-dashed rounded-2xl p-12 text-center cursor-pointer transition-all
${isDragActive ? "border-primary bg-primary/5 shadow-lg" : "border-border/50 hover:border-primary hover:bg-accent/30"}
${uploadingCount > 0 ? "pointer-events-none opacity-50" : ""}
`}
data-testid="dropzone-upload"
>
<input {...getInputProps()} />
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-primary/10 to-secondary/10 flex items-center justify-center mx-auto mb-6">
<CloudUpload className="w-8 h-8 text-primary" />
{uploadingCount > 0 ? (
<Loader2 className="w-8 h-8 text-primary animate-spin" />
) : (
<CloudUpload className="w-8 h-8 text-primary" />
)}
</div>
<p className="text-foreground font-semibold text-lg mb-2">
{isDragActive ? "Déposez vos fichiers ici" : "Glissez-déposez vos fichiers"}
</p>
<p className="text-sm text-muted-foreground mb-4">ou cliquez pour parcourir</p>
<p className="text-xs text-muted-foreground font-medium">PNG, JPG, MP4 jusqu'à 50MB</p>
{uploadingCount > 0 ? (
<>
<p className="text-foreground font-semibold text-lg mb-2">
Téléchargement en cours...
</p>
<p className="text-sm text-muted-foreground mb-4">
{uploadingCount} fichier(s) en cours de traitement
</p>
</>
) : (
<>
<p className="text-foreground font-semibold text-lg mb-2">
{isDragActive ? "Déposez vos fichiers ici" : "Glissez-déposez vos fichiers"}
</p>
<p className="text-sm text-muted-foreground mb-4">ou cliquez pour parcourir</p>
<p className="text-xs text-muted-foreground font-medium">PNG, JPG, MP4 jusqu'à 50MB</p>
</>
)}
</div>
<div className="space-y-4">