feat: implement authentication system with email verification, user management dashboard, and administrative business controls
Some checks failed
Build and Push App / build (push) Failing after 1m9s

This commit is contained in:
2026-04-26 22:46:32 +02:00
parent 31fa2fcde3
commit 281fb764c4
19 changed files with 999 additions and 561 deletions

View File

@@ -0,0 +1,298 @@
import { prisma } from '@/lib/prisma';
import { getClients } from "@/app/actions/user";
import ToggleVerifyButton from '@/components/ToggleVerifyButton';
import FeaturedModal from '@/components/FeaturedModal';
import EntrepreneurActions from '@/components/EntrepreneurActions';
import PlanSelector from '@/components/PlanSelector';
import ClientActions from "@/components/ClientActions";
import ManualVerifyButton from "@/components/ManualVerifyButton";
import { ShieldAlert, ShieldX, ShieldCheck, Users, Briefcase, User, Mail, Phone, MapPin, MailCheck, MailX } from 'lucide-react';
import Link from 'next/link';
async function getBusinesses() {
return await prisma.business.findMany({
where: {
isActive: true
},
include: {
owner: true,
},
orderBy: {
createdAt: 'desc',
},
});
}
export default async function UsersPage({
searchParams
}: {
searchParams: Promise<{ tab?: string }>
}) {
const { tab = 'entrepreneurs' } = await searchParams;
// Data for Entrepreneurs
const businesses = await getBusinesses();
const pendingCount = businesses.filter((b: any) =>
!b.verified &&
!b.isSuspended &&
['BOOSTER', 'EMPIRE'].includes(b.plan)
).length;
// Data for Clients/Visitors
const clientsResult = await getClients();
const clients = (clientsResult.success ? (clientsResult.data || []) : []) as any[];
return (
<div>
<div className="mb-8">
<h1 className="text-3xl font-bold text-white mb-2">Gestion des Utilisateurs</h1>
<p className="text-slate-400">Gérer les entrepreneurs et les visiteurs de la plateforme.</p>
</div>
{/* Tabs */}
<div className="flex gap-2 mb-8 bg-slate-900/50 p-1 rounded-xl border border-slate-800 w-fit">
<Link
href="/users?tab=entrepreneurs"
className={`px-6 py-2 rounded-lg text-sm font-bold transition-all flex items-center gap-2 ${tab === 'entrepreneurs' ? 'bg-indigo-600 text-white shadow-lg' : 'text-slate-400 hover:text-white hover:bg-slate-800'}`}
>
<Briefcase className="w-4 h-4" />
Entrepreneurs ({businesses.length})
{pendingCount > 0 && (
<span className="bg-red-500 text-white text-[10px] px-1.5 py-0.5 rounded-full">
{pendingCount}
</span>
)}
</Link>
<Link
href="/users?tab=visiteurs"
className={`px-6 py-2 rounded-lg text-sm font-bold transition-all flex items-center gap-2 ${tab === 'visiteurs' ? 'bg-indigo-600 text-white shadow-lg' : 'text-slate-400 hover:text-white hover:bg-slate-800'}`}
>
<User className="w-4 h-4" />
Visiteurs ({clients.length})
</Link>
</div>
{tab === 'entrepreneurs' ? (
<div className="space-y-6">
{pendingCount > 0 && (
<div className="bg-blue-500/10 border border-blue-500/50 px-6 py-3 rounded-2xl flex items-center gap-4 animate-pulse max-w-fit">
<div className="w-10 h-10 bg-blue-500 rounded-xl flex items-center justify-center text-white">
<ShieldCheck className="w-6 h-6" />
</div>
<div>
<div className="text-blue-400 font-bold">{pendingCount} en attente</div>
<div className="text-xs text-blue-400/80 font-medium uppercase tracking-wider">Certifications Booster</div>
</div>
</div>
)}
<div className="card overflow-hidden p-0">
<table className="data-table">
<thead>
<tr>
<th>Entreprise</th>
<th>Catégorie</th>
<th>Propriétaire</th>
<th>Email</th>
<th>Statut</th>
<th className="text-center">Forfait</th>
<th className="text-center">Mise en avant</th>
<th className="text-right">Action</th>
</tr>
</thead>
<tbody>
{businesses.length === 0 ? (
<tr>
<td colSpan={8} className="text-center py-10 text-slate-500">
Aucun entrepreneur trouvé.
</td>
</tr>
) : (
businesses.map((business: any) => (
<tr key={business.id} className={(business.isSuspended || business.owner?.isSuspended) ? 'opacity-60 grayscale-[0.5]' : ''}>
<td>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-slate-800 flex items-center justify-center overflow-hidden">
{business.logoUrl ? (
<img src={business.logoUrl} alt={business.name} className="w-full h-full object-cover" />
) : (
<span className="text-xs text-slate-500">LOGO</span>
)}
</div>
<div>
<div className="font-semibold text-white">{business.name}</div>
<div className="text-xs text-slate-500">{business.location}</div>
</div>
</div>
</td>
<td>
<span className="text-sm text-slate-300">{business.category}</span>
</td>
<td>
<div className="text-sm text-white">{business.owner.name}</div>
<div className="text-xs text-slate-500">{business.owner.email}</div>
</td>
<td>
{business.owner.emailVerified ? (
<span className="flex items-center gap-1.5 text-[10px] text-emerald-500 font-bold uppercase">
<MailCheck className="w-3.5 h-3.5" /> Vérifié
</span>
) : (
<div className="flex flex-col gap-2">
<span className="flex items-center gap-1.5 text-[10px] text-rose-500 font-bold uppercase">
<MailX className="w-3.5 h-3.5" /> Non vérifié
</span>
<ManualVerifyButton userId={business.owner.id} />
</div>
)}
</td>
<td>
<div className="flex flex-col gap-1">
{business.owner?.isSuspended ? (
<span className="badge badge-error flex items-center gap-1">
<ShieldX className="w-3 h-3" /> Compte Suspendu
</span>
) : business.isSuspended ? (
<span className="badge bg-orange-500/10 text-orange-500 border border-orange-500/20 flex items-center gap-1">
<ShieldAlert className="w-3 h-3" /> Shop Masqué
</span>
) : business.verified ? (
<span className="badge badge-verified">Vérifié</span>
) : (
<span className="badge badge-pending">En attente</span>
)}
</div>
</td>
<td className="text-center">
<PlanSelector businessId={business.id} currentPlan={business.plan} />
</td>
<td className="text-center">
<FeaturedModal business={business} />
</td>
<td className="text-right">
<div className="flex items-center justify-end gap-3">
<ToggleVerifyButton id={business.id} verified={business.verified} />
<div className="h-8 w-[1px] bg-slate-800 mx-1" />
<EntrepreneurActions
businessId={business.id}
businessName={business.name}
userId={business.owner.id}
userName={business.owner.name}
isBusinessSuspended={!!business.isSuspended}
isUserSuspended={!!business.owner?.isSuspended}
/>
</div>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
) : (
<div className="card overflow-hidden">
<div className="overflow-x-auto">
<table className="data-table">
<thead>
<tr>
<th>Utilisateur</th>
<th>Contact</th>
<th>Vérification</th>
<th>Localisation</th>
<th>Statut</th>
<th className="text-right">Action</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800">
{clients.length === 0 ? (
<tr>
<td colSpan={6} className="py-12 text-center text-slate-500">
<div className="flex flex-col items-center gap-2">
<Users className="w-8 h-8 opacity-20" />
<p>Aucun visiteur trouvé.</p>
</div>
</td>
</tr>
) : (
clients.map((client: any) => (
<tr key={client.id} className={`hover:bg-slate-800/30 transition-colors ${client.isSuspended ? 'opacity-60 grayscale-[0.5]' : ''}`}>
<td className="py-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-slate-800 flex items-center justify-center text-slate-400 font-bold text-sm overflow-hidden border border-slate-700">
{client.avatar ? (
<img src={client.avatar} alt={client.name} className="w-full h-full object-cover" />
) : (
client.name.charAt(0)
)}
</div>
<div>
<p className="text-white font-semibold">{client.name}</p>
<p className="text-slate-500 text-xs">ID: {client.id.split('-')[0]}...</p>
</div>
</div>
</td>
<td>
<div className="space-y-1">
<div className="flex items-center gap-1.5 text-xs text-slate-300">
<Mail className="w-3 h-3 text-slate-500" />
{client.email}
</div>
{client.phone && (
<div className="flex items-center gap-1.5 text-xs text-slate-300">
<Phone className="w-3 h-3 text-slate-500" />
{client.phone}
</div>
)}
</div>
</td>
<td>
{client.emailVerified ? (
<span className="flex items-center gap-1.5 text-[10px] text-emerald-500 font-bold uppercase">
<MailCheck className="w-3.5 h-3.5" /> Vérifié
</span>
) : (
<div className="flex flex-col gap-2">
<span className="flex items-center gap-1.5 text-[10px] text-rose-500 font-bold uppercase">
<MailX className="w-3.5 h-3.5" /> Non vérifié
</span>
<ManualVerifyButton userId={client.id} />
</div>
)}
</td>
<td>
<div className="flex items-center gap-1.5 text-xs text-slate-400">
<MapPin className="w-3 h-3" />
{client.location || "Non spécifié"}
</div>
</td>
<td>
<div className="flex flex-col gap-1">
{client.isSuspended ? (
<>
<span className="badge badge-error">Compte Suspendu</span>
<span className="text-[10px] text-rose-500/70 truncate max-w-[150px]" title={client.suspensionReason}>
{client.suspensionReason}
</span>
</>
) : client.businesses ? (
<span className="badge badge-pending">Shop Inactif</span>
) : (
<span className="badge bg-slate-800 text-slate-400">Actif</span>
)}
</div>
</td>
<td className="text-right">
<ClientActions userId={client.id} userName={client.name} isSuspended={!!client.isSuspended} />
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
)}
</div>
);
}