Files
pwap/mobile/src/screens/PresidentScreen.tsx
T

1359 lines
40 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useCallback } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
SafeAreaView,
ScrollView,
StatusBar,
ActivityIndicator,
Alert,
FlatList,
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<boolean | null>(null);
const [checkingAccess, setCheckingAccess] = useState(true);
// Tab state
const [activeTab, setActiveTab] = useState<TabType>('active');
// Election data
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [currentBlock, setCurrentBlock] = useState(0);
// Elections
const [activeElections, setActiveElections] = useState<ElectionInfo[]>([]);
const [pastElections, setPastElections] = useState<ElectionResult[]>([]);
const [candidates, setCandidates] = useState<CandidateInfo[]>([]);
const [selectedElection, setSelectedElection] = useState<ElectionInfo | null>(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<CandidateInfo | null>(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: any) {
if (__DEV__) console.error('[President] Vote error:', error);
Alert.alert('Error', error.message || 'Failed to submit vote');
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 (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="light-content" />
<LinearGradient colors={[KurdistanColors.kesk, '#006633']} style={styles.loadingContainer}>
<ActivityIndicator size="large" color={KurdistanColors.spi} />
<Text style={styles.loadingText}>Checking access...</Text>
</LinearGradient>
</SafeAreaView>
);
}
if (!hasWelatiTiki) {
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="light-content" />
<LinearGradient
colors={[KurdistanColors.kesk, KurdistanColors.zer, KurdistanColors.sor]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.accessDeniedContainer}
>
<View style={styles.accessDeniedContent}>
<Text style={styles.accessDeniedIcon}>🏛</Text>
<Text style={styles.accessDeniedTitle}>Citizenship Required</Text>
<Text style={styles.accessDeniedSubtitle}>
Pêdivî ye ku hûn welatî bin da ku bikarin beşdarî hilbijartinê bibin
</Text>
<Text style={styles.accessDeniedText}>
You must be a citizen to participate in presidential elections.
Please complete your citizenship application first.
</Text>
<TouchableOpacity
style={styles.becomeCitizenButton}
onPress={() => navigation.navigate('BeCitizen' as never)}
>
<Text style={styles.becomeCitizenButtonText}>Become a Citizen</Text>
</TouchableOpacity>
</View>
</LinearGradient>
</SafeAreaView>
);
}
// Main content
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="light-content" />
{/* Header */}
<LinearGradient
colors={[KurdistanColors.kesk, '#006633']}
style={styles.header}
>
<View style={styles.headerContent}>
<Text style={styles.headerIcon}>👑</Text>
<Text style={styles.headerTitle}>Serokî / President</Text>
<Text style={styles.headerSubtitle}>Hilbijartina Serokê Komarê</Text>
</View>
{/* Kurdistan Sun decoration */}
<View style={styles.sunDecoration}>
<View style={styles.sunCenter} />
{[...Array(21)].map((_, i) => (
<View
key={i}
style={[
styles.sunRay,
{ transform: [{ rotate: `${i * (360 / 21)}deg` }] }
]}
/>
))}
</View>
</LinearGradient>
{/* Tab Navigation */}
<View style={styles.tabContainer}>
<TouchableOpacity
style={[styles.tab, activeTab === 'history' && styles.tabActive]}
onPress={() => setActiveTab('history')}
>
<Text style={[styles.tabText, activeTab === 'history' && styles.tabTextActive]}>
Dîrok
</Text>
<Text style={[styles.tabSubtext, activeTab === 'history' && styles.tabSubtextActive]}>
History
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, activeTab === 'active' && styles.tabActive]}
onPress={() => setActiveTab('active')}
>
<Text style={[styles.tabText, activeTab === 'active' && styles.tabTextActive]}>
Hilbijartin
</Text>
<Text style={[styles.tabSubtext, activeTab === 'active' && styles.tabSubtextActive]}>
Elections
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, activeTab === 'candidate' && styles.tabActive]}
onPress={() => setActiveTab('candidate')}
>
<Text style={[styles.tabText, activeTab === 'candidate' && styles.tabTextActive]}>
Berendam
</Text>
<Text style={[styles.tabSubtext, activeTab === 'candidate' && styles.tabSubtextActive]}>
Candidate
</Text>
</TouchableOpacity>
</View>
{/* Content */}
<ScrollView
style={styles.content}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={() => {
setRefreshing(true);
fetchElectionData();
}} />
}
>
{loading ? (
<View style={styles.loadingSection}>
<ActivityIndicator size="large" color={KurdistanColors.kesk} />
<Text style={styles.loadingSectionText}>Loading elections...</Text>
</View>
) : (
<>
{/* History Tab */}
{activeTab === 'history' && (
<View style={styles.tabContent}>
<Text style={styles.sectionTitle}>Past Presidential Elections</Text>
{pastElections.length === 0 ? (
<View style={styles.emptyState}>
<Text style={styles.emptyStateIcon}>📜</Text>
<Text style={styles.emptyStateText}>No past elections yet</Text>
<Text style={styles.emptyStateSubtext}>
Presidential election history will appear here
</Text>
</View>
) : (
pastElections.map((result, index) => (
<View key={result.electionId} style={styles.historyCard}>
<View style={styles.historyHeader}>
<Text style={styles.historyTitle}>Election #{result.electionId}</Text>
<View style={styles.historyBadge}>
<Text style={styles.historyBadgeText}>Completed</Text>
</View>
</View>
<View style={styles.historyWinner}>
<Text style={styles.historyWinnerLabel}>Winner (Serok)</Text>
<Text style={styles.historyWinnerAddress}>
👑 {result.winners[0] ? formatAddress(result.winners[0]) : 'N/A'}
</Text>
</View>
<View style={styles.historyStats}>
<View style={styles.historyStat}>
<Text style={styles.historyStatValue}>{result.totalVotes}</Text>
<Text style={styles.historyStatLabel}>Total Votes</Text>
</View>
<View style={styles.historyStat}>
<Text style={styles.historyStatValue}>{result.turnoutPercentage}%</Text>
<Text style={styles.historyStatLabel}>Turnout</Text>
</View>
<View style={styles.historyStat}>
<Text style={styles.historyStatValue}>
{result.runoffRequired ? 'Yes' : 'No'}
</Text>
<Text style={styles.historyStatLabel}>Runoff</Text>
</View>
</View>
</View>
))
)}
</View>
)}
{/* Active Elections Tab */}
{activeTab === 'active' && (
<View style={styles.tabContent}>
{activeElections.length === 0 ? (
<View style={styles.emptyState}>
<Text style={styles.emptyStateIcon}>🗳</Text>
<Text style={styles.emptyStateText}>No active elections</Text>
<Text style={styles.emptyStateSubtext}>
Presidential elections will appear here when initiated
</Text>
</View>
) : (
<>
{/* Election Info Card */}
{selectedElection && (
<View style={styles.electionCard}>
<LinearGradient
colors={[KurdistanColors.kesk, '#008844']}
style={styles.electionCardHeader}
>
<Text style={styles.electionCardTitle}>
Presidential Election #{selectedElection.electionId}
</Text>
<View style={styles.electionStatusBadge}>
<Text style={styles.electionStatusText}>
{selectedElection.status === 'VotingPeriod' ? '🗳️ Voting Open' :
selectedElection.status === 'CandidacyPeriod' ? '📝 Registration Open' :
selectedElection.status === 'CampaignPeriod' ? '📢 Campaign Period' :
'Completed'}
</Text>
</View>
</LinearGradient>
<View style={styles.electionCardBody}>
<View style={styles.electionStats}>
<View style={styles.electionStat}>
<Text style={styles.electionStatValue}>
{selectedElection.totalCandidates}
</Text>
<Text style={styles.electionStatLabel}>Candidates</Text>
</View>
<View style={styles.electionStat}>
<Text style={styles.electionStatValue}>
{selectedElection.totalVotes}
</Text>
<Text style={styles.electionStatLabel}>Votes Cast</Text>
</View>
<View style={styles.electionStat}>
<Text style={styles.electionStatValue}>
{formatTimeRemaining(selectedElection.votingEndBlock)}
</Text>
<Text style={styles.electionStatLabel}>Time Left</Text>
</View>
</View>
{userHasVoted && (
<View style={styles.votedBadge}>
<Text style={styles.votedBadgeText}> You have voted</Text>
</View>
)}
</View>
</View>
)}
{/* Candidates List */}
<Text style={styles.sectionTitle}>Candidates</Text>
{candidates.length === 0 ? (
<View style={styles.noCandidates}>
<Text style={styles.noCandidatesText}>
No candidates registered yet
</Text>
</View>
) : (
candidates.map((candidate, index) => (
<TouchableOpacity
key={candidate.account}
style={[
styles.candidateCard,
index === 0 && styles.candidateCardLeader
]}
onPress={() => {
if (!userHasVoted && selectedElection?.status === 'VotingPeriod') {
setSelectedCandidate(candidate);
setShowVoteModal(true);
}
}}
disabled={userHasVoted || selectedElection?.status !== 'VotingPeriod'}
>
<View style={styles.candidateRank}>
<Text style={styles.candidateRankText}>
{index === 0 ? '👑' : `#${index + 1}`}
</Text>
</View>
<View style={styles.candidateInfo}>
<Text style={styles.candidateAddress}>
{formatAddress(candidate.account)}
</Text>
<Text style={styles.candidateEndorsements}>
{candidate.endorsersCount} endorsements
</Text>
</View>
<View style={styles.candidateVotes}>
<Text style={styles.candidateVoteCount}>{candidate.voteCount}</Text>
<Text style={styles.candidateVoteLabel}>votes</Text>
</View>
{!userHasVoted && selectedElection?.status === 'VotingPeriod' && (
<View style={styles.voteButton}>
<Text style={styles.voteButtonText}>Vote</Text>
</View>
)}
</TouchableOpacity>
))
)}
</>
)}
</View>
)}
{/* Become Candidate Tab */}
{activeTab === 'candidate' && (
<View style={styles.tabContent}>
<View style={styles.candidateInfoCard}>
<Text style={styles.candidateInfoTitle}>Become a Presidential Candidate</Text>
<Text style={styles.candidateInfoSubtitle}>
Bibin berendamê serokîtiyê
</Text>
<View style={styles.requirementsList}>
<Text style={styles.requirementsTitle}>Requirements:</Text>
<View style={styles.requirementItem}>
<Text style={styles.requirementIcon}></Text>
<Text style={styles.requirementText}>
Welati Tiki (Citizenship)
</Text>
<Text style={styles.requirementStatus}></Text>
</View>
<View style={styles.requirementItem}>
<Text style={styles.requirementIcon}>📊</Text>
<Text style={styles.requirementText}>
Trust Score: {PRESIDENTIAL_REQUIREMENTS.minTrustScore}+
</Text>
<Text style={styles.requirementStatus}>
{eligibilityStatus ?
(eligibilityStatus.trustScore >= PRESIDENTIAL_REQUIREMENTS.minTrustScore ? '✅' : '❌')
: '❓'}
</Text>
</View>
<View style={styles.requirementItem}>
<Text style={styles.requirementIcon}>👥</Text>
<Text style={styles.requirementText}>
{PRESIDENTIAL_REQUIREMENTS.minEndorsements}+ Endorsements
</Text>
<Text style={styles.requirementStatus}></Text>
</View>
<View style={styles.requirementItem}>
<Text style={styles.requirementIcon}>💰</Text>
<Text style={styles.requirementText}>
Deposit: {PRESIDENTIAL_REQUIREMENTS.depositAmount}
</Text>
<Text style={styles.requirementStatus}></Text>
</View>
</View>
<TouchableOpacity
style={styles.checkEligibilityButton}
onPress={checkCandidateEligibility}
disabled={checkingEligibility}
>
{checkingEligibility ? (
<ActivityIndicator color={KurdistanColors.spi} />
) : (
<Text style={styles.checkEligibilityText}>Check My Eligibility</Text>
)}
</TouchableOpacity>
{eligibilityStatus && (
<View style={[
styles.eligibilityResult,
eligibilityStatus.eligible ? styles.eligibilityResultSuccess : styles.eligibilityResultFail
]}>
<Text style={styles.eligibilityResultTitle}>
{eligibilityStatus.eligible ? '✅ You are eligible!' : '❌ Not eligible yet'}
</Text>
{eligibilityStatus.reasons.map((reason, i) => (
<Text key={i} style={styles.eligibilityResultReason}> {reason}</Text>
))}
{eligibilityStatus.eligible && selectedElection?.status === 'CandidacyPeriod' && (
<TouchableOpacity
style={styles.registerButton}
onPress={handleRegisterCandidate}
>
<Text style={styles.registerButtonText}>Register as Candidate</Text>
</TouchableOpacity>
)}
</View>
)}
</View>
</View>
)}
</>
)}
</ScrollView>
{/* Vote Confirmation Modal */}
<Modal
visible={showVoteModal}
transparent
animationType="slide"
onRequestClose={() => setShowVoteModal(false)}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContent}>
<Text style={styles.modalTitle}>Confirm Your Vote</Text>
<Text style={styles.modalSubtitle}>
Dengê xwe piştrast bikin
</Text>
{selectedCandidate && (
<View style={styles.modalCandidate}>
<Text style={styles.modalCandidateLabel}>Voting for:</Text>
<Text style={styles.modalCandidateAddress}>
{formatAddress(selectedCandidate.account)}
</Text>
</View>
)}
<Text style={styles.modalWarning}>
Your vote cannot be changed after submission.
</Text>
<View style={styles.modalButtons}>
<TouchableOpacity
style={styles.modalCancelButton}
onPress={() => {
setShowVoteModal(false);
setSelectedCandidate(null);
}}
>
<Text style={styles.modalCancelText}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.modalConfirmButton}
onPress={handleVote}
disabled={submittingVote}
>
{submittingVote ? (
<ActivityIndicator color={KurdistanColors.spi} />
) : (
<Text style={styles.modalConfirmText}>Confirm Vote</Text>
)}
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</SafeAreaView>
);
};
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;