299 lines
14 KiB
TypeScript
299 lines
14 KiB
TypeScript
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>
|
|
);
|
|
}
|