217 lines
8.9 KiB
TypeScript
217 lines
8.9 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useTransition } from 'react';
|
|
import { X, Newspaper, User, Image as ImageIcon, AlertTriangle, Loader2, AlignLeft } from 'lucide-react';
|
|
import { createNews } from '@/app/actions/news';
|
|
import { uploadImage } from '@/app/actions/upload';
|
|
import { toast } from 'react-hot-toast';
|
|
|
|
interface CreateNewsModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function CreateNewsModal({ isOpen, onClose }: CreateNewsModalProps) {
|
|
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,
|
|
excerpt: formData.get('excerpt') as string,
|
|
content: formData.get('content') as string,
|
|
author: formData.get('author') as string,
|
|
imageUrl: imagePreview || "https://picsum.photos/800/400?random=news",
|
|
};
|
|
|
|
if (!data.title || !data.excerpt || !data.content || !data.author) {
|
|
toast.error("Veuillez remplir tous les champs obligatoires");
|
|
return;
|
|
}
|
|
|
|
startTransition(async () => {
|
|
const result = await createNews(data);
|
|
if (result.success) {
|
|
toast.success("Actualité soumise 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-indigo-50/50">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-indigo-100 rounded-xl text-indigo-600">
|
|
<Newspaper className="w-6 h-6" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-xl font-bold text-gray-900">Publier une actualité</h2>
|
|
<p className="text-xs text-gray-500">Mettez en avant vos succès et nouveautés</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 article sera soumis à la validation de notre équipe de modération avant d'être publié dans la section Actualités.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-bold text-gray-700 mb-1">Titre de l'article *</label>
|
|
<input
|
|
name="title"
|
|
required
|
|
placeholder="Ex: Lancement de notre nouvelle gamme de produits"
|
|
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-indigo-500 outline-none transition-all"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-bold text-gray-700 mb-1">Auteur *</label>
|
|
<div className="relative">
|
|
<User className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
|
<input
|
|
name="author"
|
|
required
|
|
placeholder="Votre nom ou nom d'entreprise"
|
|
className="w-full pl-11 pr-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-indigo-500 outline-none transition-all"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-bold text-gray-700 mb-1">Résumé court (Excerpt) *</label>
|
|
<div className="relative">
|
|
<AlignLeft className="absolute left-4 top-4 w-4 h-4 text-gray-400" />
|
|
<textarea
|
|
name="excerpt"
|
|
required
|
|
rows={2}
|
|
placeholder="Un court résumé qui apparaîtra dans la liste des articles..."
|
|
className="w-full pl-11 pr-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-indigo-500 outline-none transition-all resize-none"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-bold text-gray-700 mb-1">Contenu de l'article *</label>
|
|
<textarea
|
|
name="content"
|
|
required
|
|
rows={6}
|
|
placeholder="Racontez votre histoire..."
|
|
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-indigo-500 outline-none transition-all resize-none"
|
|
></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-bold text-gray-700 mb-1">Image de couverture</label>
|
|
<div
|
|
className={`relative border-2 border-dashed rounded-2xl flex flex-col items-center justify-center p-8 transition-all ${
|
|
imagePreview ? 'border-indigo-500 bg-indigo-50' : 'border-gray-200 hover:border-indigo-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-indigo-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 jusqu'à 2Mo</p>
|
|
<input
|
|
type="file"
|
|
accept="image/*"
|
|
onChange={handleImageChange}
|
|
className="absolute inset-0 opacity-0 cursor-pointer"
|
|
/>
|
|
</>
|
|
)}
|
|
</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-indigo-600 hover:bg-indigo-700 disabled:bg-gray-200 disabled:cursor-not-allowed text-white px-6 py-4 rounded-2xl font-bold shadow-xl shadow-indigo-200 transition-all flex items-center justify-center gap-2"
|
|
>
|
|
{isPending ? (
|
|
<>
|
|
<Loader2 className="w-5 h-5 animate-spin" />
|
|
Soumission...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Newspaper className="w-5 h-5" />
|
|
Soumettre l'actualité
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|