Files
afrov2/components/dashboard/CreateEventModal.tsx

230 lines
9.6 KiB
TypeScript

"use client";
import React, { useState, useTransition } from 'react';
import { X, Calendar, MapPin, Link as LinkIcon, Image as ImageIcon, AlertTriangle, Loader2 } from 'lucide-react';
import { createEvent } from '@/app/actions/events';
import { uploadImage } from '@/app/actions/upload';
import { toast } from 'react-hot-toast';
interface CreateEventModalProps {
isOpen: boolean;
onClose: () => void;
}
export default function CreateEventModal({ isOpen, onClose }: CreateEventModalProps) {
const [isPending, startTransition] = useTransition();
const [imagePreview, setImagePreview] = useState<string | null>(null);
const [uploading, setUploading] = useState(false);
if (!isOpen) return null;
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data = {
title: formData.get('title') as string,
description: formData.get('description') as string,
date: formData.get('date') as string,
location: formData.get('location') as string,
thumbnailUrl: imagePreview || "https://picsum.photos/800/400?random=event",
link: formData.get('link') as string || undefined,
};
if (!data.title || !data.description || !data.date || !data.location) {
toast.error("Veuillez remplir tous les champs obligatoires");
return;
}
startTransition(async () => {
const result = await createEvent(data);
if (result.success) {
toast.success("Événement soumis avec succès !");
onClose();
} else {
toast.error(result.error || "Une erreur est survenue");
}
});
};
const handleImageChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setUploading(true);
const formData = new FormData();
formData.append('file', file);
const result = await uploadImage(formData);
if (result.success && result.url) {
setImagePreview(result.url);
} else {
toast.error(result.error || "Erreur lors de l'upload");
}
setUploading(false);
};
return (
<div className="fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-300">
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-2xl overflow-hidden animate-in zoom-in-95 duration-300">
<div className="p-6 border-b border-gray-100 flex items-center justify-between bg-brand-50/50">
<div className="flex items-center gap-3">
<div className="p-2 bg-brand-100 rounded-xl text-brand-600">
<Calendar className="w-6 h-6" />
</div>
<div>
<h2 className="text-xl font-bold text-gray-900">Publier un événement</h2>
<p className="text-xs text-gray-500">Partagez votre actualité avec la communauté</p>
</div>
</div>
<button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-full transition-colors">
<X className="w-6 h-6 text-gray-400" />
</button>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-6 max-h-[80vh] overflow-y-auto">
{/* Warning */}
<div className="flex gap-4 p-4 bg-amber-50 border border-amber-200 rounded-2xl">
<div className="shrink-0 p-2 bg-amber-100 rounded-xl text-amber-600">
<AlertTriangle className="w-5 h-5" />
</div>
<div>
<h4 className="text-sm font-bold text-amber-800">Information importante</h4>
<p className="text-xs text-amber-700 leading-relaxed">
Votre événement sera soumis à la validation de notre équipe de modération avant d'être publié sur la plateforme (généralement sous 24h).
</p>
</div>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-bold text-gray-700 mb-1">Titre de l'événement *</label>
<input
name="title"
required
placeholder="Ex: Conférence sur l'Agrotech"
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-brand-500 outline-none transition-all"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-bold text-gray-700 mb-1">Date *</label>
<div className="relative">
<Calendar className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
name="date"
type="date"
required
className="w-full pl-11 pr-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-brand-500 outline-none transition-all"
/>
</div>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-1">Lieu *</label>
<div className="relative">
<MapPin className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
name="location"
required
placeholder="Ex: Abidjan, Côte d'Ivoire"
className="w-full pl-11 pr-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-brand-500 outline-none transition-all"
/>
</div>
</div>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-1">Description *</label>
<textarea
name="description"
required
rows={4}
placeholder="Présentez votre événement en quelques lignes..."
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-brand-500 outline-none transition-all resize-none"
></textarea>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-1">Affiche / Photo de l'événement</label>
<div
className={`relative border-2 border-dashed rounded-2xl flex flex-col items-center justify-center p-8 transition-all ${
imagePreview ? 'border-brand-500 bg-brand-50' : 'border-gray-200 hover:border-brand-300 bg-gray-50'
}`}
>
{imagePreview ? (
<div className="relative w-full aspect-video rounded-lg overflow-hidden shadow-inner">
<img src={imagePreview} alt="Preview" className="w-full h-full object-cover" />
<button
type="button"
onClick={() => setImagePreview(null)}
className="absolute top-2 right-2 p-1 bg-white/80 rounded-full hover:bg-white text-gray-600 shadow-lg"
>
<X className="w-4 h-4" />
</button>
</div>
) : (
<>
{uploading ? (
<Loader2 className="w-10 h-10 text-brand-500 animate-spin" />
) : (
<ImageIcon className="w-10 h-10 text-gray-300 mb-2" />
)}
<p className="text-sm font-medium text-gray-500">Cliquez pour ajouter une image</p>
<p className="text-xs text-gray-400 mt-1">PNG, JPG, JPEG jusqu'à 5Mo</p>
<input
type="file"
accept="image/png, image/jpeg, image/jpg, image/webp"
onChange={handleImageChange}
className="absolute inset-0 opacity-0 cursor-pointer"
/>
</>
)}
</div>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-1">Lien externe (facultatif)</label>
<div className="relative">
<LinkIcon className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
name="link"
placeholder="https://votre-billetterie.com"
className="w-full pl-11 pr-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-brand-500 outline-none transition-all"
/>
</div>
</div>
</div>
<div className="flex items-center gap-4 pt-4">
<button
type="button"
onClick={onClose}
className="flex-1 px-6 py-4 rounded-2xl font-bold text-gray-600 hover:bg-gray-100 transition-all"
>
Annuler
</button>
<button
type="submit"
disabled={isPending || uploading}
className="flex-[2] bg-brand-600 hover:bg-brand-700 disabled:bg-gray-200 disabled:cursor-not-allowed text-white px-6 py-4 rounded-2xl font-bold shadow-xl shadow-brand-200 transition-all flex items-center justify-center gap-2"
>
{isPending ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
Soumission...
</>
) : (
<>
<Calendar className="w-5 h-5" />
Soumettre l'événement
</>
)}
</button>
</div>
</form>
</div>
</div>
);
}