All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m36s
226 lines
7.3 KiB
TypeScript
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>
|
|
);
|
|
}
|