import { useState, useCallback, useRef, useEffect } from "react"; import { useDropzone } from "react-dropzone"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent } from "@/components/ui/dialog"; import { useMutation, useQuery } from "@tanstack/react-query"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { CloudUpload, Image as ImageIcon, Video, X, Upload, Loader2, ZoomIn, Camera } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { SiFacebook, SiInstagram } from "react-icons/si"; export default function MediaUpload() { const { toast } = useToast(); const [selectedFile, setSelectedFile] = useState(null); const [visibleCount, setVisibleCount] = useState(5); const [zoomImage, setZoomImage] = useState(null); const loadMoreRef = useRef(null); const cameraInputRef = useRef(null); const { data: mediaList } = useQuery({ queryKey: ["/api/media"], }); const [uploadingCount, setUploadingCount] = useState(0); const uploadMutation = useMutation({ mutationFn: async (file: File) => { 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"); return response.json(); }, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ["/api/media"] }); setSelectedFile(data); }, }); 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, accept: { "image/*": [".png", ".jpg", ".jpeg", ".gif"], "video/*": [".mp4", ".mov"], }, maxSize: 52428800, multiple: true, }); const deleteMutation = useMutation({ mutationFn: async (id: string) => { await apiRequest("DELETE", `/api/media/${id}`); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/media"] }); setSelectedFile(null); }, }); const handleCameraCapture = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { // Correction Android : créer un nom valide si le fichier n'a pas de nom let fileToUpload = file; if (!file.name || file.name === 'blob' || file.name === '') { const extension = file.type.split('/')[1] || 'jpg'; const newFileName = `camera-${Date.now()}.${extension}`; fileToUpload = new File([file], newFileName, { type: file.type }); } uploadMutation.mutate(fileToUpload); // Réinitialiser l'input pour permettre de capturer la même photo à nouveau e.target.value = ''; } }; // Scroll infini - IntersectionObserver useEffect(() => { const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && mediaList && visibleCount < (mediaList as any[]).length) { setVisibleCount(prev => Math.min(prev + 5, (mediaList as any[]).length)); } }, { threshold: 0.1 } ); if (loadMoreRef.current) { observer.observe(loadMoreRef.current); } return () => { if (loadMoreRef.current) { observer.unobserve(loadMoreRef.current); } }; }, [mediaList, visibleCount]); return (

Médiathèque

Téléchargement et recadrage automatique

0 ? "pointer-events-none opacity-50" : ""} `} data-testid="dropzone-upload" >
{uploadingCount > 0 ? ( ) : ( )}
{uploadingCount > 0 ? ( <>

Téléchargement en cours...

{uploadingCount} fichier(s) en cours de traitement

) : ( <>

{isDragActive ? "Déposez vos fichiers ici" : "Glissez-déposez vos fichiers"}

ou cliquez pour parcourir

PNG, JPG, MP4 jusqu'à 50MB

)}

Fichiers téléchargés {(mediaList as any[])?.length || 0} média(s)

{(mediaList as any[])?.slice(0, visibleCount).map((media: any) => (
setSelectedFile(media)} className="relative aspect-square rounded-lg overflow-hidden border-2 border-border/50 cursor-pointer hover:border-primary hover:shadow-lg transition-all group" data-testid={`media-item-${media.id}`} > {media.type === "video" ? (
) : ( {media.fileName} )}
{media.type === "image" && ( )}
))}
{/* Élément sentinelle pour le scroll infini */} {(mediaList && visibleCount < (mediaList as any[]).length) ? (
) : null}

Aperçu et recadrage automatique

Feed
1080×1080
{selectedFile?.facebookFeedUrl || selectedFile?.instagramFeedUrl ? ( Feed preview ) : (
)}
Story
1080×1920
{selectedFile?.instagramStoryUrl ? ( Story preview ) : (
)}

Upload en cours...

Votre image est en cours de téléchargement et de traitement.
Veuillez patienter.

{/* Modal pour voir l'image originale en plein écran */} setZoomImage(null)}>
Image originale
); }