feat: implement role-based access control for KYC workflows and add permissions documentation
This commit is contained in:
@@ -7,9 +7,10 @@ import {
|
||||
Shield, Check, Clock, Bike, User, Phone,
|
||||
MapPin, FileText, Image, DollarSign, Wrench, Battery,
|
||||
CheckCircle, XCircle, ArrowLeft, Save, Printer, Send,
|
||||
MessageSquare, Edit, UserCheck, Wallet, Store, Globe, Calendar, Briefcase, Plus, Upload
|
||||
MessageSquare, Edit, UserCheck, Wallet, Store, Globe, Calendar, Briefcase, Plus, Upload, Lock
|
||||
} from 'lucide-react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { hasPermission, canApproveKycDocument, canRejectKycDocument, canMakeValidUser } from '@/lib/auth';
|
||||
|
||||
type ApplicationSource = 'app' | 'web' | 'walkin' | 'referral';
|
||||
type KYCType = 'biker' | 'investor' | 'swapstation' | 'merchant' | 'general';
|
||||
@@ -266,6 +267,18 @@ export default function KYCDetailPage() {
|
||||
const [rejectReason, setRejectReason] = useState('');
|
||||
const [uploadDocId, setUploadDocId] = useState<string | null>(null);
|
||||
|
||||
const [permApprove, setPermApprove] = useState(false);
|
||||
const [permReject, setPermReject] = useState(false);
|
||||
const [permMakeValid, setPermMakeValid] = useState(false);
|
||||
const [permDocUpload, setPermDocUpload] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setPermApprove(canApproveKycDocument());
|
||||
setPermReject(canRejectKycDocument());
|
||||
setPermMakeValid(canMakeValidUser());
|
||||
setPermDocUpload(hasPermission('kyc.doc_upload'));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const found = mockRequests.find(r => r.id === id);
|
||||
if (found) {
|
||||
@@ -425,7 +438,7 @@ export default function KYCDetailPage() {
|
||||
<>
|
||||
{/* Top Row on Mobile: Make [Type] Button */}
|
||||
<div className="flex gap-2 w-full sm:w-auto">
|
||||
{request.type === 'biker' && request.status !== 'approved' && (
|
||||
{permMakeValid && request.type === 'biker' && request.status !== 'approved' && (
|
||||
<button
|
||||
onClick={() => setShowApproveModal(true)}
|
||||
className="flex-1 sm:flex-none px-3 sm:px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700 flex items-center justify-center gap-2"
|
||||
@@ -433,7 +446,7 @@ export default function KYCDetailPage() {
|
||||
<Bike className="w-4 h-4" /> <span>Make Biker</span>
|
||||
</button>
|
||||
)}
|
||||
{request.type === 'investor' && request.status !== 'approved' && (
|
||||
{permMakeValid && request.type === 'investor' && request.status !== 'approved' && (
|
||||
<button
|
||||
onClick={() => {
|
||||
if (confirm('Approve this request and create investor profile?')) {
|
||||
@@ -446,7 +459,7 @@ export default function KYCDetailPage() {
|
||||
<DollarSign className="w-4 h-4" /> <span>Make Investor</span>
|
||||
</button>
|
||||
)}
|
||||
{request.type === 'swapstation' && request.status !== 'approved' && (
|
||||
{permMakeValid && request.type === 'swapstation' && request.status !== 'approved' && (
|
||||
<button
|
||||
onClick={() => {
|
||||
if (confirm('Approve this request and create shop profile?')) {
|
||||
@@ -459,7 +472,7 @@ export default function KYCDetailPage() {
|
||||
<Store className="w-4 h-4" /> <span>Make Shop</span>
|
||||
</button>
|
||||
)}
|
||||
{request.type === 'merchant' && request.status !== 'approved' && (
|
||||
{permMakeValid && request.type === 'merchant' && request.status !== 'approved' && (
|
||||
<button
|
||||
onClick={() => {
|
||||
if (confirm('Approve this request and create merchant profile?')) {
|
||||
@@ -472,6 +485,11 @@ export default function KYCDetailPage() {
|
||||
<User className="w-4 h-4" /> <span>Make Merchant</span>
|
||||
</button>
|
||||
)}
|
||||
{!permMakeValid && (
|
||||
<div className="flex items-center gap-1.5 px-3 sm:px-4 py-2 bg-slate-100 text-slate-400 rounded-lg text-xs">
|
||||
<Lock className="w-3 h-3" /> <span>Make Valid User</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Bottom Row on Mobile: Edit, Note, SMS */}
|
||||
@@ -663,15 +681,29 @@ export default function KYCDetailPage() {
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{doc.status === 'pending' && (
|
||||
<button onClick={() => openUploadModal(doc.id)} className="p-1 bg-amber-100 text-amber-600 rounded hover:bg-amber-200" title="Upload"><Upload className="w-4 h-4" /></button>
|
||||
<>
|
||||
{permDocUpload ? (
|
||||
<button onClick={() => openUploadModal(doc.id)} className="p-1 bg-amber-100 text-amber-600 rounded hover:bg-amber-200" title="Upload"><Upload className="w-4 h-4" /></button>
|
||||
) : (
|
||||
<span className="p-1 bg-amber-50 text-amber-300 rounded cursor-not-allowed" title="No permission"><Upload className="w-4 h-4" /></span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{(doc.status === 'uploaded' || doc.status === 'approved') && doc.imageUrl && (
|
||||
<button onClick={() => window.open(doc.imageUrl, '_blank')} className="p-1 bg-blue-100 text-blue-600 rounded hover:bg-blue-200" title="View"><Image className="w-4 h-4" /></button>
|
||||
)}
|
||||
{doc.status === 'uploaded' && (
|
||||
<>
|
||||
<button onClick={() => handleApproveDocument(doc.id)} className="p-1 bg-green-100 text-green-600 rounded hover:bg-green-200" title="Approve"><CheckCircle className="w-4 h-4" /></button>
|
||||
<button onClick={() => openRejectDocModal(doc.id)} className="p-1 bg-red-100 text-red-600 rounded hover:bg-red-200" title="Reject"><XCircle className="w-4 h-4" /></button>
|
||||
{permApprove ? (
|
||||
<button onClick={() => handleApproveDocument(doc.id)} className="p-1 bg-green-100 text-green-600 rounded hover:bg-green-200" title="Approve"><CheckCircle className="w-4 h-4" /></button>
|
||||
) : (
|
||||
<span className="p-1 bg-green-50 text-green-300 rounded cursor-not-allowed" title="No permission"><CheckCircle className="w-4 h-4" /></span>
|
||||
)}
|
||||
{permReject ? (
|
||||
<button onClick={() => openRejectDocModal(doc.id)} className="p-1 bg-red-100 text-red-600 rounded hover:bg-red-200" title="Reject"><XCircle className="w-4 h-4" /></button>
|
||||
) : (
|
||||
<span className="p-1 bg-red-50 text-red-300 rounded cursor-not-allowed" title="No permission"><XCircle className="w-4 h-4" /></span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{doc.status === 'approved' && <span className="text-xs px-2 py-1 bg-green-100 text-green-700 rounded-full">Approved</span>}
|
||||
|
||||
Reference in New Issue
Block a user