import { useState, useEffect } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { useToast } from '@/hooks/use-toast'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; import { Loader2, CheckCircle, XCircle, Clock, User, Mail, FileText, AlertTriangle } from 'lucide-react'; import { COMMISSIONS } from '@/config/commissions'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog'; import { Alert, AlertDescription } from '@/components/ui/alert'; interface PendingApplication { address: string; cids: string[]; notes: string; timestamp?: number; } interface IdentityInfo { name: string; email: string; } export function KycApprovalTab() { const { api, isApiReady, selectedAccount, connectWallet } = usePezkuwi(); const { toast } = useToast(); const [loading, setLoading] = useState(true); const [pendingApps, setPendingApps] = useState([]); const [identities, setIdentities] = useState>(new Map()); const [selectedApp, setSelectedApp] = useState(null); const [processing, setProcessing] = useState(false); const [showDetailsModal, setShowDetailsModal] = useState(false); // Load pending KYC applications useEffect(() => { if (!api || !isApiReady) { return; } loadPendingApplications(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [api, isApiReady]); const loadPendingApplications = async () => { if (!api || !isApiReady) { setLoading(false); return; } setLoading(true); try { // Get all pending applications const entries = await api.query.identityKyc.pendingKycApplications.entries(); const apps: PendingApplication[] = []; const identityMap = new Map(); for (const [key, value] of entries) { const address = key.args[0].toString(); const application = value.toJSON() as Record; // Get identity info for this address try { const identity = await api.query.identityKyc.identities(address); if (!identity.isEmpty) { const identityData = identity.toJSON() as Record; identityMap.set(address, { name: identityData.name || 'Unknown', email: identityData.email || 'No email' }); } } catch (err) { if (import.meta.env.DEV) console.error('Error fetching identity for', address, err); } apps.push({ address, cids: application.cids || [], notes: application.notes || 'No notes provided', timestamp: application.timestamp || Date.now() }); } setPendingApps(apps); setIdentities(identityMap); if (import.meta.env.DEV) console.log(`Loaded ${apps.length} pending KYC applications`); } catch (error) { if (import.meta.env.DEV) console.error('Error loading pending applications:', error); toast({ title: 'Error', description: 'Failed to load pending applications', variant: 'destructive', }); } finally { setLoading(false); } }; const handleApprove = async (application: PendingApplication) => { if (!api || !selectedAccount) { toast({ title: 'Wallet Not Connected', description: 'Please connect your admin wallet first', variant: 'destructive', }); return; } setProcessing(true); try { const { web3FromAddress } = await import('@pezkuwi/extension-dapp'); const injector = await web3FromAddress(selectedAccount.address); if (import.meta.env.DEV) console.log('Proposing KYC approval for:', application.address); if (import.meta.env.DEV) console.log('Commission member wallet:', selectedAccount.address); // Check if user is a member of DynamicCommissionCollective const members = await api.query.dynamicCommissionCollective.members(); const memberList = members.toJSON() as string[]; const isMember = memberList.includes(selectedAccount.address); if (!isMember) { toast({ title: 'Not a Commission Member', description: 'You are not a member of the KYC Approval Commission', variant: 'destructive', }); setProcessing(false); return; } if (import.meta.env.DEV) console.log('✅ User is commission member'); // Create proposal for KYC approval const proposal = api.tx.identityKyc.approveKyc(application.address); const lengthBound = proposal.encodedLength; // Create proposal directly (no proxy needed) if (import.meta.env.DEV) console.log('Creating commission proposal for KYC approval'); if (import.meta.env.DEV) console.log('Applicant:', application.address); if (import.meta.env.DEV) console.log('Threshold:', COMMISSIONS.KYC.threshold); const tx = api.tx.dynamicCommissionCollective.propose( COMMISSIONS.KYC.threshold, proposal, lengthBound ); if (import.meta.env.DEV) console.log('Transaction created:', tx.toHuman()); await new Promise((resolve, reject) => { tx.signAndSend( selectedAccount.address, { signer: injector.signer }, ({ status, dispatchError, events }) => { if (import.meta.env.DEV) console.log('Transaction status:', status.type); if (status.isInBlock || status.isFinalized) { if (dispatchError) { let errorMessage = 'Transaction failed'; if (dispatchError.isModule) { const decoded = api.registry.findMetaError(dispatchError.asModule); errorMessage = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`; } else { errorMessage = dispatchError.toString(); } if (import.meta.env.DEV) console.error('Approval error:', errorMessage); toast({ title: 'Approval Failed', description: errorMessage, variant: 'destructive', }); reject(new Error(errorMessage)); return; } // Check for Proposed event if (import.meta.env.DEV) console.log('All events:', events.map(e => `${e.event.section}.${e.event.method}`)); const proposedEvent = events.find(({ event }) => event.section === 'dynamicCommissionCollective' && event.method === 'Proposed' ); if (proposedEvent) { if (import.meta.env.DEV) console.log('✅ KYC Approval proposal created'); toast({ title: 'Proposal Created', description: `KYC approval proposed for ${application.address.slice(0, 8)}... Waiting for other commission members to vote.`, }); resolve(); } else { if (import.meta.env.DEV) console.warn('Transaction included but no Proposed event'); resolve(); } } } ).catch((error) => { if (import.meta.env.DEV) console.error('Failed to sign and send:', error); toast({ title: 'Transaction Error', description: error instanceof Error ? error.message : 'Failed to submit transaction', variant: 'destructive', }); reject(error); }); }); // Reload applications after approval setTimeout(() => { loadPendingApplications(); setShowDetailsModal(false); setSelectedApp(null); }, 2000); } catch (error) { if (import.meta.env.DEV) console.error('Error approving KYC:', error); toast({ title: 'Error', description: error instanceof Error ? error.message : 'Failed to approve KYC', variant: 'destructive', }); } finally { setProcessing(false); } }; const handleReject = async (application: PendingApplication) => { if (!api || !selectedAccount) { toast({ title: 'Wallet Not Connected', description: 'Please connect your admin wallet first', variant: 'destructive', }); return; } const confirmReject = window.confirm( `Are you sure you want to REJECT KYC for ${application.address}?\n\nThis will slash their deposit.` ); if (!confirmReject) return; setProcessing(true); try { const { web3FromAddress } = await import('@pezkuwi/extension-dapp'); const injector = await web3FromAddress(selectedAccount.address); if (import.meta.env.DEV) console.log('Rejecting KYC for:', application.address); const tx = api.tx.identityKyc.rejectKyc(application.address); await new Promise((resolve, reject) => { tx.signAndSend( selectedAccount.address, { signer: injector.signer }, ({ status, dispatchError, events }) => { if (status.isInBlock || status.isFinalized) { if (dispatchError) { let errorMessage = 'Transaction failed'; if (dispatchError.isModule) { const decoded = api.registry.findMetaError(dispatchError.asModule); errorMessage = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`; } else { errorMessage = dispatchError.toString(); } toast({ title: 'Rejection Failed', description: errorMessage, variant: 'destructive', }); reject(new Error(errorMessage)); return; } const rejectedEvent = events.find(({ event }) => event.section === 'identityKyc' && event.method === 'KycRejected' ); if (rejectedEvent) { toast({ title: 'Rejected', description: `KYC rejected for ${application.address.slice(0, 8)}...`, }); resolve(); } else { resolve(); } } } ).catch(reject); }); setTimeout(() => { loadPendingApplications(); setShowDetailsModal(false); setSelectedApp(null); }, 2000); } catch (error) { if (import.meta.env.DEV) console.error('Error rejecting KYC:', error); toast({ title: 'Error', description: error instanceof Error ? error.message : 'Failed to reject KYC', variant: 'destructive', }); } finally { setProcessing(false); } }; const openDetailsModal = (app: PendingApplication) => { setSelectedApp(app); setShowDetailsModal(true); }; if (!isApiReady) { return (
Connecting to blockchain...
); } if (!selectedAccount) { return ( Please connect your admin wallet to view and approve KYC applications. ); } return ( <> Pending KYC Applications {loading ? (
) : pendingApps.length === 0 ? (

No pending applications

All KYC applications have been processed

) : ( Applicant Name Email Documents Status Actions {pendingApps.map((app) => { const identity = identities.get(app.address); return ( {app.address.slice(0, 6)}...{app.address.slice(-4)}
{identity?.name || 'Loading...'}
{identity?.email || 'Loading...'}
{app.cids.length} CID(s) Pending
); })}
)}
{/* Details Modal */} KYC Application Details Review application before approving or rejecting {selectedApp && (

{selectedApp.address}

{identities.get(selectedApp.address)?.name || 'Unknown'}

{identities.get(selectedApp.address)?.email || 'No email'}

{selectedApp.timestamp ? new Date(selectedApp.timestamp).toLocaleString() : 'Unknown'}

{selectedApp.notes}

{selectedApp.cids.map((cid, index) => ( ))}
Important: Approving this application will:
  • Unreserve the applicant's deposit
  • Mint a Welati (Citizen) NFT automatically
  • Enable trust score tracking
  • Grant governance voting rights
)}
); } function Label({ children, className }: { children: React.ReactNode; className?: string }) { return ; }