maj url photo + page 404 + gestion programmation d'article
This commit is contained in:
174
admin/src/components/ImageUploader.tsx
Normal file
174
admin/src/components/ImageUploader.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Upload, Link as LinkIcon, X, Image as ImageIcon, Loader2 } from 'lucide-react';
|
||||
import { uploadImage } from '@/app/actions/upload';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export default function ImageUploader({ value, onChange, label, placeholder, name }: Props) {
|
||||
const [mode, setMode] = useState<'url' | 'upload'>(value?.startsWith('/uploads/') ? 'upload' : 'url');
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
// Basic validation
|
||||
if (!file.type.startsWith('image/')) {
|
||||
toast.error("Le fichier doit être une image");
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
toast.error("L'image ne doit pas dépasser 5 Mo");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUploading(true);
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const result = await uploadImage(formData);
|
||||
if (result.success && result.url) {
|
||||
onChange(result.url);
|
||||
toast.success("Image uploadée avec succès");
|
||||
} else {
|
||||
toast.error(result.error || "Erreur lors de l'upload");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("Erreur de connexion lors de l'upload");
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
if (fileInputRef.current) fileInputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const clearImage = () => {
|
||||
onChange('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium text-slate-400">{label || "Image"}</label>
|
||||
<div className="flex bg-slate-900 rounded-lg p-1 border border-slate-800">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMode('url')}
|
||||
className={`flex items-center gap-1.5 px-3 py-1 rounded-md text-xs font-medium transition-colors ${
|
||||
mode === 'url' ? 'bg-indigo-600 text-white' : 'text-slate-400 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<LinkIcon className="w-3.5 h-3.5" />
|
||||
Lien URL
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMode('upload')}
|
||||
className={`flex items-center gap-1.5 px-3 py-1 rounded-md text-xs font-medium transition-colors ${
|
||||
mode === 'upload' ? 'bg-indigo-600 text-white' : 'text-slate-400 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<Upload className="w-3.5 h-3.5" />
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative group">
|
||||
{mode === 'url' ? (
|
||||
<div className="relative">
|
||||
<LinkIcon className="absolute left-3 top-3.5 w-5 h-5 text-slate-500" />
|
||||
<input
|
||||
type="text"
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder || "https://images.unsplash.com/..."}
|
||||
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 pl-10 text-white focus:outline-none focus:border-indigo-500 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{!value ? (
|
||||
<div
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="cursor-pointer border-2 border-dashed border-slate-700 hover:border-indigo-500 rounded-xl p-8 flex flex-col items-center justify-center gap-3 bg-slate-900/50 hover:bg-slate-900 transition-all group"
|
||||
>
|
||||
{isUploading ? (
|
||||
<Loader2 className="w-10 h-10 text-indigo-500 animate-spin" />
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded-full bg-slate-800 flex items-center justify-center group-hover:bg-indigo-500/20 group-hover:text-indigo-400 transition-colors">
|
||||
<Upload className="w-6 h-6 text-slate-400 group-hover:text-indigo-400" />
|
||||
</div>
|
||||
)}
|
||||
<div className="text-center">
|
||||
<p className="text-sm font-medium text-white">
|
||||
{isUploading ? "Upload en cours..." : "Cliquez pour uploader"}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500 mt-1">PNG, JPG ou WEBP jusqu'à 5 Mo</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative rounded-xl overflow-hidden aspect-video bg-slate-900 border border-slate-700">
|
||||
<img
|
||||
src={value}
|
||||
alt="Prévisualisation"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="p-2 bg-white/10 backdrop-blur-md rounded-full text-white hover:bg-white/20 transition-colors"
|
||||
>
|
||||
<Upload className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearImage}
|
||||
className="p-2 bg-red-500/80 backdrop-blur-md rounded-full text-white hover:bg-red-500 transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
{/* Hidden input for value to be sent via form if needed */}
|
||||
<input type="hidden" name={name} value={value} />
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileChange}
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{value && mode === 'url' && (
|
||||
<div className="mt-3 relative rounded-lg overflow-hidden aspect-video border border-slate-800">
|
||||
<img src={value} alt="Preview" className="w-full h-full object-cover" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearImage}
|
||||
className="absolute top-2 right-2 p-1.5 bg-black/60 backdrop-blur-md rounded-full text-white hover:bg-black transition-colors"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user