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, ThumbsUp, ThumbsDown, Clock, RefreshCw } from 'lucide-react'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { COMMISSIONS } from '@/config/commissions'; interface Proposal { hash: string; proposalIndex: number; threshold: number; ayes: string[]; nays: string[]; end: number; call?: unknown; } export function CommissionVotingTab() { const { api, isApiReady, selectedAccount } = usePezkuwi(); const { toast } = useToast(); const [loading, setLoading] = useState(true); const [proposals, setProposals] = useState([]); const [voting, setVoting] = useState(null); const [isCommissionMember, setIsCommissionMember] = useState(false); useEffect(() => { if (!api || !isApiReady) { return; } checkMembership(); loadProposals(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [api, isApiReady, selectedAccount]); const checkMembership = async () => { if (!api || !selectedAccount) { if (import.meta.env.DEV) console.log('No API or selected account'); setIsCommissionMember(false); return; } try { if (import.meta.env.DEV) console.log('Checking membership for:', selectedAccount.address); // Check if user is directly a member of DynamicCommissionCollective const members = await api.query.dynamicCommissionCollective.members(); const memberList = members.toJSON() as string[]; if (import.meta.env.DEV) console.log('Commission members:', memberList); const isMember = memberList.includes(selectedAccount.address); if (import.meta.env.DEV) console.log('Is commission member:', isMember); setIsCommissionMember(isMember); } catch (error) { if (import.meta.env.DEV) console.error('Error checking membership:', error); setIsCommissionMember(false); } }; const loadProposals = async () => { if (!api || !isApiReady) { setLoading(false); return; } setLoading(true); try { // Get all active proposal hashes const proposalHashes = await api.query.dynamicCommissionCollective.proposals(); const proposalList: Proposal[] = []; for (let i = 0; i < proposalHashes.length; i++) { const hash = proposalHashes[i]; // Get voting info for this proposal const voting = await api.query.dynamicCommissionCollective.voting(hash); if (!voting.isEmpty) { const voteData = voting.unwrap(); // Get proposal details const proposalOption = await api.query.dynamicCommissionCollective.proposalOf(hash); let proposalCall = null; if (!proposalOption.isEmpty) { proposalCall = proposalOption.unwrap(); } // Get the actual proposal index from the chain const proposalIndex = (voteData as Record).index?.toNumber() || i; proposalList.push({ hash: hash.toHex(), proposalIndex: proposalIndex, threshold: voteData.threshold.toNumber(), ayes: voteData.ayes.map((a: { toString: () => string }) => a.toString()), nays: voteData.nays.map((n: { toString: () => string }) => n.toString()), end: voteData.end.toNumber(), call: proposalCall?.toHuman(), }); } } setProposals(proposalList); if (import.meta.env.DEV) console.log(`Loaded ${proposalList.length} active proposals`); } catch (error) { if (import.meta.env.DEV) console.error('Error loading proposals:', error); toast({ title: 'Error', description: 'Failed to load proposals', variant: 'destructive', }); } finally { setLoading(false); } }; const handleVote = async (proposal: Proposal, approve: boolean) => { if (!api || !selectedAccount) { toast({ title: 'Wallet Not Connected', description: 'Please connect your wallet first', variant: 'destructive', }); return; } if (!isCommissionMember) { toast({ title: 'Not a Commission Member', description: 'You are not a member of the KYC Approval Commission', variant: 'destructive', }); return; } setVoting(proposal.hash); try { const { web3FromAddress } = await import('@pezkuwi/extension-dapp'); const injector = await web3FromAddress(selectedAccount.address); if (import.meta.env.DEV) console.log(`Voting ${approve ? 'AYE' : 'NAY'} on proposal:`, proposal.hash); // Vote directly (no proxy needed) const tx = api.tx.dynamicCommissionCollective.vote( proposal.hash, proposal.proposalIndex, approve ); 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('Vote error:', errorMessage); toast({ title: 'Vote Failed', description: errorMessage, variant: 'destructive', }); reject(new Error(errorMessage)); return; } // Check for Voted event const votedEvent = events.find(({ event }) => event.section === 'dynamicCommissionCollective' && event.method === 'Voted' ); // Check for Executed event (threshold reached) const executedEvent = events.find(({ event }) => event.section === 'dynamicCommissionCollective' && event.method === 'Executed' ); if (executedEvent) { if (import.meta.env.DEV) console.log('✅ Proposal executed (threshold reached)'); toast({ title: 'Success', description: 'Proposal passed and executed! KYC approved.', }); } else if (votedEvent) { if (import.meta.env.DEV) console.log('✅ Vote recorded'); toast({ title: 'Vote Recorded', description: `Your ${approve ? 'AYE' : 'NAY'} vote has been recorded`, }); } 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 proposals after voting setTimeout(() => { loadProposals(); }, 2000); } catch (error) { if (import.meta.env.DEV) console.error('Error voting:', error); toast({ title: 'Error', description: error instanceof Error ? error.message : 'Failed to vote', variant: 'destructive', }); } finally { setVoting(null); } }; const handleExecute = async (proposal: Proposal) => { if (!api || !selectedAccount) { toast({ title: 'Wallet Not Connected', description: 'Please connect your wallet first', variant: 'destructive', }); return; } setVoting(proposal.hash); try { const { web3FromAddress } = await import('@pezkuwi/extension-dapp'); const injector = await web3FromAddress(selectedAccount.address); if (import.meta.env.DEV) console.log('Executing proposal:', proposal.hash); // Get proposal length bound const proposalOption = await api.query.dynamicCommissionCollective.proposalOf(proposal.hash); const proposalCall = proposalOption.unwrap(); const lengthBound = proposalCall.encodedLength; const tx = api.tx.dynamicCommissionCollective.close( proposal.hash, proposal.proposalIndex, { refTime: 1_000_000_000_000, // 1 trillion for ref time proofSize: 64 * 1024, // 64 KB for proof size }, lengthBound ); 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('Execute error:', errorMessage); toast({ title: 'Execute Failed', description: errorMessage, variant: 'destructive', }); reject(new Error(errorMessage)); return; } const executedEvent = events.find(({ event }) => event.section === 'dynamicCommissionCollective' && event.method === 'Executed' ); const closedEvent = events.find(({ event }) => event.section === 'dynamicCommissionCollective' && event.method === 'Closed' ); if (executedEvent) { const eventData = executedEvent.event.data.toHuman(); if (import.meta.env.DEV) console.log('✅ Proposal executed'); if (import.meta.env.DEV) console.log('Execute event data:', eventData); if (import.meta.env.DEV) console.log('Result:', eventData); // Check if execution was successful const result = eventData[eventData.length - 1]; // Last parameter is usually the result if (result && typeof result === 'object' && 'Err' in result) { if (import.meta.env.DEV) console.error('Execution failed:', result.Err); toast({ title: 'Execution Failed', description: `Proposal closed but execution failed: ${JSON.stringify(result.Err)}`, variant: 'destructive', }); } else { toast({ title: 'Proposal Executed!', description: 'KYC approved and NFT minted successfully!', }); } } else if (closedEvent) { if (import.meta.env.DEV) console.log('Proposal closed'); toast({ title: 'Proposal Closed', description: 'Proposal has been closed', }); } 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); }); }); setTimeout(() => { loadProposals(); }, 2000); } catch (error) { if (import.meta.env.DEV) console.error('Error executing:', error); toast({ title: 'Error', description: error instanceof Error ? error.message : 'Failed to execute proposal', variant: 'destructive', }); } finally { setVoting(null); } }; const getProposalDescription = (call: Record): string => { if (!call) return 'Unknown proposal'; try { const callStr = JSON.stringify(call); if (callStr.includes('approveKyc')) { return 'KYC Approval'; } if (callStr.includes('rejectKyc')) { return 'KYC Rejection'; } return 'Commission Action'; } catch { return 'Unknown proposal'; } }; const getStatusBadge = (proposal: Proposal) => { const progress = (proposal.ayes.length / proposal.threshold) * 100; if (proposal.ayes.length >= proposal.threshold) { return PASSED; } if (progress >= 50) { return VOTING ({progress.toFixed(0)}%); } return VOTING ({progress.toFixed(0)}%); }; if (!isApiReady) { return (
Connecting to blockchain...
); } if (!selectedAccount) { return (

Please connect your wallet to view commission proposals

); } if (!isCommissionMember) { const handleJoinCommission = async () => { if (!api || !selectedAccount) return; try { const { web3FromAddress } = await import('@pezkuwi/extension-dapp'); const injector = await web3FromAddress(selectedAccount.address); // Get current members const currentMembers = await api.query.dynamicCommissionCollective.members(); const memberList = (currentMembers.toJSON() as string[]) || []; // Add current user to members list if (!memberList.includes(selectedAccount.address)) { memberList.push(selectedAccount.address); } if (import.meta.env.DEV) console.log('Adding member to commission:', selectedAccount.address); if (import.meta.env.DEV) console.log('New member list:', memberList); // Use sudo to update members (requires sudo access) const tx = api.tx.sudo.sudo( api.tx.dynamicCommissionCollective.setMembers( memberList, null, memberList.length ) ); await tx.signAndSend( selectedAccount.address, { signer: injector.signer }, ({ status, dispatchError }) => { if (status.isInBlock || status.isFinalized) { if (dispatchError) { let errorMessage = 'Failed to join commission'; if (dispatchError.isModule) { const decoded = api.registry.findMetaError(dispatchError.asModule); errorMessage = `${decoded.section}.${decoded.name}`; } toast({ title: 'Error', description: errorMessage, variant: 'destructive', }); } else { toast({ title: 'Success', description: 'You have joined the KYC Commission!', }); setTimeout(() => checkMembership(), 2000); } } } ); } catch (error) { toast({ title: 'Error', description: error instanceof Error ? error.message : 'Failed to join commission', variant: 'destructive', }); } }; return (

You are not a member of the KYC Approval Commission

Only commission members can view and vote on proposals

); } return (

Commission Proposals

Active voting proposals for {COMMISSIONS.KYC.name}

{loading ? (
Loading proposals...
) : proposals.length === 0 ? (

No active proposals

Proposals will appear here when commission members create them

) : ( Active Proposals ({proposals.length}) Proposal Type Status Votes Actions {proposals.map((proposal) => ( #{proposal.proposalIndex} {getProposalDescription(proposal.call)} {getStatusBadge(proposal)}
{proposal.ayes.length} {proposal.nays.length} / {proposal.threshold}
{proposal.ayes.length >= proposal.threshold ? ( ) : ( <> )}
))}
)}
); }