|
|
|
|
@@ -592,57 +592,149 @@ export default function RequestsPage() {
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="divide-y divide-slate-50">
|
|
|
|
|
{/* Desktop Table View */}
|
|
|
|
|
<div className="hidden lg:block overflow-x-auto">
|
|
|
|
|
<table className="w-full text-left text-sm text-slate-600">
|
|
|
|
|
<thead className="bg-slate-50 text-slate-500 font-medium border-b border-slate-100">
|
|
|
|
|
<tr>
|
|
|
|
|
<th className="p-4">Applicant</th>
|
|
|
|
|
<th className="p-4">Contact & Location</th>
|
|
|
|
|
<th className="p-4">Application Details</th>
|
|
|
|
|
<th className="p-4">Status</th>
|
|
|
|
|
<th className="p-4 text-right">Actions</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody className="divide-y divide-slate-100">
|
|
|
|
|
{filteredRequests.map(request => {
|
|
|
|
|
const TypeIcon = typeIcons[request.type];
|
|
|
|
|
return (
|
|
|
|
|
<tr key={request.id} className="hover:bg-slate-50 transition-colors">
|
|
|
|
|
<td className="p-4 align-top">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center shrink-0">
|
|
|
|
|
<User className="w-5 h-5 text-slate-600" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="font-semibold text-slate-800">{request.name}</p>
|
|
|
|
|
<div className="flex items-center gap-2 mt-1">
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded-full ${typeColors[request.type]}`}>
|
|
|
|
|
<TypeIcon className="w-3 h-3" /> {request.type}
|
|
|
|
|
</span>
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded-full ${sourceColors[request.applicationSource]}`}>
|
|
|
|
|
<Phone className="w-3 h-3" /> {sourceLabels[request.applicationSource]}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4 align-top">
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<p className="flex items-center gap-1"><Phone className="w-3.5 h-3.5 text-slate-400" /> {request.phone}</p>
|
|
|
|
|
<p className="flex items-center gap-1"><MapPin className="w-3.5 h-3.5 text-slate-400" /> {request.location}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4 align-top">
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<p className="flex items-center gap-1"><Clock className="w-3.5 h-3.5 text-slate-400" /> {request.submittedAt}</p>
|
|
|
|
|
<p className="flex items-center gap-1"><FileText className="w-3.5 h-3.5 text-slate-400" /> Docs: {request.requiredDocuments.filter(d => d.status === 'uploaded' || d.status === 'approved').length}/{request.requiredDocuments.length}</p>
|
|
|
|
|
{request.type === 'biker' && request.verificationStage && (
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded-full bg-cyan-100 text-cyan-700`}>
|
|
|
|
|
<Check className="w-3 h-3" /> {stageLabels[request.verificationStage]}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{request.smsHistory.length > 0 && (
|
|
|
|
|
<p className="flex items-center gap-1 text-blue-600 text-xs">
|
|
|
|
|
<MessageSquare className="w-3 h-3" /> {request.smsHistory.length} msg
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4 align-top">
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${statusColors[request.status]}`}>
|
|
|
|
|
{request.status === 'pending' && <Clock className="w-3 h-3" />}
|
|
|
|
|
{request.status === 'documents_needed' && <AlertCircle className="w-3 h-3" />}
|
|
|
|
|
{request.status === 'under_review' && <Eye className="w-3 h-3" />}
|
|
|
|
|
{request.status === 'approved' && <Check className="w-3 h-3" />}
|
|
|
|
|
{request.status === 'rejected' && <X className="w-3 h-3" />}
|
|
|
|
|
{statusLabels[request.status]}
|
|
|
|
|
</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4 align-top text-right">
|
|
|
|
|
<div className="flex justify-end gap-2">
|
|
|
|
|
{request.status !== 'approved' && request.status !== 'rejected' && (
|
|
|
|
|
<>
|
|
|
|
|
{request.type === 'biker' && request.status === 'under_review' && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleAction(request, 'approve')}
|
|
|
|
|
className="py-1.5 px-3 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center gap-1"
|
|
|
|
|
>
|
|
|
|
|
<Check className="w-3 h-3" /> Make Biker
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
{request.type === 'investor' && request.status === 'under_review' && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleAction(request, 'approve')}
|
|
|
|
|
className="py-1.5 px-3 bg-purple-600 text-white text-xs font-semibold rounded-lg hover:bg-purple-700 flex items-center gap-1"
|
|
|
|
|
>
|
|
|
|
|
<DollarSign className="w-3 h-3" /> Make Investor
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
{request.type === 'swapstation' && request.status === 'under_review' && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleAction(request, 'approve')}
|
|
|
|
|
className="py-1.5 px-3 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center gap-1"
|
|
|
|
|
>
|
|
|
|
|
<Store className="w-3 h-3" /> Approve Shop
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
<Link
|
|
|
|
|
href={`/admin/kyc/${request.id}`}
|
|
|
|
|
className="py-1.5 px-3 border border-slate-200 text-slate-600 text-xs font-semibold rounded-lg hover:bg-slate-50 flex items-center gap-1 inline-flex"
|
|
|
|
|
>
|
|
|
|
|
<Eye className="w-3 h-3" /> Details
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
{filteredRequests.length === 0 && (
|
|
|
|
|
<tr>
|
|
|
|
|
<td colSpan={5} className="p-12 text-center">
|
|
|
|
|
<Users className="w-12 h-12 text-slate-300 mx-auto mb-4" />
|
|
|
|
|
<p className="text-slate-500">No requests found</p>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
)}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Mobile/Tablet Card View */}
|
|
|
|
|
<div className="lg:hidden p-4 space-y-4 bg-slate-50/50">
|
|
|
|
|
{filteredRequests.map(request => {
|
|
|
|
|
const TypeIcon = typeIcons[request.type];
|
|
|
|
|
return (
|
|
|
|
|
<Link key={request.id} href={`/admin/kyc/${request.id}`} className="block p-5 hover:bg-slate-50 transition-colors">
|
|
|
|
|
<div className="flex flex-col lg:flex-row lg:items-start gap-4">
|
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
<div className="w-12 h-12 rounded-full bg-slate-100 flex items-center justify-center">
|
|
|
|
|
<User className="w-6 h-6 text-slate-600" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<p className="font-semibold text-slate-800">{request.name}</p>
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${typeColors[request.type]}`}>
|
|
|
|
|
<TypeIcon className="w-3 h-3" /> {request.type}
|
|
|
|
|
</span>
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${sourceColors[request.applicationSource]}`}>
|
|
|
|
|
<Phone className="w-3 h-3" /> {sourceLabels[request.applicationSource]}
|
|
|
|
|
</span>
|
|
|
|
|
<div key={request.id} className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
|
|
|
<div className="p-4 border-b border-slate-100">
|
|
|
|
|
<div className="flex justify-between items-start mb-3">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center shrink-0">
|
|
|
|
|
<User className="w-5 h-5 text-slate-600" />
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-sm text-slate-500 flex items-center gap-2">
|
|
|
|
|
<Phone className="w-3 h-3" /> {request.phone}
|
|
|
|
|
<span className="text-slate-300">|</span>
|
|
|
|
|
<MapPin className="w-3 h-3" /> {request.location}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
<div className="flex flex-wrap gap-4 text-sm text-slate-600">
|
|
|
|
|
<p className="flex items-center gap-1">
|
|
|
|
|
<Clock className="w-3 h-3" /> {request.submittedAt}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="flex items-center gap-1">
|
|
|
|
|
<FileText className="w-3 h-3" /> Docs: {request.requiredDocuments.filter(d => d.status === 'uploaded' || d.status === 'approved').length}/{request.requiredDocuments.length}
|
|
|
|
|
</p>
|
|
|
|
|
{request.type === 'biker' && request.verificationStage && (
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-cyan-100 text-cyan-700`}>
|
|
|
|
|
<Check className="w-3 h-3" /> {stageLabels[request.verificationStage]}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{request.smsHistory.length > 0 && (
|
|
|
|
|
<p className="flex items-center gap-1 text-blue-600">
|
|
|
|
|
<MessageSquare className="w-3 h-3" /> {request.smsHistory.length} msg
|
|
|
|
|
<div>
|
|
|
|
|
<p className="font-semibold text-slate-800">{request.name}</p>
|
|
|
|
|
<p className="text-xs text-slate-500 flex items-center gap-1 mt-0.5">
|
|
|
|
|
<Phone className="w-3 h-3" /> {request.phone}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${statusColors[request.status]}`}>
|
|
|
|
|
<div className="flex flex-wrap gap-2 mt-2">
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-[10px] font-medium px-2 py-0.5 rounded-full ${statusColors[request.status]}`}>
|
|
|
|
|
{request.status === 'pending' && <Clock className="w-3 h-3" />}
|
|
|
|
|
{request.status === 'documents_needed' && <AlertCircle className="w-3 h-3" />}
|
|
|
|
|
{request.status === 'under_review' && <Eye className="w-3 h-3" />}
|
|
|
|
|
@@ -650,51 +742,78 @@ export default function RequestsPage() {
|
|
|
|
|
{request.status === 'rejected' && <X className="w-3 h-3" />}
|
|
|
|
|
{statusLabels[request.status]}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{request.status !== 'approved' && request.status !== 'rejected' && (
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
{request.type === 'biker' && request.status === 'under_review' && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleAction(request, 'approve')}
|
|
|
|
|
className="py-1.5 px-3 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center gap-1"
|
|
|
|
|
>
|
|
|
|
|
<Check className="w-3 h-3" /> Make Biker
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
{request.type === 'investor' && request.status === 'under_review' && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleAction(request, 'approve')}
|
|
|
|
|
className="py-1.5 px-3 bg-purple-600 text-white text-xs font-semibold rounded-lg hover:bg-purple-700 flex items-center gap-1"
|
|
|
|
|
>
|
|
|
|
|
<DollarSign className="w-3 h-3" /> Make Investor
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
{request.type === 'swapstation' && request.status === 'under_review' && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleAction(request, 'approve')}
|
|
|
|
|
className="py-1.5 px-3 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center gap-1"
|
|
|
|
|
>
|
|
|
|
|
<Store className="w-3 h-3" /> Approve Shop
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
<button
|
|
|
|
|
onClick={(e) => { e.preventDefault(); router.push(`/admin/kyc/${request.id}`); }}
|
|
|
|
|
className="py-1.5 px-3 border border-slate-200 text-slate-600 text-xs font-semibold rounded-lg hover:bg-slate-50 flex items-center gap-1"
|
|
|
|
|
>
|
|
|
|
|
<Eye className="w-3 h-3" /> Details
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-[10px] font-medium px-2 py-0.5 rounded-full ${typeColors[request.type]}`}>
|
|
|
|
|
<TypeIcon className="w-3 h-3" /> {request.type}
|
|
|
|
|
</span>
|
|
|
|
|
{request.type === 'biker' && request.verificationStage && (
|
|
|
|
|
<span className={`inline-flex items-center gap-1 text-[10px] font-medium px-2 py-0.5 rounded-full bg-cyan-100 text-cyan-700`}>
|
|
|
|
|
<Check className="w-3 h-3" /> {stageLabels[request.verificationStage]}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Link>
|
|
|
|
|
|
|
|
|
|
<div className="p-4 space-y-2 text-sm text-slate-600">
|
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
|
<span className="flex items-center gap-1 text-xs"><MapPin className="w-3.5 h-3.5 text-slate-400" /> {request.location}</span>
|
|
|
|
|
<span className="flex items-center gap-1 text-xs"><Clock className="w-3.5 h-3.5 text-slate-400" /> {request.submittedAt}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
|
<span className="flex items-center gap-1 text-xs">
|
|
|
|
|
<FileText className="w-3.5 h-3.5 text-slate-400" /> Docs: {request.requiredDocuments.filter(d => d.status === 'uploaded' || d.status === 'approved').length}/{request.requiredDocuments.length}
|
|
|
|
|
</span>
|
|
|
|
|
{request.smsHistory.length > 0 && (
|
|
|
|
|
<span className="flex items-center gap-1 text-blue-600 text-xs">
|
|
|
|
|
<MessageSquare className="w-3.5 h-3.5" /> {request.smsHistory.length} msgs
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="p-3 border-t border-slate-100 bg-slate-50 flex gap-2">
|
|
|
|
|
{request.status !== 'approved' && request.status !== 'rejected' && (
|
|
|
|
|
<>
|
|
|
|
|
{request.type === 'biker' && request.status === 'under_review' && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleAction(request, 'approve')}
|
|
|
|
|
className="flex-1 py-2 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center justify-center gap-1"
|
|
|
|
|
>
|
|
|
|
|
<Check className="w-3 h-3" /> Approve
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
{request.type === 'investor' && request.status === 'under_review' && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleAction(request, 'approve')}
|
|
|
|
|
className="flex-1 py-2 bg-purple-600 text-white text-xs font-semibold rounded-lg hover:bg-purple-700 flex items-center justify-center gap-1"
|
|
|
|
|
>
|
|
|
|
|
<DollarSign className="w-3 h-3" /> Approve
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
{request.type === 'swapstation' && request.status === 'under_review' && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleAction(request, 'approve')}
|
|
|
|
|
className="flex-1 py-2 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center justify-center gap-1"
|
|
|
|
|
>
|
|
|
|
|
<Store className="w-3 h-3" /> Approve
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
<Link
|
|
|
|
|
href={`/admin/kyc/${request.id}`}
|
|
|
|
|
className="flex-1 py-2 border border-slate-200 text-slate-700 bg-white text-xs font-semibold rounded-lg hover:bg-slate-50 flex items-center justify-center gap-1 text-center"
|
|
|
|
|
>
|
|
|
|
|
<Eye className="w-3 h-3" /> Details
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
|
|
{filteredRequests.length === 0 && (
|
|
|
|
|
<div className="p-12 text-center">
|
|
|
|
|
<Users className="w-12 h-12 text-slate-300 mx-auto mb-4" />
|
|
|
|
|
<p className="text-slate-500">No requests found</p>
|
|
|
|
|
<div className="p-8 text-center bg-white rounded-xl border border-slate-200">
|
|
|
|
|
<Users className="w-12 h-12 text-slate-300 mx-auto mb-3" />
|
|
|
|
|
<p className="text-slate-500 text-sm">No requests found</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|