synchronisation du contenu avec la DB et amélioration du CMS

- Migration du blog et des interviews des données mockées vers PostgreSQL (Prisma).
- Refonte des pages publiques en Server Components pour de meilleures performances/SEO.
- Intégration d'un éditeur de texte riche (React Quill) avec titres H1-H6 et séparateurs.
- Correction des erreurs de validation Prisma liées aux paramètres asynchrones (Next.js 15+).
- Correction des problèmes d'affichage des modales de suspension via React Portals.
- Installation de @tailwindcss/typography et correction du débordement de texte (break-words).
- Implémentation des actions de modération (suspension/révocation) pour les utilisateurs et boutiques.
This commit is contained in:
2026-04-12 18:59:20 +02:00
parent b73939d381
commit 887030ee47
84 changed files with 5577 additions and 15128 deletions

View File

@@ -0,0 +1,111 @@
"use client";
import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { ShieldAlert, X, AlertOctagon } from 'lucide-react';
import { toast } from 'react-hot-toast';
interface SuspensionModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: (reason: string) => Promise<void>;
title: string;
subtitle: string;
}
export default function SuspensionModal({
isOpen,
onClose,
onConfirm,
title,
subtitle
}: SuspensionModalProps) {
const [reason, setReason] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
if (!isOpen || !mounted) return null;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!reason.trim()) {
toast.error("Veuillez saisir un motif de suspension");
return;
}
setIsSubmitting(true);
try {
await onConfirm(reason);
setReason('');
onClose();
} catch (error) {
console.error(error);
} finally {
setIsSubmitting(false);
}
};
const modalContent = (
<div className="modal-overlay" style={{ zIndex: 9999 }}>
<div className="modal-content max-w-md shadow-2xl">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3 text-red-500">
<ShieldAlert className="w-7 h-7" />
<h2 className="text-2xl font-bold text-white">{title}</h2>
</div>
<button
onClick={onClose}
className="p-1 rounded-full hover:bg-slate-800 text-slate-500 hover:text-white transition-all"
>
<X className="w-6 h-6" />
</button>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="bg-red-500/10 border border-red-500/20 p-4 rounded-xl flex gap-3 mb-6">
<AlertOctagon className="w-6 h-6 text-red-500 shrink-0" />
<p className="text-sm text-slate-300 leading-relaxed font-medium">{subtitle}</p>
</div>
<div className="space-y-2">
<label className="block text-xs font-bold text-slate-500 uppercase tracking-widest">
Motif de la suspension
</label>
<textarea
required
rows={4}
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder="Expliquez pourquoi ce compte est suspendu. Ce message sera visible par l'utilisateur."
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-4 text-white focus:outline-none focus:border-red-500 transition-all text-sm shadow-inner"
/>
</div>
<div className="flex gap-3 pt-6">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-3 bg-slate-800 hover:bg-slate-700 text-white rounded-xl transition-all font-bold text-sm"
>
Annuler
</button>
<button
type="submit"
disabled={isSubmitting}
className="flex-1 px-4 py-3 bg-red-600 hover:bg-red-700 disabled:opacity-50 text-white rounded-xl transition-all font-bold text-sm shadow-lg shadow-red-900/40"
>
{isSubmitting ? "En cours..." : "Confirmer"}
</button>
</div>
</form>
</div>
</div>
);
return createPortal(modalContent, document.body);
}