import { useState, useEffect, useRef } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { apiRequest, queryClient } from "@/lib/queryClient"; import Sidebar from "@/components/sidebar"; import TopBar from "@/components/topbar"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Slider } from "@/components/ui/slider"; import { Badge } from "@/components/ui/badge"; import { Wand2, Save, Image as ImageIcon, X } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; interface Ribbon { enabled: boolean; text: "Promo" | "Nouveauté" | "Arrivage" | "Stock limité"; color: "red" | "yellow"; position: "north_west" | "north_east"; } interface PriceBadge { enabled: boolean; price: string; color: "red" | "yellow"; position: "north_east" | "south_east" | "north_west" | "south_west"; size: number; } interface Logo { enabled: boolean; opacity: number; position: "north_east" | "south_east" | "north_west" | "south_west" | "center"; size: "small" | "medium" | "large"; } export default function ImageEditorMobile() { const [sidebarOpen, setSidebarOpen] = useState(false); const [selectedMedia, setSelectedMedia] = useState(null); const [ribbon, setRibbon] = useState({ enabled: false, text: "Promo", color: "red", position: "north_west" }); const [priceBadge, setPriceBadge] = useState({ enabled: false, price: "", color: "red", position: "north_east", size: 20 }); const [logo, setLogo] = useState({ enabled: false, opacity: 60, position: "south_east", size: "medium" }); const [previewUrl, setPreviewUrl] = useState(""); const { data: mediaList } = useQuery({ queryKey: ["/api/media"], }); const { data: cloudinaryConfig } = useQuery({ queryKey: ['/api/cloudinary/config'], }); const previewRef = useRef(null); const { toast } = useToast(); const saveImageMutation = useMutation({ mutationFn: async () => { if (!selectedMedia) { throw new Error("Aucune image sélectionnée"); } const payload = { imageUrl: selectedMedia.originalUrl, ribbon: ribbon.enabled ? ribbon : undefined, priceBadge: priceBadge.enabled && priceBadge.price ? priceBadge : undefined, logo: logo.enabled ? logo : undefined, }; const response = await apiRequest('POST', '/api/media/apply-overlays', payload); return response; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/media"] }); toast({ title: "Image enregistrée !", description: "L'image éditée a été ajoutée à votre médiathèque.", }); }, onError: (error: Error) => { toast({ title: "Erreur", description: error.message || "Impossible d'enregistrer l'image.", variant: "destructive" }); } }); useEffect(() => { if (!selectedMedia) { setPreviewUrl(""); return; } let url = selectedMedia.originalUrl; const cloudinaryPattern = /https:\/\/res\.cloudinary\.com\/([^\/]+)\/image\/upload\//; const match = url.match(cloudinaryPattern); if (!match) { setPreviewUrl(url); return; } const [fullMatch, cloudName] = match; const imagePath = url.replace(fullMatch, ''); const transformations: string[] = []; transformations.push('w_800,h_800,c_fit'); const transformedUrl = `https://res.cloudinary.com/${cloudName}/image/upload/${transformations.join('/')}/${imagePath}`; setPreviewUrl(transformedUrl); }, [selectedMedia, ribbon, priceBadge, logo]); return (
{sidebarOpen && (
setSidebarOpen(false)} /> )}
setSidebarOpen(false)} />
setSidebarOpen(!sidebarOpen)} /> {/* MOBILE: Single column layout */}

Éditeur d'images

Ajoutez des rubans, badges prix et filtres

{/* Preview Card - Top on mobile */} {selectedMedia && ( Aperçu
Aperçu {ribbon.enabled && ribbon.text && (
{ribbon.text === "Stock limité" ? ( <> Stock
limité ) : ribbon.text}
)} {priceBadge.enabled && priceBadge.price && (
{priceBadge.price} €
)} {logo.enabled && cloudinaryConfig && (cloudinaryConfig as any).logoPublicId && ( Logo )}
)} {/* Image Selection */} Sélectionner une image {mediaList && (mediaList as any[]).filter((m: any) => m.type === 'image').length > 0 ? (
{(mediaList as any[]) .filter((m: any) => m.type === 'image') .map((media: any) => (
setSelectedMedia(media)} data-testid={`select-image-${media.id}`} >
))}
) : (

Aucune image disponible. Uploadez des images dans la médiathèque.

)}
{/* Ribbon Controls */} Ruban promotionnel
setRibbon({ ...ribbon, enabled: checked as boolean })} className="w-5 h-5" data-testid="checkbox-ribbon" />
{ribbon.enabled && ( <>
setRibbon({ ...ribbon, text: value as any })} className="space-y-2" > {["Promo", "Nouveauté", "Arrivage", "Stock limité"].map((text) => (
))}
setRibbon({ ...ribbon, color: value as any })} className="space-y-2" >
)}
{/* Price Badge */} Badge prix
setPriceBadge({ ...priceBadge, enabled: checked as boolean })} className="w-5 h-5" data-testid="checkbox-price" />
{priceBadge.enabled && ( <>
setPriceBadge({ ...priceBadge, price: e.target.value })} className="min-h-[48px]" data-testid="input-price" />
setPriceBadge({ ...priceBadge, color: value as any })} className="space-y-2" >
{priceBadge.size}px
setPriceBadge({ ...priceBadge, size: value[0] })} min={12} max={70} step={1} className="touch-manipulation" data-testid="slider-price-size" />
)}
{/* Logo */} Logo
setLogo({ ...logo, enabled: checked as boolean })} className="w-5 h-5" data-testid="checkbox-logo-enabled" />
{logo.enabled && ( <>
{logo.opacity}%
setLogo({ ...logo, opacity: value[0] })} min={0} max={100} step={5} className="touch-manipulation" data-testid="slider-logo-opacity" />
)}
); }