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,148 @@
"use client";
import React, { useState } from 'react';
import { User, Mail, Phone, MapPin, FileText, Save, Loader2 } from 'lucide-react';
import { useUser } from '../UserProvider';
import { toast } from 'react-hot-toast';
const DashboardProfileClient = () => {
const { user, login } = useUser();
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
name: user?.name || '',
email: user?.email || '',
phone: user?.phone || '',
bio: user?.bio || '',
location: user?.location || '',
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const res = await fetch('/api/users/me', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'x-user-id': user?.id || ''
},
body: JSON.stringify(formData)
});
const data = await res.json();
if (!data.error) {
login(data); // Sync local storage/context
toast.success('Profil personnel mis à jour !');
} else {
toast.error(data.error);
}
} catch (error) {
toast.error('Erreur lors de la sauvegarde');
} finally {
setLoading(false);
}
};
return (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div className="p-6 border-b border-gray-100 bg-gray-50">
<h3 className="text-xl font-bold font-serif text-gray-900">Informations Personnelles</h3>
<p className="text-sm text-gray-500">Ces informations sont visibles par les entrepreneurs que vous contactez.</p>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-sm font-semibold text-gray-700 flex items-center gap-2">
<User className="w-4 h-4 text-brand-600" /> Nom Complet
</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
className="w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-brand-500 outline-none transition-all"
placeholder="Votre nom"
required
/>
</div>
<div className="space-y-2">
<label className="text-sm font-semibold text-gray-700 flex items-center gap-2">
<Mail className="w-4 h-4 text-brand-600" /> Email
</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
className="w-full px-4 py-2 border border-gray-200 rounded-lg bg-gray-50 text-gray-500 cursor-not-allowed"
disabled
/>
</div>
<div className="space-y-2">
<label className="text-sm font-semibold text-gray-700 flex items-center gap-2">
<Phone className="w-4 h-4 text-brand-600" /> Téléphone
</label>
<input
type="tel"
name="phone"
value={formData.phone}
onChange={handleChange}
className="w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-brand-500 outline-none transition-all"
placeholder="+225 ..."
/>
</div>
<div className="space-y-2">
<label className="text-sm font-semibold text-gray-700 flex items-center gap-2">
<MapPin className="w-4 h-4 text-brand-600" /> Ville / Pays
</label>
<input
type="text"
name="location"
value={formData.location}
onChange={handleChange}
className="w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-brand-500 outline-none transition-all"
placeholder="Ex: Abidjan, Côte d'Ivoire"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-semibold text-gray-700 flex items-center gap-2">
<FileText className="w-4 h-4 text-brand-600" /> Bio / Présentation
</label>
<textarea
name="bio"
value={formData.bio}
onChange={handleChange}
rows={4}
className="w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-brand-500 outline-none transition-all resize-none"
placeholder="Parlez-nous un peu de vous..."
/>
</div>
<div className="pt-4 border-t border-gray-100 flex justify-end">
<button
type="submit"
disabled={loading}
className="px-6 py-2.5 bg-brand-600 text-white font-bold rounded-lg hover:bg-brand-700 transition-all flex items-center gap-2 disabled:opacity-50"
>
{loading ? <Loader2 className="w-5 h-5 animate-spin" /> : <Save className="w-5 h-5" />}
Enregistrer les modifications
</button>
</div>
</form>
</div>
);
};
export default DashboardProfileClient;