import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, SafeAreaView, ScrollView, StatusBar, ActivityIndicator, Alert, Modal, RefreshControl, } from 'react-native'; import { LinearGradient } from 'expo-linear-gradient'; import { useNavigation } from '@react-navigation/native'; import { KurdistanColors } from '../theme/colors'; import { usePezkuwi } from '../contexts/PezkuwiContext'; import { fetchUserTikis } from '../../shared/lib/tiki'; import { getActiveElections, getElectionCandidates, getElectionResults, hasVoted, getCurrentBlock, blocksToTime, type ElectionInfo, type CandidateInfo, type ElectionResult, } from '../../shared/lib/welati'; type TabType = 'history' | 'active' | 'candidate'; // Presidential election constants (from pezpallet-welati) const PRESIDENTIAL_REQUIREMENTS = { minTrustScore: 600, minEndorsements: 1000, depositAmount: '100 PEZ', requiredTiki: 'Welati', }; const PresidentScreen: React.FC = () => { const navigation = useNavigation(); const { api, isApiReady, selectedAccount, getKeyPair } = usePezkuwi(); // Access control state const [hasWelatiTiki, setHasWelatiTiki] = useState(null); const [checkingAccess, setCheckingAccess] = useState(true); // Tab state const [activeTab, setActiveTab] = useState('active'); // Election data const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [currentBlock, setCurrentBlock] = useState(0); // Elections const [activeElections, setActiveElections] = useState([]); const [pastElections, setPastElections] = useState([]); const [candidates, setCandidates] = useState([]); const [selectedElection, setSelectedElection] = useState(null); const [userHasVoted, setUserHasVoted] = useState(false); // Candidate eligibility const [eligibilityStatus, setEligibilityStatus] = useState<{ eligible: boolean; trustScore: number; reasons: string[]; } | null>(null); const [checkingEligibility, setCheckingEligibility] = useState(false); // Voting modal const [showVoteModal, setShowVoteModal] = useState(false); const [selectedCandidate, setSelectedCandidate] = useState(null); const [submittingVote, setSubmittingVote] = useState(false); // Check welati tiki access useEffect(() => { const checkAccess = async () => { if (!api || !isApiReady || !selectedAccount) { setCheckingAccess(false); setHasWelatiTiki(false); return; } try { const tikis = await fetchUserTikis(api, selectedAccount.address); const hasWelati = tikis.includes('Welati'); setHasWelatiTiki(hasWelati); } catch (error) { if (__DEV__) console.error('[President] Error checking tiki:', error); setHasWelatiTiki(false); } finally { setCheckingAccess(false); } }; checkAccess(); }, [api, isApiReady, selectedAccount]); // Fetch election data const fetchElectionData = useCallback(async () => { if (!api || !isApiReady) return; try { setLoading(true); // Get current block const block = await getCurrentBlock(api); setCurrentBlock(block); // Get all elections const elections = await getActiveElections(api); // Filter presidential elections const presidentialElections = elections.filter(e => e.electionType === 'Presidential'); // Separate active and completed const active = presidentialElections.filter(e => e.status !== 'Completed'); const completed = presidentialElections.filter(e => e.status === 'Completed'); setActiveElections(active); // Get results for completed elections const results: ElectionResult[] = []; for (const election of completed) { const result = await getElectionResults(api, election.electionId); if (result) { results.push(result); } } setPastElections(results); // If there's an active election, get candidates if (active.length > 0) { const firstActive = active[0]; setSelectedElection(firstActive); const electionCandidates = await getElectionCandidates(api, firstActive.electionId); setCandidates(electionCandidates); // Check if user has voted if (selectedAccount) { const voted = await hasVoted(api, firstActive.electionId, selectedAccount.address); setUserHasVoted(voted); } } } catch (error) { if (__DEV__) console.error('[President] Error fetching elections:', error); } finally { setLoading(false); setRefreshing(false); } }, [api, isApiReady, selectedAccount]); useEffect(() => { if (hasWelatiTiki) { fetchElectionData(); } }, [hasWelatiTiki, fetchElectionData]); // Check candidate eligibility const checkCandidateEligibility = async () => { if (!api || !isApiReady || !selectedAccount) return; setCheckingEligibility(true); try { // Check trust score const trustScoreRaw = await api.query.trust?.trustScores?.(selectedAccount.address); const trustScore = trustScoreRaw ? Number(trustScoreRaw.toString()) : 0; const reasons: string[] = []; let eligible = true; // Check trust score requirement if (trustScore < PRESIDENTIAL_REQUIREMENTS.minTrustScore) { eligible = false; reasons.push(`Trust score must be at least ${PRESIDENTIAL_REQUIREMENTS.minTrustScore} (yours: ${trustScore})`); } // Check if already a candidate in active election if (selectedElection) { const isCandidate = candidates.some(c => c.account === selectedAccount.address); if (isCandidate) { eligible = false; reasons.push('You are already a candidate in this election'); } } // Check if there's an active election in candidacy period if (!selectedElection || selectedElection.status !== 'CandidacyPeriod') { eligible = false; reasons.push('No election is currently accepting candidates'); } if (eligible) { reasons.push('You meet all requirements to become a candidate!'); } setEligibilityStatus({ eligible, trustScore, reasons }); } catch (error) { if (__DEV__) console.error('[President] Error checking eligibility:', error); Alert.alert('Error', 'Failed to check eligibility'); } finally { setCheckingEligibility(false); } }; // Submit vote const handleVote = async () => { if (!api || !selectedAccount || !selectedElection || !selectedCandidate) return; setSubmittingVote(true); try { const keyPair = await getKeyPair(selectedAccount.address); if (!keyPair) { Alert.alert('Error', 'Could not retrieve key pair for signing'); return; } // Create vote transaction const tx = api.tx.welati.castVote( selectedElection.electionId, [selectedCandidate.account], null // No district for presidential ); // Sign and send await tx.signAndSend(keyPair, { nonce: -1 }, ({ status, events }) => { if (status.isInBlock || status.isFinalized) { // Check for success const success = events.some(({ event }) => api.events.system.ExtrinsicSuccess.is(event) ); if (success) { Alert.alert( 'Vote Submitted!', 'Your vote has been recorded on the blockchain.', [{ text: 'OK', onPress: () => { setShowVoteModal(false); setSelectedCandidate(null); setUserHasVoted(true); fetchElectionData(); }}] ); } else { Alert.alert('Error', 'Vote transaction failed'); } setSubmittingVote(false); } }); } catch (error: unknown) { if (__DEV__) console.error('[President] Vote error:', error); const errorMessage = error instanceof Error ? error.message : 'Failed to submit vote'; Alert.alert('Error', errorMessage); setSubmittingVote(false); } }; // Register as candidate const handleRegisterCandidate = async () => { if (!api || !selectedAccount || !selectedElection) return; Alert.alert( 'Register as Candidate', `To register as a presidential candidate, you need:\n\n` + `• Trust Score: ${PRESIDENTIAL_REQUIREMENTS.minTrustScore}+\n` + `• Endorsements: ${PRESIDENTIAL_REQUIREMENTS.minEndorsements}+\n` + `• Deposit: ${PRESIDENTIAL_REQUIREMENTS.depositAmount}\n\n` + `This will open the registration form.`, [ { text: 'Cancel', style: 'cancel' }, { text: 'Continue', onPress: () => { // TODO: Implement candidate registration form Alert.alert('Coming Soon', 'Candidate registration will be available soon.'); } } ] ); }; // Format time remaining const formatTimeRemaining = (endBlock: number) => { const remaining = endBlock - currentBlock; if (remaining <= 0) return 'Ended'; const time = blocksToTime(remaining); if (time.days > 0) return `${time.days}d ${time.hours}h`; if (time.hours > 0) return `${time.hours}h ${time.minutes}m`; return `${time.minutes}m`; }; // Format address const formatAddress = (address: string) => { return `${address.slice(0, 6)}...${address.slice(-4)}`; }; // Render access denied screen if (checkingAccess) { return ( Checking access... ); } if (!hasWelatiTiki) { return ( 🏛️ Citizenship Required Pêdivî ye ku hûn welatî bin da ku bikarin beşdarî hilbijartinê bibin You must be a citizen to participate in presidential elections. Please complete your citizenship application first. navigation.navigate('BeCitizen' as never)} > Become a Citizen ); } // Main content return ( {/* Tab Navigation */} setActiveTab('history')} > Dîrok History setActiveTab('active')} > Hilbijartin Elections setActiveTab('candidate')} > Berendam Candidate {/* Content */} { setRefreshing(true); fetchElectionData(); }} /> } > {loading ? ( Loading elections... ) : ( <> {/* History Tab */} {activeTab === 'history' && ( Past Presidential Elections {pastElections.length === 0 ? ( 📜 No past elections yet Presidential election history will appear here ) : ( pastElections.map((result, _index) => ( Election #{result.electionId} Completed Winner (Serok) 👑 {result.winners[0] ? formatAddress(result.winners[0]) : 'N/A'} {result.totalVotes} Total Votes {result.turnoutPercentage}% Turnout {result.runoffRequired ? 'Yes' : 'No'} Runoff )) )} )} {/* Active Elections Tab */} {activeTab === 'active' && ( {activeElections.length === 0 ? ( 🗳️ No active elections Presidential elections will appear here when initiated ) : ( <> {/* Election Info Card */} {selectedElection && ( Presidential Election #{selectedElection.electionId} {selectedElection.status === 'VotingPeriod' ? '🗳️ Voting Open' : selectedElection.status === 'CandidacyPeriod' ? '📝 Registration Open' : selectedElection.status === 'CampaignPeriod' ? '📢 Campaign Period' : 'Completed'} {selectedElection.totalCandidates} Candidates {selectedElection.totalVotes} Votes Cast {formatTimeRemaining(selectedElection.votingEndBlock)} Time Left {userHasVoted && ( ✓ You have voted )} )} {/* Candidates List */} Candidates {candidates.length === 0 ? ( No candidates registered yet ) : ( candidates.map((candidate, index) => ( { if (!userHasVoted && selectedElection?.status === 'VotingPeriod') { setSelectedCandidate(candidate); setShowVoteModal(true); } }} disabled={userHasVoted || selectedElection?.status !== 'VotingPeriod'} > {index === 0 ? '👑' : `#${index + 1}`} {formatAddress(candidate.account)} {candidate.endorsersCount} endorsements {candidate.voteCount} votes {!userHasVoted && selectedElection?.status === 'VotingPeriod' && ( Vote )} )) )} )} )} {/* Become Candidate Tab */} {activeTab === 'candidate' && ( Become a Presidential Candidate Bibin berendamê serokîtiyê Requirements: Welati Tiki (Citizenship) 📊 Trust Score: {PRESIDENTIAL_REQUIREMENTS.minTrustScore}+ {eligibilityStatus ? (eligibilityStatus.trustScore >= PRESIDENTIAL_REQUIREMENTS.minTrustScore ? '✅' : '❌') : '❓'} 👥 {PRESIDENTIAL_REQUIREMENTS.minEndorsements}+ Endorsements 💰 Deposit: {PRESIDENTIAL_REQUIREMENTS.depositAmount} {checkingEligibility ? ( ) : ( Check My Eligibility )} {eligibilityStatus && ( {eligibilityStatus.eligible ? '✅ You are eligible!' : '❌ Not eligible yet'} {eligibilityStatus.reasons.map((reason, i) => ( • {reason} ))} {eligibilityStatus.eligible && selectedElection?.status === 'CandidacyPeriod' && ( Register as Candidate )} )} )} )} {/* Vote Confirmation Modal */} setShowVoteModal(false)} > Confirm Your Vote Dengê xwe piştrast bikin {selectedCandidate && ( Voting for: {formatAddress(selectedCandidate.account)} )} ⚠️ Your vote cannot be changed after submission. { setShowVoteModal(false); setSelectedCandidate(null); }} > Cancel {submittingVote ? ( ) : ( Confirm Vote )} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F5F5F5', }, // Loading loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, loadingText: { color: KurdistanColors.spi, marginTop: 16, fontSize: 16, }, // Access Denied accessDeniedContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, accessDeniedContent: { backgroundColor: 'rgba(255,255,255,0.95)', borderRadius: 24, padding: 32, margin: 24, alignItems: 'center', boxShadow: '0px 8px 24px rgba(0, 0, 0, 0.2)', elevation: 10, }, accessDeniedIcon: { fontSize: 64, marginBottom: 16, }, accessDeniedTitle: { fontSize: 24, fontWeight: 'bold', color: KurdistanColors.reş, marginBottom: 8, }, accessDeniedSubtitle: { fontSize: 14, color: KurdistanColors.kesk, textAlign: 'center', marginBottom: 16, fontStyle: 'italic', }, accessDeniedText: { fontSize: 14, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22, }, becomeCitizenButton: { backgroundColor: KurdistanColors.kesk, paddingHorizontal: 32, paddingVertical: 14, borderRadius: 12, marginBottom: 12, }, becomeCitizenButtonText: { color: KurdistanColors.spi, fontSize: 16, fontWeight: 'bold', }, backButton: { padding: 12, }, backButtonText: { color: KurdistanColors.kesk, fontSize: 16, }, // Header header: { paddingTop: 20, paddingBottom: 30, paddingHorizontal: 20, position: 'relative', overflow: 'hidden', }, headerBackButton: { position: 'absolute', top: 20, left: 16, zIndex: 10, padding: 8, }, headerBackText: { fontSize: 24, color: KurdistanColors.spi, }, headerContent: { alignItems: 'center', marginTop: 10, }, headerIcon: { fontSize: 48, marginBottom: 8, }, headerTitle: { fontSize: 24, fontWeight: 'bold', color: KurdistanColors.spi, marginBottom: 4, }, headerSubtitle: { fontSize: 14, color: 'rgba(255,255,255,0.8)', }, // Sun decoration sunDecoration: { position: 'absolute', right: -30, top: -30, width: 120, height: 120, opacity: 0.2, }, sunCenter: { position: 'absolute', width: 40, height: 40, borderRadius: 20, backgroundColor: KurdistanColors.zer, left: 40, top: 40, }, sunRay: { position: 'absolute', width: 3, height: 60, backgroundColor: KurdistanColors.zer, left: 58, top: 0, transformOrigin: 'center bottom', }, // Tabs tabContainer: { flexDirection: 'row', backgroundColor: KurdistanColors.spi, paddingVertical: 8, paddingHorizontal: 8, borderBottomWidth: 1, borderBottomColor: '#E0E0E0', }, tab: { flex: 1, paddingVertical: 12, alignItems: 'center', borderRadius: 8, }, tabActive: { backgroundColor: `${KurdistanColors.kesk}15`, }, tabText: { fontSize: 14, fontWeight: '600', color: '#666', }, tabTextActive: { color: KurdistanColors.kesk, }, tabSubtext: { fontSize: 10, color: '#999', marginTop: 2, }, tabSubtextActive: { color: KurdistanColors.kesk, }, // Content content: { flex: 1, }, tabContent: { padding: 16, }, // Loading section loadingSection: { padding: 60, alignItems: 'center', }, loadingSectionText: { marginTop: 12, color: '#666', }, // Section title sectionTitle: { fontSize: 18, fontWeight: 'bold', color: KurdistanColors.reş, marginBottom: 16, }, // Empty state emptyState: { alignItems: 'center', padding: 40, }, emptyStateIcon: { fontSize: 64, marginBottom: 16, }, emptyStateText: { fontSize: 18, fontWeight: '600', color: '#666', marginBottom: 8, }, emptyStateSubtext: { fontSize: 14, color: '#999', textAlign: 'center', }, // History card historyCard: { backgroundColor: KurdistanColors.spi, borderRadius: 16, padding: 16, marginBottom: 16, boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.08)', elevation: 3, }, historyHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, }, historyTitle: { fontSize: 16, fontWeight: 'bold', color: KurdistanColors.reş, }, historyBadge: { backgroundColor: '#E8F5E9', paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, }, historyBadgeText: { color: KurdistanColors.kesk, fontSize: 12, fontWeight: '600', }, historyWinner: { backgroundColor: `${KurdistanColors.zer}20`, padding: 12, borderRadius: 12, marginBottom: 12, }, historyWinnerLabel: { fontSize: 12, color: '#666', marginBottom: 4, }, historyWinnerAddress: { fontSize: 16, fontWeight: 'bold', color: KurdistanColors.reş, }, historyStats: { flexDirection: 'row', justifyContent: 'space-around', }, historyStat: { alignItems: 'center', }, historyStatValue: { fontSize: 18, fontWeight: 'bold', color: KurdistanColors.kesk, }, historyStatLabel: { fontSize: 12, color: '#666', }, // Election card electionCard: { borderRadius: 16, overflow: 'hidden', marginBottom: 24, boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', elevation: 5, }, electionCardHeader: { padding: 20, alignItems: 'center', }, electionCardTitle: { fontSize: 18, fontWeight: 'bold', color: KurdistanColors.spi, marginBottom: 8, }, electionStatusBadge: { backgroundColor: 'rgba(255,255,255,0.2)', paddingHorizontal: 16, paddingVertical: 6, borderRadius: 20, }, electionStatusText: { color: KurdistanColors.spi, fontSize: 14, fontWeight: '600', }, electionCardBody: { backgroundColor: KurdistanColors.spi, padding: 20, }, electionStats: { flexDirection: 'row', justifyContent: 'space-around', }, electionStat: { alignItems: 'center', }, electionStatValue: { fontSize: 24, fontWeight: 'bold', color: KurdistanColors.kesk, }, electionStatLabel: { fontSize: 12, color: '#666', marginTop: 4, }, votedBadge: { backgroundColor: '#E8F5E9', padding: 12, borderRadius: 12, marginTop: 16, alignItems: 'center', }, votedBadgeText: { color: KurdistanColors.kesk, fontWeight: '600', }, // Candidates noCandidates: { padding: 24, alignItems: 'center', }, noCandidatesText: { color: '#999', fontSize: 14, }, candidateCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: KurdistanColors.spi, borderRadius: 12, padding: 16, marginBottom: 12, boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.05)', elevation: 2, }, candidateCardLeader: { borderWidth: 2, borderColor: KurdistanColors.zer, }, candidateRank: { width: 40, height: 40, borderRadius: 20, backgroundColor: '#F0F0F0', justifyContent: 'center', alignItems: 'center', marginRight: 12, }, candidateRankText: { fontSize: 16, fontWeight: 'bold', color: KurdistanColors.reş, }, candidateInfo: { flex: 1, }, candidateAddress: { fontSize: 14, fontWeight: '600', color: KurdistanColors.reş, fontFamily: 'monospace', }, candidateEndorsements: { fontSize: 12, color: '#666', marginTop: 2, }, candidateVotes: { alignItems: 'center', marginRight: 12, }, candidateVoteCount: { fontSize: 20, fontWeight: 'bold', color: KurdistanColors.kesk, }, candidateVoteLabel: { fontSize: 10, color: '#666', }, voteButton: { backgroundColor: KurdistanColors.kesk, paddingHorizontal: 16, paddingVertical: 8, borderRadius: 8, }, voteButtonText: { color: KurdistanColors.spi, fontWeight: 'bold', fontSize: 14, }, // Candidate info card candidateInfoCard: { backgroundColor: KurdistanColors.spi, borderRadius: 16, padding: 24, boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.08)', elevation: 4, }, candidateInfoTitle: { fontSize: 20, fontWeight: 'bold', color: KurdistanColors.reş, marginBottom: 4, }, candidateInfoSubtitle: { fontSize: 14, color: KurdistanColors.kesk, marginBottom: 24, fontStyle: 'italic', }, requirementsList: { marginBottom: 24, }, requirementsTitle: { fontSize: 16, fontWeight: '600', color: KurdistanColors.reş, marginBottom: 16, }, requirementItem: { flexDirection: 'row', alignItems: 'center', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#F0F0F0', }, requirementIcon: { fontSize: 20, marginRight: 12, }, requirementText: { flex: 1, fontSize: 14, color: KurdistanColors.reş, }, requirementStatus: { fontSize: 20, }, checkEligibilityButton: { backgroundColor: KurdistanColors.kesk, padding: 16, borderRadius: 12, alignItems: 'center', }, checkEligibilityText: { color: KurdistanColors.spi, fontSize: 16, fontWeight: 'bold', }, eligibilityResult: { marginTop: 20, padding: 16, borderRadius: 12, }, eligibilityResultSuccess: { backgroundColor: '#E8F5E9', }, eligibilityResultFail: { backgroundColor: '#FFEBEE', }, eligibilityResultTitle: { fontSize: 16, fontWeight: 'bold', marginBottom: 12, }, eligibilityResultReason: { fontSize: 14, color: '#666', marginBottom: 4, }, registerButton: { backgroundColor: KurdistanColors.zer, padding: 14, borderRadius: 10, alignItems: 'center', marginTop: 16, }, registerButtonText: { color: KurdistanColors.reş, fontSize: 16, fontWeight: 'bold', }, // Modal modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end', }, modalContent: { backgroundColor: KurdistanColors.spi, borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: 24, }, modalTitle: { fontSize: 22, fontWeight: 'bold', color: KurdistanColors.reş, textAlign: 'center', marginBottom: 4, }, modalSubtitle: { fontSize: 14, color: KurdistanColors.kesk, textAlign: 'center', marginBottom: 24, }, modalCandidate: { backgroundColor: '#F5F5F5', padding: 16, borderRadius: 12, marginBottom: 16, }, modalCandidateLabel: { fontSize: 12, color: '#666', marginBottom: 4, }, modalCandidateAddress: { fontSize: 16, fontWeight: 'bold', color: KurdistanColors.reş, fontFamily: 'monospace', }, modalWarning: { fontSize: 14, color: KurdistanColors.sor, textAlign: 'center', marginBottom: 24, }, modalButtons: { flexDirection: 'row', gap: 12, }, modalCancelButton: { flex: 1, padding: 16, borderRadius: 12, backgroundColor: '#F0F0F0', alignItems: 'center', }, modalCancelText: { fontSize: 16, fontWeight: '600', color: '#666', }, modalConfirmButton: { flex: 1, padding: 16, borderRadius: 12, backgroundColor: KurdistanColors.kesk, alignItems: 'center', }, modalConfirmText: { fontSize: 16, fontWeight: 'bold', color: KurdistanColors.spi, }, }); export default PresidentScreen;