mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-05-10 07:47:55 +00:00
ddcc09a593
- 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>
643 lines
19 KiB
TypeScript
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'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'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'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'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ê yî? (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;
|