diff --git a/mobile/src/screens/GovernanceScreen.tsx b/mobile/src/screens/GovernanceScreen.tsx index 8f515b09..52ad6baf 100644 --- a/mobile/src/screens/GovernanceScreen.tsx +++ b/mobile/src/screens/GovernanceScreen.tsx @@ -7,6 +7,8 @@ import { RefreshControl, Alert, Pressable, + TouchableOpacity, + FlatList, } from 'react-native'; import { usePolkadot } from '../contexts/PolkadotContext'; import { AppColors, KurdistanColors } from '../theme/colors'; @@ -31,6 +33,27 @@ interface Proposal { endBlock: number; } +interface Election { + id: number; + type: 'Presidential' | 'Parliamentary' | 'Constitutional Court'; + status: 'Registration' | 'Campaign' | 'Voting' | 'Completed'; + candidates: Candidate[]; + totalVotes: number; + endBlock: number; + currentBlock: number; +} + +interface Candidate { + id: string; + name: string; + votes: number; + percentage: number; + party?: string; + trustScore: number; +} + +type TabType = 'proposals' | 'elections' | 'parliament'; + /** * Governance Screen * View proposals, vote, participate in governance @@ -38,16 +61,22 @@ interface Proposal { */ export default function GovernanceScreen() { const { api, selectedAccount, isApiReady } = usePolkadot(); + const [activeTab, setActiveTab] = useState('proposals'); const [proposals, setProposals] = useState([]); + const [elections, setElections] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [selectedProposal, setSelectedProposal] = useState(null); + const [selectedElection, setSelectedElection] = useState(null); const [voteSheetVisible, setVoteSheetVisible] = useState(false); + const [electionSheetVisible, setElectionSheetVisible] = useState(false); const [voting, setVoting] = useState(false); + const [votedCandidates, setVotedCandidates] = useState([]); useEffect(() => { if (isApiReady && selectedAccount) { fetchProposals(); + fetchElections(); } }, [isApiReady, selectedAccount]); @@ -100,6 +129,43 @@ export default function GovernanceScreen() { } }; + const fetchElections = async () => { + try { + // Mock elections data + // In production, this would fetch from pallet-tiki or election pallet + const mockElections: Election[] = [ + { + id: 1, + type: 'Presidential', + status: 'Voting', + totalVotes: 45678, + endBlock: 1000000, + currentBlock: 995000, + candidates: [ + { id: '1', name: 'Candidate A', votes: 23456, percentage: 51.3, trustScore: 850 }, + { id: '2', name: 'Candidate B', votes: 22222, percentage: 48.7, trustScore: 780 } + ] + }, + { + id: 2, + type: 'Parliamentary', + status: 'Campaign', + totalVotes: 12340, + endBlock: 1200000, + currentBlock: 995000, + candidates: [ + { id: '3', name: 'Candidate C', votes: 5678, percentage: 46.0, party: 'Green Party', trustScore: 720 }, + { id: '4', name: 'Candidate D', votes: 4567, percentage: 37.0, party: 'Democratic Alliance', trustScore: 690 }, + { id: '5', name: 'Candidate E', votes: 2095, percentage: 17.0, party: 'Independent', trustScore: 650 } + ] + } + ]; + setElections(mockElections); + } catch (error) { + console.error('Error fetching elections:', error); + } + }; + const handleVote = async (approve: boolean) => { if (!selectedProposal) return; @@ -132,6 +198,46 @@ export default function GovernanceScreen() { } }; + const handleElectionVote = (candidateId: string) => { + if (!selectedElection) return; + + if (selectedElection.type === 'Parliamentary') { + // Multiple selection for Parliamentary + setVotedCandidates(prev => + prev.includes(candidateId) + ? prev.filter(id => id !== candidateId) + : [...prev, candidateId] + ); + } else { + // Single selection for Presidential + setVotedCandidates([candidateId]); + } + }; + + const submitElectionVote = async () => { + if (votedCandidates.length === 0) { + Alert.alert('Error', 'Please select at least one candidate'); + return; + } + + try { + setVoting(true); + // TODO: Submit votes to blockchain via pallet-tiki + // await api.tx.tiki.voteInElection(electionId, candidateIds).signAndSend(...) + + Alert.alert('Success', 'Your vote has been recorded!'); + setElectionSheetVisible(false); + setSelectedElection(null); + setVotedCandidates([]); + fetchElections(); + } catch (error: any) { + console.error('Election voting error:', error); + Alert.alert('Error', error.message || 'Failed to submit vote'); + } finally { + setVoting(false); + } + }; + if (loading && proposals.length === 0) { return ( @@ -144,6 +250,44 @@ export default function GovernanceScreen() { return ( + {/* Header */} + + Governance + + Participate in digital democracy + + + + {/* Tabs */} + + setActiveTab('proposals')} + > + + Proposals ({proposals.length}) + + + + setActiveTab('elections')} + > + + Elections ({elections.length}) + + + + setActiveTab('parliament')} + > + + Parliament + + + + { setRefreshing(true); fetchProposals(); + fetchElections(); }} /> } > - {/* Header */} - - Governance - - Participate in digital democracy - - - {/* Stats */} @@ -171,31 +308,97 @@ export default function GovernanceScreen() { Active Proposals - 1,234 - Total Voters + {elections.length} + Active Elections - {/* Proposals List */} - Active Proposals - {proposals.length === 0 ? ( - - No active proposals - - Check back later for new governance proposals - - - ) : ( - proposals.map((proposal) => ( - { - setSelectedProposal(proposal); - setVoteSheetVisible(true); - }} - /> - )) + {/* Tab Content */} + {activeTab === 'proposals' && ( + <> + Active Proposals + {proposals.length === 0 ? ( + + No active proposals + + Check back later for new governance proposals + + + ) : ( + proposals.map((proposal) => ( + { + setSelectedProposal(proposal); + setVoteSheetVisible(true); + }} + /> + )) + )} + + )} + + {activeTab === 'elections' && ( + <> + Active Elections + {elections.length === 0 ? ( + + No active elections + + Check back later for upcoming elections + + + ) : ( + elections.map((election) => ( + { + setSelectedElection(election); + setVotedCandidates([]); + setElectionSheetVisible(true); + }} + /> + )) + )} + + )} + + {activeTab === 'parliament' && ( + <> + Parliament Status + + + Active Members + 0 / 27 + + + Current Session + + + + Pending Votes + 5 + + + + Dîwan (Constitutional Court) + + + Active Judges + 0 / 9 + + + Pending Reviews + 3 + + + Recent Decisions + 12 + + + )} {/* Info Card */} @@ -265,10 +468,148 @@ export default function GovernanceScreen() { )} + + {/* Election Vote Bottom Sheet */} + setElectionSheetVisible(false)} + title={selectedElection ? `${selectedElection.type} Election` : 'Election'} + height={600} + > + {selectedElection && ( + + + + + {selectedElection.totalVotes.toLocaleString()} votes cast + + + + + {selectedElection.type === 'Parliamentary' + ? 'You can select multiple candidates' + : 'Select one candidate'} + + + {/* Candidates List */} + + {selectedElection.candidates.map((candidate) => ( + handleElectionVote(candidate.id)} + > + + + {candidate.name} + {candidate.party && ( + {candidate.party} + )} + + Trust Score: {candidate.trustScore} + + + + + {candidate.percentage.toFixed(1)}% + + + {candidate.votes.toLocaleString()} votes + + + + + + + {votedCandidates.includes(candidate.id) && ( + + ✓ Selected + + )} + + ))} + + + {/* Submit Vote Button */} +