Files
pwap/mobile/src/screens/BeCitizenApplyScreen.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

643 lines
19 KiB
TypeScript

import React, { useState } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
SafeAreaView,
ScrollView,
StatusBar,
TextInput,
Alert,
ActivityIndicator,
Modal,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { usePezkuwi } from '../contexts/PezkuwiContext';
import {
submitKycApplication,
uploadToIPFS,
FOUNDER_ADDRESS,
} from '../../shared/lib/citizenship-workflow';
import type { Region, MaritalStatus } from '../../shared/lib/citizenship-workflow';
import { KurdistanColors } from '../theme/colors';
// Temporary custom picker component (until we fix @react-native-picker/picker installation)
const CustomPicker: React.FC<{
selectedValue: Region;
onValueChange: (value: Region) => void;
options: Array<{ label: string; value: Region }>;
}> = ({ selectedValue, onValueChange, options }) => {
const [isVisible, setIsVisible] = useState(false);
const selectedOption = options.find(opt => opt.value === selectedValue);
return (
<>
<TouchableOpacity
style={styles.pickerButton}
onPress={() => setIsVisible(true)}
>
<Text style={styles.pickerButtonText}>
{selectedOption?.label || 'Select Region'}
</Text>
<Text style={styles.pickerArrow}></Text>
</TouchableOpacity>
<Modal
visible={isVisible}
transparent
animationType="slide"
onRequestClose={() => setIsVisible(false)}
>
<TouchableOpacity
style={styles.modalOverlay}
activeOpacity={1}
onPress={() => setIsVisible(false)}
>
<View style={styles.pickerModal}>
<View style={styles.pickerHeader}>
<Text style={styles.pickerTitle}>Select Your Region</Text>
<TouchableOpacity onPress={() => setIsVisible(false)}>
<Text style={styles.pickerClose}></Text>
</TouchableOpacity>
</View>
<ScrollView>
{options.map((option) => (
<TouchableOpacity
key={option.value}
style={[
styles.pickerOption,
selectedValue === option.value && styles.pickerOptionSelected,
]}
onPress={() => {
onValueChange(option.value);
setIsVisible(false);
}}
>
<Text
style={[
styles.pickerOptionText,
selectedValue === option.value && styles.pickerOptionTextSelected,
]}
>
{option.label}
</Text>
{selectedValue === option.value && (
<Text style={styles.pickerCheckmark}></Text>
)}
</TouchableOpacity>
))}
</ScrollView>
</View>
</TouchableOpacity>
</Modal>
</>
);
};
const BeCitizenApplyScreen: React.FC = () => {
const navigation = useNavigation();
const { api, selectedAccount } = usePezkuwi();
const [isSubmitting, setIsSubmitting] = useState(false);
// Form State
const [fullName, setFullName] = useState('');
const [fatherName, setFatherName] = useState('');
const [grandfatherName, setGrandfatherName] = useState('');
const [motherName, setMotherName] = useState('');
const [tribe, setTribe] = useState('');
const [maritalStatus, setMaritalStatus] = useState<MaritalStatus>('nezewici');
const [childrenCount, setChildrenCount] = useState(0);
const [children, setChildren] = useState<Array<{ name: string; birthYear: number }>>([]);
const [region, setRegion] = useState<Region>('basur');
const [email, setEmail] = useState('');
const [profession, setProfession] = useState('');
const [referralCode, setReferralCode] = useState('');
const regionOptions = [
{ label: 'Bakur (North - Turkey/Türkiye)', value: 'bakur' as Region },
{ label: 'Başûr (South - Iraq)', value: 'basur' as Region },
{ label: 'Rojava (West - Syria)', value: 'rojava' as Region },
{ label: 'Rojhilat (East - Iran)', value: 'rojhelat' as Region },
{ label: 'Kurdistan a Sor (Red Kurdistan)', value: 'kurdistan_a_sor' as Region },
{ label: 'Diaspora (Living Abroad)', value: 'diaspora' as Region },
];
const handleChildCountChange = (count: number) => {
setChildrenCount(count);
// Initialize children array
const newChildren = Array.from({ length: count }, (_, i) =>
children[i] || { name: '', birthYear: new Date().getFullYear() }
);
setChildren(newChildren);
};
const updateChild = (index: number, field: 'name' | 'birthYear', value: string | number) => {
const updated = [...children];
if (field === 'name') {
updated[index].name = value as string;
} else {
updated[index].birthYear = value as number;
}
setChildren(updated);
};
const handleSubmit = async () => {
if (!fullName || !fatherName || !grandfatherName || !motherName || !tribe || !region || !email || !profession) {
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,
grandfatherName,
motherName,
tribe,
maritalStatus,
childrenCount: maritalStatus === 'zewici' ? childrenCount : 0,
children: maritalStatus === 'zewici' ? children.filter(c => c.name) : [],
region,
email,
profession,
referralCode: referralCode || FOUNDER_ADDRESS, // Auto-assign to founder if empty
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,
citizenshipData.fullName,
citizenshipData.email,
String(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: () => {
navigation.goBack();
},
},
]
);
} 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);
}
};
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<ScrollView style={styles.formContainer} showsVerticalScrollIndicator={false}>
<Text style={styles.formTitle}>Nasnameya Kesane</Text>
<Text style={styles.formSubtitle}>Personal Identity - Citizenship Application</Text>
{/* Personal Identity */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Personal Identity</Text>
<View style={styles.inputGroup}>
<Text style={styles.label}>Navê Te (Your Full Name) *</Text>
<TextInput
style={styles.input}
placeholder="e.g., Berzê Ronahî"
value={fullName}
onChangeText={setFullName}
placeholderTextColor="#999"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Navê Bavê Te (Father&apos;s Name) *</Text>
<TextInput
style={styles.input}
placeholder="e.g., Şêrko"
value={fatherName}
onChangeText={setFatherName}
placeholderTextColor="#999"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Navê Bavkalê Te (Grandfather&apos;s Name) *</Text>
<TextInput
style={styles.input}
placeholder="e.g., Welat"
value={grandfatherName}
onChangeText={setGrandfatherName}
placeholderTextColor="#999"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Navê Dayika Te (Mother&apos;s Name) *</Text>
<TextInput
style={styles.input}
placeholder="e.g., Gula"
value={motherName}
onChangeText={setMotherName}
placeholderTextColor="#999"
/>
</View>
</View>
{/* Tribal Affiliation */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Eşîra Te (Tribal Affiliation)</Text>
<View style={styles.inputGroup}>
<Text style={styles.label}>Eşîra Te (Your Tribe) *</Text>
<TextInput
style={styles.input}
placeholder="e.g., Barzanî, Soran, Hewramî..."
value={tribe}
onChangeText={setTribe}
placeholderTextColor="#999"
/>
</View>
</View>
{/* Family Status */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Rewşa Malbatê (Family Status)</Text>
<View style={styles.inputGroup}>
<Text style={styles.label}>Zewicî / Nezewicî (Married / Unmarried) *</Text>
<View style={styles.radioGroup}>
<TouchableOpacity
style={styles.radioButton}
onPress={() => setMaritalStatus('zewici')}
>
<View style={[styles.radioCircle, maritalStatus === 'zewici' && styles.radioSelected]}>
{maritalStatus === 'zewici' && <View style={styles.radioDot} />}
</View>
<Text style={styles.radioLabel}>Zewicî (Married)</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.radioButton}
onPress={() => setMaritalStatus('nezewici')}
>
<View style={[styles.radioCircle, maritalStatus === 'nezewici' && styles.radioSelected]}>
{maritalStatus === 'nezewici' && <View style={styles.radioDot} />}
</View>
<Text style={styles.radioLabel}>Nezewicî (Unmarried)</Text>
</TouchableOpacity>
</View>
</View>
{maritalStatus === 'zewici' && (
<>
<View style={styles.inputGroup}>
<Text style={styles.label}>Hejmara Zarokan (Number of Children)</Text>
<TextInput
style={styles.input}
placeholder="0"
value={String(childrenCount)}
onChangeText={(text) => handleChildCountChange(parseInt(text) || 0)}
keyboardType="number-pad"
placeholderTextColor="#999"
/>
</View>
{childrenCount > 0 && (
<View style={styles.inputGroup}>
<Text style={styles.label}>Navên Zarokan (Children&apos;s Names)</Text>
{children.map((child, index) => (
<View key={index} style={styles.childRow}>
<TextInput
style={[styles.input, styles.childInput]}
placeholder={`Child ${index + 1} Name`}
value={child.name}
onChangeText={(text) => updateChild(index, 'name', text)}
placeholderTextColor="#999"
/>
<TextInput
style={[styles.input, styles.childInput]}
placeholder="Birth Year"
value={String(child.birthYear)}
onChangeText={(text) => updateChild(index, 'birthYear', parseInt(text) || new Date().getFullYear())}
keyboardType="number-pad"
placeholderTextColor="#999"
/>
</View>
))}
</View>
)}
</>
)}
</View>
{/* Geographic Origin */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Herêma Te (Your Region)</Text>
<View style={styles.inputGroup}>
<Text style={styles.label}>Ji Kuderê ? (Where are you from?) *</Text>
<CustomPicker
selectedValue={region}
onValueChange={setRegion}
options={regionOptions}
/>
</View>
</View>
{/* Contact & Profession */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Têkilî û Pîşe (Contact & Profession)</Text>
<View style={styles.inputGroup}>
<Text style={styles.label}>E-mail *</Text>
<TextInput
style={styles.input}
placeholder="example@email.com"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
placeholderTextColor="#999"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Pîşeya Te (Your Profession) *</Text>
<TextInput
style={styles.input}
placeholder="e.g., Mamosta, Bijîşk, Xebatkar..."
value={profession}
onChangeText={setProfession}
placeholderTextColor="#999"
/>
</View>
</View>
{/* Referral */}
<View style={[styles.section, styles.referralSection]}>
<Text style={styles.sectionTitle}>Koda Referral (Referral Code - Optional)</Text>
<Text style={styles.sectionDescription}>
If you were invited by another citizen, enter their referral code
</Text>
<View style={styles.inputGroup}>
<TextInput
style={styles.input}
placeholder="Referral code (optional)"
value={referralCode}
onChangeText={setReferralCode}
placeholderTextColor="#999"
/>
<Text style={styles.helpText}>
If empty, you will be automatically linked to the Founder (Satoshi Qazi Muhammed)
</Text>
</View>
</View>
<TouchableOpacity
style={[styles.submitButton, isSubmitting && styles.submitButtonDisabled]}
onPress={handleSubmit}
activeOpacity={0.8}
disabled={isSubmitting}
>
{isSubmitting ? (
<ActivityIndicator color={KurdistanColors.spi} />
) : (
<Text style={styles.submitButtonText}>Submit Application</Text>
)}
</TouchableOpacity>
<View style={styles.spacer} />
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
formContainer: {
flex: 1,
padding: 20,
},
formTitle: {
fontSize: 24,
fontWeight: 'bold',
color: KurdistanColors.reş,
marginBottom: 4,
},
formSubtitle: {
fontSize: 14,
color: '#666',
marginBottom: 24,
},
section: {
backgroundColor: KurdistanColors.spi,
borderRadius: 12,
padding: 16,
marginBottom: 16,
boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.05)',
elevation: 2,
},
sectionTitle: {
fontSize: 16,
fontWeight: '700',
color: KurdistanColors.kesk,
marginBottom: 12,
},
sectionDescription: {
fontSize: 12,
color: '#666',
marginBottom: 12,
},
referralSection: {
backgroundColor: `${KurdistanColors.mor}10`,
borderWidth: 1,
borderColor: `${KurdistanColors.mor}30`,
},
inputGroup: {
marginBottom: 16,
},
label: {
fontSize: 14,
fontWeight: '600',
color: KurdistanColors.reş,
marginBottom: 8,
},
input: {
backgroundColor: '#F8F9FA',
borderRadius: 8,
padding: 12,
fontSize: 14,
borderWidth: 1,
borderColor: '#E0E0E0',
color: KurdistanColors.reş,
},
radioGroup: {
gap: 12,
},
radioButton: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
},
radioCircle: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 2,
borderColor: KurdistanColors.kesk,
marginRight: 10,
justifyContent: 'center',
alignItems: 'center',
},
radioSelected: {
borderColor: KurdistanColors.kesk,
},
radioDot: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: KurdistanColors.kesk,
},
radioLabel: {
fontSize: 14,
color: KurdistanColors.reş,
},
childRow: {
flexDirection: 'row',
gap: 8,
marginBottom: 8,
},
childInput: {
flex: 1,
marginBottom: 0,
},
pickerButton: {
backgroundColor: '#F8F9FA',
borderRadius: 8,
borderWidth: 1,
borderColor: '#E0E0E0',
padding: 12,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
pickerButtonText: {
fontSize: 14,
color: KurdistanColors.reş,
},
pickerArrow: {
fontSize: 12,
color: '#666',
},
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'flex-end',
},
pickerModal: {
backgroundColor: KurdistanColors.spi,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
maxHeight: '70%',
paddingBottom: 20,
},
pickerHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#E0E0E0',
},
pickerTitle: {
fontSize: 18,
fontWeight: '700',
color: KurdistanColors.reş,
},
pickerClose: {
fontSize: 24,
color: '#666',
fontWeight: '300',
},
pickerOption: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#F0F0F0',
},
pickerOptionSelected: {
backgroundColor: `${KurdistanColors.kesk}10`,
},
pickerOptionText: {
fontSize: 15,
color: KurdistanColors.reş,
},
pickerOptionTextSelected: {
fontWeight: '600',
color: KurdistanColors.kesk,
},
pickerCheckmark: {
fontSize: 18,
color: KurdistanColors.kesk,
fontWeight: 'bold',
},
helpText: {
fontSize: 11,
color: '#666',
marginTop: 4,
},
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 BeCitizenApplyScreen;