Refactor component imports and structure: Update import paths for various components to improve organization, moving them into appropriate subdirectories. Remove unused components related to user and event management, enhancing code clarity and maintainability across the application.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m36s

This commit is contained in:
Julien Froidefond
2025-12-12 16:48:41 +01:00
parent 880e96d6e4
commit 97db800c73
27 changed files with 23 additions and 23 deletions

View File

@@ -0,0 +1,225 @@
"use client";
import { useState, useEffect, useRef, type ChangeEvent } from "react";
import { Input, Button, Card } from "@/components/ui";
interface ImageSelectorProps {
value: string;
onChange: (url: string) => void;
label: string;
}
export default function ImageSelector({
value,
onChange,
label,
}: ImageSelectorProps) {
const [availableImages, setAvailableImages] = useState<string[]>([]);
const [uploading, setUploading] = useState(false);
const [urlInput, setUrlInput] = useState("");
const [showGallery, setShowGallery] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
fetchAvailableImages();
}, []);
const fetchAvailableImages = async () => {
try {
const response = await fetch("/api/admin/images/list");
if (response.ok) {
const data = await response.json();
setAvailableImages(data.images || []);
}
} catch (error) {
console.error("Error fetching images:", error);
}
};
const handleFileUpload = async (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setUploading(true);
try {
const formData = new FormData();
formData.append("file", file);
const response = await fetch("/api/admin/images/upload", {
method: "POST",
body: formData,
});
if (response.ok) {
const data = await response.json();
onChange(data.url);
await fetchAvailableImages(); // Rafraîchir la liste
} else {
alert("Erreur lors de l'upload de l'image");
}
} catch (error) {
console.error("Error uploading image:", error);
alert("Erreur lors de l'upload de l'image");
} finally {
setUploading(false);
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
}
};
const handleUrlSubmit = () => {
if (urlInput.trim()) {
onChange(urlInput.trim());
setUrlInput("");
}
};
return (
<div className="space-y-3">
<label className="block text-xs sm:text-sm text-gray-300 mb-1 break-words">
{label}
</label>
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4">
{/* Colonne gauche - Image */}
<div className="flex-shrink-0 flex justify-center sm:justify-start">
{value ? (
<div className="relative w-full sm:w-48 h-40 sm:h-32 border border-pixel-gold/30 rounded overflow-hidden bg-black/60">
<img
src={value}
alt="Preview"
className="w-full h-full object-cover"
onError={(e) => {
const target = e.currentTarget;
// Ne pas boucler si l'image de fallback échoue aussi
const currentSrc = target.src;
const fallbackSrc = "/got-2.jpg";
if (!currentSrc.includes(fallbackSrc)) {
target.src = fallbackSrc;
} else {
target.style.display = "none";
}
}}
/>
<button
onClick={() => onChange("")}
className="absolute top-2 right-2 px-2 py-1 bg-red-900/80 text-red-200 text-xs rounded hover:bg-red-900 transition"
>
</button>
</div>
) : (
<div className="w-full sm:w-48 h-40 sm:h-32 border border-pixel-gold/30 rounded bg-black/60 flex items-center justify-center">
<span className="text-xs text-gray-500">Aucune</span>
</div>
)}
</div>
{/* Colonne droite - Contrôles */}
<div className="flex-1 space-y-3 min-w-0">
{/* Input URL */}
<div className="flex flex-col sm:flex-row gap-2">
<Input
type="text"
value={urlInput}
onChange={(e) => setUrlInput(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && handleUrlSubmit()}
placeholder="https://example.com/image.jpg ou /image.jpg"
className="flex-1 text-xs sm:text-sm px-3 py-2 min-w-0"
/>
<Button
onClick={handleUrlSubmit}
variant="primary"
size="sm"
className="whitespace-nowrap flex-shrink-0"
>
URL
</Button>
</div>
{/* Upload depuis le disque */}
<div className="flex flex-col sm:flex-row gap-2">
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleFileUpload}
className="hidden"
id={`file-${label}`}
/>
<label htmlFor={`file-${label}`}>
<Button
variant="primary"
size="sm"
as="span"
disabled={uploading}
className="flex-1 text-center cursor-pointer"
>
{uploading ? "Upload..." : "Upload depuis le disque"}
</Button>
</label>
<Button
onClick={() => setShowGallery(!showGallery)}
variant="primary"
size="sm"
className="whitespace-nowrap"
>
{showGallery ? "Masquer" : "Galerie"}
</Button>
</div>
{/* Chemin de l'image */}
{value && (
<p className="text-xs text-gray-400 truncate break-all">{value}</p>
)}
</div>
</div>
{/* Galerie d'images */}
{showGallery && (
<Card variant="dark" className="mt-4 p-3 sm:p-4">
<h4 className="text-xs sm:text-sm text-gray-300 mb-3">
Images disponibles
</h4>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2 sm:gap-3 max-h-64 overflow-y-auto">
{availableImages.length === 0 ? (
<div className="col-span-full text-center text-gray-400 text-sm py-4">
Aucune image disponible
</div>
) : (
availableImages.map((imageUrl) => (
<button
key={imageUrl}
onClick={() => {
onChange(imageUrl);
setShowGallery(false);
}}
className={`relative aspect-video rounded overflow-hidden border-2 transition ${
value === imageUrl
? "border-pixel-gold"
: "border-pixel-gold/30 hover:border-pixel-gold/50"
}`}
>
<img
src={imageUrl}
alt={imageUrl}
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.style.display = "none";
}}
/>
{value === imageUrl && (
<div className="absolute inset-0 bg-pixel-gold/20 flex items-center justify-center">
<span className="text-pixel-gold text-xs"></span>
</div>
)}
</button>
))
)}
</div>
</Card>
)}
</div>
);
}