Files
got-gaming/components/ImageSelector.tsx

226 lines
7.3 KiB
TypeScript

"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>
);
}