Files
pwap/mobile/src/screens/BeCitizenScreen.tsx
T
pezkuwichain ddcc09a593 feat(mobile): Enable shared/lib imports via symlink for production blockchain integration
- Create symlink mobile/shared -> ../shared for Metro bundler access
- Update all @pezkuwi/lib/* imports to use relative paths (../../shared/lib/*)
- Dashboard Roles card now fetches real tiki data from blockchain
- Staking screen uses production getStakingInfo and getAllScores
- All citizenship, referral, and NFT screens use real blockchain queries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 07:38:33 +03:00

589 lines
17 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 } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
SafeAreaView,
ScrollView,
StatusBar,
TextInput,
Alert,
ActivityIndicator,
} from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
import { usePezkuwi } from '../contexts/PezkuwiContext';
import {
submitKycApplication,
uploadToIPFS,
getCitizenshipStatus,
} from '../../shared/lib/citizenship-workflow';
import { KurdistanColors } from '../theme/colors';
const BeCitizenScreen: React.FC = () => {
const { api, selectedAccount } = usePezkuwi();
const [_isExistingCitizen, _setIsExistingCitizen] = useState(false);
const [currentStep, setCurrentStep] = useState<'choice' | 'new' | 'existing'>('choice');
const [isSubmitting, setIsSubmitting] = useState(false);
// New Citizen Form State
const [fullName, setFullName] = useState('');
const [fatherName, setFatherName] = useState('');
const [motherName, setMotherName] = useState('');
const [tribe, setTribe] = useState('');
const [region, setRegion] = useState('');
const [email, setEmail] = useState('');
const [profession, setProfession] = useState('');
const [referralCode, setReferralCode] = useState('');
// Existing Citizen Login State
const [citizenId, setCitizenId] = useState('');
const [password, setPassword] = useState('');
const handleNewCitizenApplication = async () => {
if (!fullName || !fatherName || !motherName || !email) {
Alert.alert('Error', 'Please fill in all required fields');
return;
}
if (!api || !selectedAccount) {
Alert.alert('Error', 'Please connect your wallet first');
return;
}
setIsSubmitting(true);
try {
// Prepare citizenship data
const citizenshipData = {
fullName,
fatherName,
motherName,
tribe,
region,
email,
profession,
referralCode,
walletAddress: selectedAccount.address,
timestamp: Date.now(),
};
// Step 1: Upload encrypted data to IPFS
const ipfsCid = await uploadToIPFS(citizenshipData);
if (!ipfsCid) {
throw new Error('Failed to upload data to IPFS');
}
// Step 2: Submit KYC application to blockchain
const result = await submitKycApplication(
api,
selectedAccount,
fullName,
email,
ipfsCid,
'Citizenship application via mobile app'
);
if (result.success) {
Alert.alert(
'Application Submitted!',
'Your citizenship application has been submitted for review. You will receive a confirmation once approved.',
[
{
text: 'OK',
onPress: () => {
// Reset form
setFullName('');
setFatherName('');
setMotherName('');
setTribe('');
setRegion('');
setEmail('');
setProfession('');
setReferralCode('');
setCurrentStep('choice');
},
},
]
);
} else {
Alert.alert('Application Failed', result.error || 'Failed to submit application');
}
} catch (error: unknown) {
if (__DEV__) console.error('Citizenship application error:', error);
Alert.alert('Error', error instanceof Error ? error.message : 'An unexpected error occurred');
} finally {
setIsSubmitting(false);
}
};
const handleExistingCitizenLogin = async () => {
if (!api || !selectedAccount) {
Alert.alert('Error', 'Please connect your wallet first');
return;
}
setIsSubmitting(true);
try {
const status = await getCitizenshipStatus(api, selectedAccount.address);
if (status.kycStatus === 'Approved' && status.hasCitizenTiki) {
Alert.alert(
'Success',
`Welcome back, Citizen!\n\nYour Tiki Number: ${status.tikiNumber || 'N/A'}`,
[
{
text: 'OK',
onPress: () => {
setCitizenId('');
setPassword('');
setCurrentStep('choice');
},
},
]
);
} else if (status.kycStatus === 'Approved' && !status.hasCitizenTiki) {
Alert.alert(
'Almost there!',
'Your KYC is approved, but you haven\'t claimed your Citizen Tiki yet. Please claim it on the web portal.',
[{ text: 'OK' }]
);
} else if (status.kycStatus === 'Pending') {
Alert.alert(
'Application Pending',
'Your citizenship application is still under review.',
[{ text: 'OK' }]
);
} else {
Alert.alert(
'Not a Citizen',
'We couldn\'t find a citizenship record for this wallet. If you have a Citizen ID and Password, please note that wallet-based verification is now preferred.',
[{ text: 'OK' }]
);
}
} catch (error: unknown) {
if (__DEV__) console.error('Citizenship verification error:', error);
Alert.alert('Error', 'Failed to verify citizenship status');
} finally {
setIsSubmitting(false);
}
};
if (currentStep === 'choice') {
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.gradient}
>
<ScrollView
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
<View style={styles.header}>
<View style={styles.logoContainer}>
<Text style={styles.logoText}>🏛</Text>
</View>
<Text style={styles.title}>Be a Citizen</Text>
<Text style={styles.subtitle}>
Join the Pezkuwi decentralized nation
</Text>
</View>
<View style={styles.choiceContainer}>
<TouchableOpacity
style={styles.choiceCard}
onPress={() => setCurrentStep('new')}
activeOpacity={0.8}
>
<Text style={styles.choiceIcon}>📝</Text>
<Text style={styles.choiceTitle}>New Citizen</Text>
<Text style={styles.choiceDescription}>
Apply for citizenship and join our community
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.choiceCard}
onPress={() => setCurrentStep('existing')}
activeOpacity={0.8}
>
<Text style={styles.choiceIcon}>🔐</Text>
<Text style={styles.choiceTitle}>Existing Citizen</Text>
<Text style={styles.choiceDescription}>
Access your citizenship account
</Text>
</TouchableOpacity>
</View>
<View style={styles.infoSection}>
<Text style={styles.infoTitle}>Citizenship Benefits</Text>
<View style={styles.benefitItem}>
<Text style={styles.benefitIcon}></Text>
<Text style={styles.benefitText}>Voting rights in governance</Text>
</View>
<View style={styles.benefitItem}>
<Text style={styles.benefitIcon}></Text>
<Text style={styles.benefitText}>Access to exclusive services</Text>
</View>
<View style={styles.benefitItem}>
<Text style={styles.benefitIcon}></Text>
<Text style={styles.benefitText}>Referral rewards program</Text>
</View>
<View style={styles.benefitItem}>
<Text style={styles.benefitIcon}></Text>
<Text style={styles.benefitText}>Community recognition</Text>
</View>
</View>
</ScrollView>
</LinearGradient>
</SafeAreaView>
);
}
if (currentStep === 'new') {
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<ScrollView style={styles.formContainer} showsVerticalScrollIndicator={false}>
<TouchableOpacity
style={styles.backButton}
onPress={() => setCurrentStep('choice')}
>
<Text style={styles.backButtonText}> Back</Text>
</TouchableOpacity>
<Text style={styles.formTitle}>New Citizen Application</Text>
<Text style={styles.formSubtitle}>
Please provide your information to apply for citizenship
</Text>
<View style={styles.inputGroup}>
<Text style={styles.label}>Full Name *</Text>
<TextInput
style={styles.input}
placeholder="Enter your full name"
value={fullName}
onChangeText={setFullName}
placeholderTextColor="#999"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Father&apos;s Name *</Text>
<TextInput
style={styles.input}
placeholder="Enter father's name"
value={fatherName}
onChangeText={setFatherName}
placeholderTextColor="#999"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Mother&apos;s Name *</Text>
<TextInput
style={styles.input}
placeholder="Enter mother's name"
value={motherName}
onChangeText={setMotherName}
placeholderTextColor="#999"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Tribe</Text>
<TextInput
style={styles.input}
placeholder="Enter tribe (optional)"
value={tribe}
onChangeText={setTribe}
placeholderTextColor="#999"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Region</Text>
<TextInput
style={styles.input}
placeholder="Enter region (optional)"
value={region}
onChangeText={setRegion}
placeholderTextColor="#999"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Email *</Text>
<TextInput
style={styles.input}
placeholder="Enter email address"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
placeholderTextColor="#999"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Profession</Text>
<TextInput
style={styles.input}
placeholder="Enter profession (optional)"
value={profession}
onChangeText={setProfession}
placeholderTextColor="#999"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Referral Code</Text>
<TextInput
style={styles.input}
placeholder="Enter referral code (optional)"
value={referralCode}
onChangeText={setReferralCode}
placeholderTextColor="#999"
/>
</View>
<TouchableOpacity
style={[styles.submitButton, isSubmitting && styles.submitButtonDisabled]}
onPress={handleNewCitizenApplication}
activeOpacity={0.8}
disabled={isSubmitting}
>
{isSubmitting ? (
<ActivityIndicator color={KurdistanColors.spi} />
) : (
<Text style={styles.submitButtonText}>Submit Application</Text>
)}
</TouchableOpacity>
<View style={styles.spacer} />
</ScrollView>
</SafeAreaView>
);
}
// Existing Citizen Login
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<ScrollView style={styles.formContainer} showsVerticalScrollIndicator={false}>
<TouchableOpacity
style={styles.backButton}
onPress={() => setCurrentStep('choice')}
>
<Text style={styles.backButtonText}> Back</Text>
</TouchableOpacity>
<Text style={styles.formTitle}>Citizen Verification</Text>
<Text style={styles.formSubtitle}>
Verify your status using your connected wallet
</Text>
<View style={styles.infoCard}>
<Text style={styles.infoText}>
Existing citizens are verified through their blockchain identity. Ensure your citizenship wallet is selected in the wallet tab.
</Text>
</View>
<TouchableOpacity
style={[styles.submitButton, isSubmitting && styles.submitButtonDisabled]}
onPress={handleExistingCitizenLogin}
activeOpacity={0.8}
disabled={isSubmitting}
>
{isSubmitting ? (
<ActivityIndicator color={KurdistanColors.spi} />
) : (
<Text style={styles.submitButtonText}>Verify Citizenship</Text>
)}
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
gradient: {
flex: 1,
},
scrollContent: {
flexGrow: 1,
padding: 20,
paddingTop: 60,
},
header: {
alignItems: 'center',
marginBottom: 40,
},
logoContainer: {
width: 100,
height: 100,
borderRadius: 50,
backgroundColor: KurdistanColors.spi,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 20,
boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.3)',
elevation: 8,
},
logoText: {
fontSize: 48,
},
title: {
fontSize: 28,
fontWeight: 'bold',
color: KurdistanColors.spi,
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: KurdistanColors.spi,
textAlign: 'center',
opacity: 0.9,
},
choiceContainer: {
gap: 16,
marginBottom: 40,
},
choiceCard: {
backgroundColor: KurdistanColors.spi,
borderRadius: 20,
padding: 24,
alignItems: 'center',
boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.2)',
elevation: 6,
},
choiceIcon: {
fontSize: 48,
marginBottom: 16,
},
choiceTitle: {
fontSize: 20,
fontWeight: 'bold',
color: KurdistanColors.kesk,
marginBottom: 8,
},
choiceDescription: {
fontSize: 14,
color: '#666',
textAlign: 'center',
},
infoSection: {
backgroundColor: 'rgba(255, 255, 255, 0.2)',
borderRadius: 16,
padding: 20,
},
infoTitle: {
fontSize: 18,
fontWeight: '600',
color: KurdistanColors.spi,
marginBottom: 16,
},
benefitItem: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
benefitIcon: {
fontSize: 16,
color: KurdistanColors.spi,
marginRight: 12,
fontWeight: 'bold',
},
benefitText: {
fontSize: 14,
color: KurdistanColors.spi,
flex: 1,
},
infoCard: {
backgroundColor: `${KurdistanColors.kesk}15`,
padding: 16,
borderRadius: 12,
marginBottom: 24,
borderLeftWidth: 4,
borderLeftColor: KurdistanColors.kesk,
},
infoText: {
fontSize: 14,
color: KurdistanColors.reş,
lineHeight: 20,
opacity: 0.8,
},
formContainer: {
flex: 1,
padding: 20,
},
backButton: {
marginBottom: 20,
},
backButtonText: {
fontSize: 16,
color: KurdistanColors.kesk,
fontWeight: '600',
},
formTitle: {
fontSize: 24,
fontWeight: 'bold',
color: KurdistanColors.reş,
marginBottom: 8,
},
formSubtitle: {
fontSize: 14,
color: '#666',
marginBottom: 24,
},
inputGroup: {
marginBottom: 20,
},
label: {
fontSize: 14,
fontWeight: '600',
color: KurdistanColors.reş,
marginBottom: 8,
},
input: {
backgroundColor: KurdistanColors.spi,
borderRadius: 12,
padding: 16,
fontSize: 16,
borderWidth: 1,
borderColor: '#E0E0E0',
},
submitButton: {
backgroundColor: KurdistanColors.kesk,
borderRadius: 12,
padding: 16,
alignItems: 'center',
marginTop: 20,
boxShadow: '0px 4px 6px rgba(0, 128, 0, 0.3)',
elevation: 6,
},
submitButtonDisabled: {
opacity: 0.6,
},
submitButtonText: {
fontSize: 18,
fontWeight: 'bold',
color: KurdistanColors.spi,
},
spacer: {
height: 40,
},
});
export default BeCitizenScreen;