mirror of
https://github.com/pezkuwichain/pezkuwi-mobile-app.git
synced 2026-05-31 11:01:06 +00:00
auto-commit for bed65b0f-c949-4d15-b953-0bf08c7c9e55
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { StatusBar } from 'expo-status-bar';
|
||||||
|
import RootNavigator from './src/navigation/RootNavigator';
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StatusBar style="auto" />
|
||||||
|
<RootNavigator />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
+14
-26
@@ -1,42 +1,30 @@
|
|||||||
{
|
{
|
||||||
"expo": {
|
"expo": {
|
||||||
"name": "frontend",
|
"name": "pezkuwi-mobile-app",
|
||||||
"slug": "frontend",
|
"slug": "pezkuwi-mobile-app",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/images/icon.png",
|
"icon": "./assets/icon.png",
|
||||||
"scheme": "frontend",
|
"userInterfaceStyle": "light",
|
||||||
"userInterfaceStyle": "automatic",
|
|
||||||
"newArchEnabled": true,
|
"newArchEnabled": true,
|
||||||
|
"splash": {
|
||||||
|
"image": "./assets/splash-icon.png",
|
||||||
|
"resizeMode": "contain",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
"ios": {
|
"ios": {
|
||||||
"supportsTablet": true
|
"supportsTablet": true
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
"foregroundImage": "./assets/adaptive-icon.png",
|
||||||
"backgroundColor": "#000"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"edgeToEdgeEnabled": true
|
"edgeToEdgeEnabled": true,
|
||||||
|
"predictiveBackGestureEnabled": false
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"bundler": "metro",
|
"favicon": "./assets/favicon.png"
|
||||||
"output": "static",
|
|
||||||
"favicon": "./assets/images/favicon.png"
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
"expo-router",
|
|
||||||
[
|
|
||||||
"expo-splash-screen",
|
|
||||||
{
|
|
||||||
"image": "./assets/images/splash-icon.png",
|
|
||||||
"imageWidth": 200,
|
|
||||||
"resizeMode": "contain",
|
|
||||||
"backgroundColor": "#000"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"experiments": {
|
|
||||||
"typedRoutes": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { Text, View, StyleSheet, Image } from "react-native";
|
|
||||||
|
|
||||||
const EXPO_PUBLIC_BACKEND_URL = process.env.EXPO_PUBLIC_BACKEND_URL;
|
|
||||||
|
|
||||||
export default function Index() {
|
|
||||||
console.log(EXPO_PUBLIC_BACKEND_URL, "EXPO_PUBLIC_BACKEND_URL");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<Image
|
|
||||||
source={require("../assets/images/app-image.png")}
|
|
||||||
style={styles.image}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: "#0c0c0c",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
resizeMode: "contain",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 601 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 316 KiB |
@@ -0,0 +1,8 @@
|
|||||||
|
import { registerRootComponent } from 'expo';
|
||||||
|
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
|
||||||
|
// It also ensures that whether you load the app in Expo Go or in a native build,
|
||||||
|
// the environment is set up appropriately
|
||||||
|
registerRootComponent(App);
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
Modal,
|
||||||
|
TouchableOpacity,
|
||||||
|
Image,
|
||||||
|
Alert,
|
||||||
|
Share,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import QRCode from 'react-native-qrcode-svg';
|
||||||
|
import * as Clipboard from 'expo-clipboard';
|
||||||
|
import Colors from '../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../constants/theme';
|
||||||
|
|
||||||
|
interface ReceiveModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
token: {
|
||||||
|
symbol: string;
|
||||||
|
name: string;
|
||||||
|
icon: any;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ReceiveModal({ visible, onClose, token }: ReceiveModalProps) {
|
||||||
|
// Mock wallet address - in production, this would come from user's wallet
|
||||||
|
const walletAddress = 'pezkuwi1a2b3c4d5e6f7g8h9i0j';
|
||||||
|
const network = 'Optimism';
|
||||||
|
|
||||||
|
const handleCopyAddress = async () => {
|
||||||
|
await Clipboard.setStringAsync(walletAddress);
|
||||||
|
Alert.alert('Copied!', 'Wallet address copied to clipboard');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShareAddress = async () => {
|
||||||
|
try {
|
||||||
|
await Share.share({
|
||||||
|
message: `My ${token.symbol} wallet address:\n${walletAddress}\n\nNetwork: ${network}`,
|
||||||
|
title: `${token.symbol} Wallet Address`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sharing:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
transparent
|
||||||
|
animationType="slide"
|
||||||
|
onRequestClose={onClose}
|
||||||
|
>
|
||||||
|
<View style={styles.overlay}>
|
||||||
|
<View style={styles.modalContainer}>
|
||||||
|
{/* Close Button */}
|
||||||
|
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
|
||||||
|
<Ionicons name="close" size={28} color={Colors.textDark} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Token Icon */}
|
||||||
|
<Image source={token.icon} style={styles.tokenIcon} />
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<Text style={styles.title}>Receive {token.symbol}</Text>
|
||||||
|
|
||||||
|
{/* QR Code Card */}
|
||||||
|
<View style={styles.qrCard}>
|
||||||
|
<QRCode value={walletAddress} size={200} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Wallet Address */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.addressContainer}
|
||||||
|
onPress={handleCopyAddress}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text style={styles.addressText}>{walletAddress}</Text>
|
||||||
|
<Ionicons name="copy-outline" size={20} color={Colors.primary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.tapToCopy}>Tap to copy</Text>
|
||||||
|
|
||||||
|
{/* Network Badge */}
|
||||||
|
<View style={styles.networkBadge}>
|
||||||
|
<Ionicons name="globe-outline" size={20} color="#007AFF" />
|
||||||
|
<Text style={styles.networkText}>{network} Network</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Share Button */}
|
||||||
|
<TouchableOpacity style={styles.shareButton} onPress={handleShareAddress}>
|
||||||
|
<Ionicons name="share-outline" size={20} color={Colors.textDark} />
|
||||||
|
<Text style={styles.shareButtonText}>Share Address</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Warning Box */}
|
||||||
|
<View style={styles.warningBox}>
|
||||||
|
<Ionicons name="warning-outline" size={24} color="#F59E0B" />
|
||||||
|
<Text style={styles.warningText}>
|
||||||
|
Only send {token.symbol} to this address on {network} network
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
overlay: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: Spacing.lg,
|
||||||
|
},
|
||||||
|
modalContainer: {
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: 400,
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: BorderRadius.xl,
|
||||||
|
padding: Spacing.xl,
|
||||||
|
...Shadow.medium,
|
||||||
|
},
|
||||||
|
closeButton: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: Spacing.md,
|
||||||
|
right: Spacing.md,
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
tokenIcon: {
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
alignSelf: 'center',
|
||||||
|
marginTop: Spacing.md,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: Typography.sizes.xxl,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: Spacing.sm,
|
||||||
|
marginBottom: Spacing.lg,
|
||||||
|
},
|
||||||
|
qrCard: {
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: BorderRadius.lg,
|
||||||
|
padding: Spacing.lg,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
...Shadow.small,
|
||||||
|
alignSelf: 'center',
|
||||||
|
},
|
||||||
|
addressContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
borderRadius: BorderRadius.md,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: Spacing.sm,
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
},
|
||||||
|
addressText: {
|
||||||
|
fontSize: Typography.sizes.sm,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginRight: Spacing.sm,
|
||||||
|
},
|
||||||
|
tapToCopy: {
|
||||||
|
fontSize: Typography.sizes.xs,
|
||||||
|
color: Colors.textLight,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: Spacing.xs,
|
||||||
|
},
|
||||||
|
networkBadge: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: '#E3F2FD',
|
||||||
|
borderRadius: BorderRadius.md,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: Spacing.sm,
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
alignSelf: 'center',
|
||||||
|
},
|
||||||
|
networkText: {
|
||||||
|
fontSize: Typography.sizes.md,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: '#007AFF',
|
||||||
|
marginLeft: Spacing.xs,
|
||||||
|
},
|
||||||
|
shareButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
borderRadius: BorderRadius.md,
|
||||||
|
paddingVertical: Spacing.md,
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
},
|
||||||
|
shareButtonText: {
|
||||||
|
fontSize: Typography.sizes.md,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginLeft: Spacing.sm,
|
||||||
|
},
|
||||||
|
warningBox: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#FEF3C7',
|
||||||
|
borderRadius: BorderRadius.md,
|
||||||
|
padding: Spacing.md,
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
},
|
||||||
|
warningText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: Typography.sizes.sm,
|
||||||
|
color: '#92400E',
|
||||||
|
marginLeft: Spacing.sm,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
Modal,
|
||||||
|
TouchableOpacity,
|
||||||
|
TextInput,
|
||||||
|
Image,
|
||||||
|
Alert,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import Colors from '../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../constants/theme';
|
||||||
|
|
||||||
|
interface SendModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
token: {
|
||||||
|
symbol: string;
|
||||||
|
name: string;
|
||||||
|
balance: string;
|
||||||
|
icon: any;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SendModal({ visible, onClose, token }: SendModalProps) {
|
||||||
|
const [recipientAddress, setRecipientAddress] = useState('');
|
||||||
|
const [amount, setAmount] = useState('');
|
||||||
|
const [network, setNetwork] = useState('Optimism');
|
||||||
|
|
||||||
|
const handleMaxAmount = () => {
|
||||||
|
setAmount(token.balance);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSend = () => {
|
||||||
|
if (!recipientAddress) {
|
||||||
|
Alert.alert('Error', 'Please enter recipient address');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!amount || parseFloat(amount) <= 0) {
|
||||||
|
Alert.alert('Error', 'Please enter valid amount');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (parseFloat(amount) > parseFloat(token.balance)) {
|
||||||
|
Alert.alert('Error', 'Insufficient balance');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Alert.alert(
|
||||||
|
'Confirm Transaction',
|
||||||
|
`Send ${amount} ${token.symbol} to ${recipientAddress.substring(0, 10)}...?`,
|
||||||
|
[
|
||||||
|
{ text: 'Cancel', style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: 'Confirm',
|
||||||
|
onPress: () => {
|
||||||
|
// TODO: Integrate with blockchain service
|
||||||
|
Alert.alert('Success', 'Transaction submitted!');
|
||||||
|
onClose();
|
||||||
|
setRecipientAddress('');
|
||||||
|
setAmount('');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQRScan = () => {
|
||||||
|
// TODO: Open QR scanner
|
||||||
|
Alert.alert('QR Scanner', 'QR scanner will be implemented');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePaste = async () => {
|
||||||
|
// TODO: Paste from clipboard
|
||||||
|
Alert.alert('Paste', 'Clipboard paste will be implemented');
|
||||||
|
};
|
||||||
|
|
||||||
|
const usdValue = (parseFloat(amount) || 0) * 1.5; // Mock exchange rate
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
transparent
|
||||||
|
animationType="slide"
|
||||||
|
onRequestClose={onClose}
|
||||||
|
>
|
||||||
|
<View style={styles.overlay}>
|
||||||
|
<View style={styles.modalContainer}>
|
||||||
|
{/* Close Button */}
|
||||||
|
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
|
||||||
|
<Ionicons name="close" size={28} color={Colors.textDark} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Token Icon */}
|
||||||
|
<Image source={token.icon} style={styles.tokenIcon} />
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<Text style={styles.title}>Send {token.symbol}</Text>
|
||||||
|
|
||||||
|
{/* Available Balance */}
|
||||||
|
<Text style={styles.balance}>
|
||||||
|
Available Balance: {token.balance} {token.symbol}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* Recipient Address */}
|
||||||
|
<Text style={styles.label}>Recipient Address</Text>
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Enter wallet address"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
value={recipientAddress}
|
||||||
|
onChangeText={setRecipientAddress}
|
||||||
|
autoCapitalize="none"
|
||||||
|
/>
|
||||||
|
<TouchableOpacity style={styles.iconButton} onPress={handleQRScan}>
|
||||||
|
<Ionicons name="qr-code-outline" size={24} color={Colors.primary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.iconButton} onPress={handlePaste}>
|
||||||
|
<Ionicons name="clipboard-outline" size={24} color={Colors.primary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Amount */}
|
||||||
|
<Text style={styles.label}>Amount</Text>
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<TextInput
|
||||||
|
style={[styles.input, { flex: 1 }]}
|
||||||
|
placeholder="0.00"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
value={amount}
|
||||||
|
onChangeText={setAmount}
|
||||||
|
keyboardType="decimal-pad"
|
||||||
|
/>
|
||||||
|
<TouchableOpacity style={styles.maxButton} onPress={handleMaxAmount}>
|
||||||
|
<Text style={styles.maxButtonText}>MAX</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.usdValue}>USD Value: ${usdValue.toFixed(2)}</Text>
|
||||||
|
|
||||||
|
{/* Network */}
|
||||||
|
<View style={styles.networkRow}>
|
||||||
|
<Text style={styles.networkLabel}>Network:</Text>
|
||||||
|
<TouchableOpacity style={styles.networkSelector}>
|
||||||
|
<Text style={styles.networkText}>{network}</Text>
|
||||||
|
<Ionicons name="chevron-down" size={20} color={Colors.textDark} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Transaction Fee */}
|
||||||
|
<View style={styles.feeRow}>
|
||||||
|
<Text style={styles.feeText}>Fee: 0.001 HEZ (~$0.01)</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Send Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.sendButton, { backgroundColor: token.color }]}
|
||||||
|
onPress={handleSend}
|
||||||
|
>
|
||||||
|
<Text style={styles.sendButtonText}>Send {token.symbol}</Text>
|
||||||
|
<Ionicons name="arrow-forward" size={20} color="#FFFFFF" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
overlay: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: Spacing.lg,
|
||||||
|
},
|
||||||
|
modalContainer: {
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: 400,
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: BorderRadius.xl,
|
||||||
|
padding: Spacing.xl,
|
||||||
|
...Shadow.medium,
|
||||||
|
},
|
||||||
|
closeButton: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: Spacing.md,
|
||||||
|
left: Spacing.md,
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
tokenIcon: {
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
alignSelf: 'center',
|
||||||
|
marginTop: Spacing.md,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: Typography.sizes.xxl,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: Spacing.sm,
|
||||||
|
},
|
||||||
|
balance: {
|
||||||
|
fontSize: Typography.sizes.md,
|
||||||
|
color: Colors.textLight,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: Spacing.xs,
|
||||||
|
marginBottom: Spacing.lg,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: Typography.sizes.md,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: Spacing.xs,
|
||||||
|
marginTop: Spacing.md,
|
||||||
|
},
|
||||||
|
inputContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
borderRadius: BorderRadius.md,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: Spacing.sm,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: Typography.sizes.md,
|
||||||
|
color: Colors.textDark,
|
||||||
|
paddingVertical: Spacing.xs,
|
||||||
|
},
|
||||||
|
iconButton: {
|
||||||
|
marginLeft: Spacing.sm,
|
||||||
|
},
|
||||||
|
maxButton: {
|
||||||
|
backgroundColor: Colors.primary,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: Spacing.xs,
|
||||||
|
borderRadius: BorderRadius.sm,
|
||||||
|
},
|
||||||
|
maxButtonText: {
|
||||||
|
fontSize: Typography.sizes.sm,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
usdValue: {
|
||||||
|
fontSize: Typography.sizes.sm,
|
||||||
|
color: Colors.textLight,
|
||||||
|
marginTop: Spacing.xs,
|
||||||
|
},
|
||||||
|
networkRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
},
|
||||||
|
networkLabel: {
|
||||||
|
fontSize: Typography.sizes.md,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
networkSelector: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: Spacing.sm,
|
||||||
|
borderRadius: BorderRadius.sm,
|
||||||
|
},
|
||||||
|
networkText: {
|
||||||
|
fontSize: Typography.sizes.md,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginRight: Spacing.xs,
|
||||||
|
},
|
||||||
|
feeRow: {
|
||||||
|
marginTop: Spacing.md,
|
||||||
|
},
|
||||||
|
feeText: {
|
||||||
|
fontSize: Typography.sizes.sm,
|
||||||
|
color: Colors.textLight,
|
||||||
|
},
|
||||||
|
sendButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: Spacing.md,
|
||||||
|
borderRadius: BorderRadius.lg,
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
...Shadow.small,
|
||||||
|
},
|
||||||
|
sendButtonText: {
|
||||||
|
fontSize: Typography.sizes.lg,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
marginRight: Spacing.sm,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* PezkuwiChain Blockchain Configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ChainConfig } from '../types';
|
||||||
|
|
||||||
|
export const CHAIN_CONFIGS: Record<'testnet' | 'mainnet', ChainConfig> = {
|
||||||
|
testnet: {
|
||||||
|
name: 'PezkuwiChain Testnet',
|
||||||
|
rpcUrl: 'wss://testnet-rpc.pezkuwichain.io',
|
||||||
|
chainId: 'pezkuwi-testnet',
|
||||||
|
genesisHash: '0x0000000000000000000000000000000000000000000000000000000000000000', // Will be updated
|
||||||
|
decimals: {
|
||||||
|
hez: 12,
|
||||||
|
pez: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mainnet: {
|
||||||
|
name: 'PezkuwiChain Mainnet',
|
||||||
|
rpcUrl: 'wss://mainnet-rpc.pezkuwichain.io',
|
||||||
|
chainId: 'pezkuwi-mainnet',
|
||||||
|
genesisHash: '0x0000000000000000000000000000000000000000000000000000000000000000', // Will be updated
|
||||||
|
decimals: {
|
||||||
|
hez: 12,
|
||||||
|
pez: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default to testnet for development
|
||||||
|
export const DEFAULT_CHAIN: 'testnet' | 'mainnet' = 'testnet';
|
||||||
|
|
||||||
|
export const CURRENT_CHAIN_CONFIG = CHAIN_CONFIGS[DEFAULT_CHAIN];
|
||||||
|
|
||||||
|
// Pallet names
|
||||||
|
export const PALLETS = {
|
||||||
|
BALANCES: 'balances',
|
||||||
|
ASSETS: 'assets',
|
||||||
|
STAKING: 'staking',
|
||||||
|
WELATI: 'welati',
|
||||||
|
TRUST: 'trust',
|
||||||
|
PEZ_REWARDS: 'pezRewards',
|
||||||
|
PEZ_TREASURY: 'pezTreasury',
|
||||||
|
IDENTITY_KYC: 'identityKyc',
|
||||||
|
PERWERDE: 'perwerde',
|
||||||
|
REFERRAL: 'referral',
|
||||||
|
VALIDATOR_POOL: 'validatorPool',
|
||||||
|
STAKING_SCORE: 'stakingScore',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Asset IDs
|
||||||
|
export const ASSET_IDS = {
|
||||||
|
PEZ: 1, // PEZ token asset ID
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constants from blockchain
|
||||||
|
export const BLOCKCHAIN_CONSTANTS = {
|
||||||
|
// PEZ Token
|
||||||
|
PEZ_TOTAL_SUPPLY: '5000000000000000000000', // 5 billion with 12 decimals
|
||||||
|
PEZ_HALVING_PERIOD: 48, // months
|
||||||
|
PEZ_EPOCH_BLOCKS: 432000, // ~30 days
|
||||||
|
PEZ_CLAIM_PERIOD: 100800, // ~1 week
|
||||||
|
|
||||||
|
// Parliamentary NFT
|
||||||
|
PARLIAMENTARY_NFT_COUNT: 201,
|
||||||
|
PARLIAMENTARY_NFT_COLLECTION_ID: 100,
|
||||||
|
PARLIAMENTARY_NFT_INCENTIVE_PERCENT: 10, // 10% of rewards pool
|
||||||
|
|
||||||
|
// Trust Score
|
||||||
|
TRUST_SCORE_MIN: 0,
|
||||||
|
TRUST_SCORE_MAX: 1000,
|
||||||
|
|
||||||
|
// Staking
|
||||||
|
MIN_STAKE_AMOUNT: '1000000000000', // 1 HEZ with 12 decimals
|
||||||
|
UNBONDING_PERIOD: 28, // days
|
||||||
|
|
||||||
|
// Governance
|
||||||
|
PROPOSAL_DEPOSIT: '100000000000000', // 100 PEZ with 12 decimals
|
||||||
|
VOTING_PERIOD: 7, // days
|
||||||
|
};
|
||||||
|
|
||||||
|
// Block time (approximate)
|
||||||
|
export const BLOCK_TIME = 6; // seconds
|
||||||
|
|
||||||
|
// Transaction fees (approximate)
|
||||||
|
export const TX_FEES = {
|
||||||
|
TRANSFER: '0.01', // HEZ
|
||||||
|
STAKE: '0.02',
|
||||||
|
VOTE: '0.01',
|
||||||
|
PROPOSAL: '0.05',
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* PezkuwiChain Mobile App - Color Palette
|
||||||
|
* Elegant, soft Kurdish-inspired colors
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const Colors = {
|
||||||
|
// Primary Kurdish Colors (Soft)
|
||||||
|
coral: '#F08080', // Soft red - Send, Home
|
||||||
|
mint: '#98D8C8', // Soft green - Governance, Vote
|
||||||
|
gold: '#E8C896', // Soft gold - Trust score, Parliamentary
|
||||||
|
peach: '#F5B895', // Soft orange - Update, Create buttons
|
||||||
|
teal: '#7DD3C0', // Soft teal - Identity, Receive
|
||||||
|
lavender: '#C8B6D6', // Soft purple - Education, Stake
|
||||||
|
navy: '#5D7A8C', // Soft navy - Business
|
||||||
|
|
||||||
|
// Supporting Colors
|
||||||
|
blue: '#89CFF0', // Soft blue - Wallet, Receive
|
||||||
|
cyan: '#7DD3C0', // Soft cyan - Exchange
|
||||||
|
emerald: '#7FD8BE', // Soft emerald - Trust
|
||||||
|
violet: '#B19CD9', // Soft violet - Rewards
|
||||||
|
pink: '#FFB6C1', // Soft pink - Certificates
|
||||||
|
|
||||||
|
// Backgrounds
|
||||||
|
background: '#FAFAFA', // Off-white main background
|
||||||
|
card: '#FFFFFF', // White cards
|
||||||
|
header: '#F5F5F5', // Light gray header
|
||||||
|
|
||||||
|
// Text Colors
|
||||||
|
textDark: '#2C3E50', // Primary text
|
||||||
|
textGray: '#7F8C8D', // Secondary text
|
||||||
|
textLight: '#BDC3C7', // Tertiary text
|
||||||
|
|
||||||
|
// Functional Colors
|
||||||
|
success: '#2ECC71', // Green for success
|
||||||
|
warning: '#F39C12', // Orange for warnings
|
||||||
|
error: '#E74C3C', // Red for errors
|
||||||
|
info: '#3498DB', // Blue for info
|
||||||
|
|
||||||
|
// Gradients (start and end colors)
|
||||||
|
gradients: {
|
||||||
|
header: ['#F08080', '#F5B895'], // Coral to peach
|
||||||
|
hezCard: ['#F08080', '#F5B895'], // Red to orange
|
||||||
|
pezCard: ['#98D8C8', '#7DD3C0'], // Green to teal
|
||||||
|
governance: ['#98D8C8', '#7DD3C0'], // Mint gradient
|
||||||
|
education: ['#C8B6D6', '#B19CD9'], // Lavender gradient
|
||||||
|
business: ['#5D7A8C', '#7F8C8D'], // Navy gradient
|
||||||
|
exchange: ['#7DD3C0', '#89CFF0'], // Cyan gradient
|
||||||
|
referral: ['#F5B895', '#E8C896'], // Peach to gold
|
||||||
|
identity: ['#7DD3C0', '#98D8C8'], // Teal to mint
|
||||||
|
},
|
||||||
|
|
||||||
|
// Shadow
|
||||||
|
shadow: 'rgba(0, 0, 0, 0.08)',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Colors;
|
||||||
|
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* PezkuwiChain Mobile App - Theme Constants
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const Typography = {
|
||||||
|
// Font Sizes
|
||||||
|
sizes: {
|
||||||
|
tiny: 10,
|
||||||
|
small: 12,
|
||||||
|
body: 14,
|
||||||
|
medium: 16,
|
||||||
|
large: 18,
|
||||||
|
title: 20,
|
||||||
|
heading: 24,
|
||||||
|
display: 32,
|
||||||
|
hero: 40,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Font Weights
|
||||||
|
weights: {
|
||||||
|
regular: '400' as const,
|
||||||
|
medium: '500' as const,
|
||||||
|
semibold: '600' as const,
|
||||||
|
bold: '700' as const,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Line Heights
|
||||||
|
lineHeights: {
|
||||||
|
tight: 1.2,
|
||||||
|
normal: 1.5,
|
||||||
|
relaxed: 1.8,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Spacing = {
|
||||||
|
xs: 4,
|
||||||
|
sm: 8,
|
||||||
|
md: 12,
|
||||||
|
lg: 16,
|
||||||
|
xl: 20,
|
||||||
|
xxl: 24,
|
||||||
|
xxxl: 32,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BorderRadius = {
|
||||||
|
small: 8,
|
||||||
|
medium: 12,
|
||||||
|
large: 16,
|
||||||
|
xlarge: 20,
|
||||||
|
xxlarge: 24,
|
||||||
|
round: 9999,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Shadow = {
|
||||||
|
soft: {
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.08,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 3,
|
||||||
|
},
|
||||||
|
medium: {
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.12,
|
||||||
|
shadowRadius: 12,
|
||||||
|
elevation: 5,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IconSizes = {
|
||||||
|
tiny: 16,
|
||||||
|
small: 20,
|
||||||
|
medium: 24,
|
||||||
|
large: 32,
|
||||||
|
xlarge: 40,
|
||||||
|
xxlarge: 60,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Typography,
|
||||||
|
Spacing,
|
||||||
|
BorderRadius,
|
||||||
|
Shadow,
|
||||||
|
IconSizes,
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import Colors from '../constants/colors';
|
||||||
|
|
||||||
|
// Screens
|
||||||
|
import HomeScreen from '../screens/Home/HomeScreen';
|
||||||
|
import WalletScreen from '../screens/Wallet/WalletScreen';
|
||||||
|
import GovernanceScreen from '../screens/Governance/GovernanceScreen';
|
||||||
|
import ReferralScreen from '../screens/Referral/ReferralScreen';
|
||||||
|
import ProfileScreen from '../screens/Profile/ProfileScreen';
|
||||||
|
|
||||||
|
const Tab = createBottomTabNavigator();
|
||||||
|
|
||||||
|
export default function BottomTabNavigator() {
|
||||||
|
return (
|
||||||
|
<Tab.Navigator
|
||||||
|
screenOptions={({ route }) => ({
|
||||||
|
tabBarIcon: ({ focused, color, size }) => {
|
||||||
|
let iconName: keyof typeof Ionicons.glyphMap;
|
||||||
|
|
||||||
|
if (route.name === 'Home') {
|
||||||
|
iconName = focused ? 'home' : 'home-outline';
|
||||||
|
} else if (route.name === 'Wallet') {
|
||||||
|
iconName = focused ? 'wallet' : 'wallet-outline';
|
||||||
|
} else if (route.name === 'Governance') {
|
||||||
|
iconName = focused ? 'business' : 'business-outline';
|
||||||
|
} else if (route.name === 'Referral') {
|
||||||
|
iconName = focused ? 'gift' : 'gift-outline';
|
||||||
|
} else if (route.name === 'Profile') {
|
||||||
|
iconName = focused ? 'person' : 'person-outline';
|
||||||
|
} else {
|
||||||
|
iconName = 'help-outline';
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Ionicons name={iconName} size={size} color={color} />;
|
||||||
|
},
|
||||||
|
tabBarActiveTintColor: Colors.coral,
|
||||||
|
tabBarInactiveTintColor: Colors.textGray,
|
||||||
|
tabBarStyle: {
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: '#F0F0F0',
|
||||||
|
paddingBottom: 8,
|
||||||
|
paddingTop: 8,
|
||||||
|
height: 60,
|
||||||
|
},
|
||||||
|
tabBarLabelStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
headerShown: false,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Tab.Screen name="Home" component={HomeScreen} />
|
||||||
|
<Tab.Screen name="Wallet" component={WalletScreen} />
|
||||||
|
<Tab.Screen name="Governance" component={GovernanceScreen} />
|
||||||
|
<Tab.Screen name="Referral" component={ReferralScreen} />
|
||||||
|
<Tab.Screen name="Profile" component={ProfileScreen} />
|
||||||
|
</Tab.Navigator>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
|
|
||||||
|
// Auth Screens
|
||||||
|
import LanguageSelectionScreen from '../screens/Auth/LanguageSelectionScreen';
|
||||||
|
import SignUpScreen from '../screens/Auth/SignUpScreen';
|
||||||
|
|
||||||
|
// Main App
|
||||||
|
import BottomTabNavigator from './BottomTabNavigator';
|
||||||
|
|
||||||
|
// Identity & KYC Screens
|
||||||
|
import IdentityKYCFormScreen from '../screens/Identity/IdentityKYCFormScreen';
|
||||||
|
import CitizenCardScreen from '../screens/Identity/CitizenCardScreen';
|
||||||
|
|
||||||
|
// Additional Screens
|
||||||
|
import EducationScreen from '../screens/Education/EducationScreen';
|
||||||
|
import BusinessScreen from '../screens/Business/BusinessScreen';
|
||||||
|
import ExchangeScreen from '../screens/Exchange/ExchangeScreen';
|
||||||
|
import ReferralScreen from '../screens/Referral/ReferralScreen';
|
||||||
|
|
||||||
|
const Stack = createNativeStackNavigator();
|
||||||
|
|
||||||
|
export default function RootNavigator() {
|
||||||
|
return (
|
||||||
|
<NavigationContainer>
|
||||||
|
<Stack.Navigator
|
||||||
|
initialRouteName="LanguageSelection"
|
||||||
|
screenOptions={{
|
||||||
|
headerShown: false,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Auth Flow */}
|
||||||
|
<Stack.Screen name="LanguageSelection" component={LanguageSelectionScreen} />
|
||||||
|
<Stack.Screen name="SignUp" component={SignUpScreen} />
|
||||||
|
|
||||||
|
{/* Main App */}
|
||||||
|
<Stack.Screen name="MainTabs" component={BottomTabNavigator} />
|
||||||
|
|
||||||
|
{/* Identity & KYC */}
|
||||||
|
<Stack.Screen name="IdentityKYCForm" component={IdentityKYCFormScreen} />
|
||||||
|
<Stack.Screen name="CitizenCard" component={CitizenCardScreen} />
|
||||||
|
|
||||||
|
{/* Additional Screens */}
|
||||||
|
<Stack.Screen name="Education" component={EducationScreen} />
|
||||||
|
<Stack.Screen name="Business" component={BusinessScreen} />
|
||||||
|
<Stack.Screen name="Exchange" component={ExchangeScreen} />
|
||||||
|
<Stack.Screen name="Referral" component={ReferralScreen} />
|
||||||
|
</Stack.Navigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Ministries screen to imports and stack
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
SafeAreaView,
|
||||||
|
StatusBar,
|
||||||
|
} from 'react-native';
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius } from '../../constants/theme';
|
||||||
|
|
||||||
|
interface Language {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
nativeName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LANGUAGES: Language[] = [
|
||||||
|
{ code: 'en', name: 'English', nativeName: 'English' },
|
||||||
|
{ code: 'ku', name: 'Kurdish (Kurmanji)', nativeName: 'Kurdî (Kurmancî)' },
|
||||||
|
{ code: 'ckb', name: 'Kurdish (Sorani)', nativeName: 'کوردی (سۆرانی)' },
|
||||||
|
{ code: 'tr', name: 'Turkish', nativeName: 'Türkçe' },
|
||||||
|
{ code: 'ar', name: 'Arabic', nativeName: 'العربية' },
|
||||||
|
{ code: 'fa', name: 'Persian', nativeName: 'فارسی' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function LanguageSelectionScreen({ navigation }: any) {
|
||||||
|
const [selectedLanguage, setSelectedLanguage] = useState<string>('en');
|
||||||
|
|
||||||
|
const handleContinue = () => {
|
||||||
|
// Save language preference
|
||||||
|
// TODO: Implement i18n language switching
|
||||||
|
navigation.navigate('SignUp');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<StatusBar barStyle="light-content" />
|
||||||
|
<LinearGradient
|
||||||
|
colors={['#F08080', '#F5B895', '#E8C896']}
|
||||||
|
style={styles.gradient}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 0, y: 1 }}
|
||||||
|
>
|
||||||
|
<View style={styles.content}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.title}>Welcome to</Text>
|
||||||
|
<Text style={styles.brandName}>PezkuwiChain</Text>
|
||||||
|
<Text style={styles.subtitle}>Your Digital Citizenship Platform</Text>
|
||||||
|
<Text style={styles.description}>
|
||||||
|
Building the future of Kurdish digital infrastructure
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Language Selection */}
|
||||||
|
<View style={styles.languageSection}>
|
||||||
|
<Text style={styles.sectionTitle}>Select Your Language</Text>
|
||||||
|
|
||||||
|
<View style={styles.languageGrid}>
|
||||||
|
{LANGUAGES.map((language) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={language.code}
|
||||||
|
style={[
|
||||||
|
styles.languageButton,
|
||||||
|
selectedLanguage === language.code && styles.languageButtonSelected,
|
||||||
|
]}
|
||||||
|
onPress={() => setSelectedLanguage(language.code)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.languageText,
|
||||||
|
selectedLanguage === language.code && styles.languageTextSelected,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{language.nativeName}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Continue Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.continueButton}
|
||||||
|
onPress={handleContinue}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
>
|
||||||
|
<Text style={styles.continueButtonText}>Get Started →</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</LinearGradient>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.coral,
|
||||||
|
},
|
||||||
|
gradient: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
paddingVertical: Spacing.xxxl,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: Spacing.xxxl,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: Typography.sizes.heading,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
brandName: {
|
||||||
|
fontSize: Typography.sizes.display,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.medium,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
fontSize: Typography.sizes.body,
|
||||||
|
color: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
languageSection: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
languageGrid: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: Spacing.md,
|
||||||
|
},
|
||||||
|
languageButton: {
|
||||||
|
width: '48%',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
paddingVertical: Spacing.lg,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: 'transparent',
|
||||||
|
},
|
||||||
|
languageButtonSelected: {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
borderColor: '#FFFFFF',
|
||||||
|
},
|
||||||
|
languageText: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.medium,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
languageTextSelected: {
|
||||||
|
color: Colors.coral,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
},
|
||||||
|
continueButton: {
|
||||||
|
backgroundColor: 'rgba(212, 160, 23, 0.9)',
|
||||||
|
borderRadius: BorderRadius.xxlarge,
|
||||||
|
paddingVertical: Spacing.lg,
|
||||||
|
paddingHorizontal: Spacing.xxxl,
|
||||||
|
alignItems: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 5,
|
||||||
|
},
|
||||||
|
continueButtonText: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TextInput,
|
||||||
|
TouchableOpacity,
|
||||||
|
SafeAreaView,
|
||||||
|
KeyboardAvoidingView,
|
||||||
|
Platform,
|
||||||
|
ScrollView,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
|
||||||
|
export default function SignUpScreen({ navigation }: any) {
|
||||||
|
const [isSignUp, setIsSignUp] = useState(true);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
fullName: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
referralCode: '',
|
||||||
|
});
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
// TODO: Implement authentication logic
|
||||||
|
navigation.navigate('MainTabs');
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleMode = () => {
|
||||||
|
setIsSignUp(!isSignUp);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<KeyboardAvoidingView
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
|
style={styles.keyboardView}
|
||||||
|
>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.scrollContent}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.logo}>PezkuwiChain</Text>
|
||||||
|
<Text style={styles.subtitle}>Access your governance account</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Toggle Buttons */}
|
||||||
|
<View style={styles.toggleContainer}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.toggleButton, !isSignUp && styles.toggleButtonActive]}
|
||||||
|
onPress={() => setIsSignUp(false)}
|
||||||
|
>
|
||||||
|
<Text style={[styles.toggleText, !isSignUp && styles.toggleTextActive]}>
|
||||||
|
Sign In
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.toggleButton, isSignUp && styles.toggleButtonActive]}
|
||||||
|
onPress={() => setIsSignUp(true)}
|
||||||
|
>
|
||||||
|
<Text style={[styles.toggleText, isSignUp && styles.toggleTextActive]}>
|
||||||
|
Sign Up
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
|
<View style={styles.form}>
|
||||||
|
{isSignUp && (
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Full Name</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="person-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="John Doe"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
value={formData.fullName}
|
||||||
|
onChangeText={(text) => setFormData({ ...formData, fullName: text })}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Email</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="mail-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="name@example.com"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
keyboardType="email-address"
|
||||||
|
autoCapitalize="none"
|
||||||
|
value={formData.email}
|
||||||
|
onChangeText={(text) => setFormData({ ...formData, email: text })}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Password</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="lock-closed-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="••••••••"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
secureTextEntry={!showPassword}
|
||||||
|
value={formData.password}
|
||||||
|
onChangeText={(text) => setFormData({ ...formData, password: text })}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity onPress={() => setShowPassword(!showPassword)}>
|
||||||
|
<Ionicons
|
||||||
|
name={showPassword ? 'eye-outline' : 'eye-off-outline'}
|
||||||
|
size={20}
|
||||||
|
color={Colors.textGray}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{isSignUp && (
|
||||||
|
<>
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Confirm Password</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="lock-closed-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="••••••••"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
secureTextEntry={!showConfirmPassword}
|
||||||
|
value={formData.confirmPassword}
|
||||||
|
onChangeText={(text) =>
|
||||||
|
setFormData({ ...formData, confirmPassword: text })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name={showConfirmPassword ? 'eye-outline' : 'eye-off-outline'}
|
||||||
|
size={20}
|
||||||
|
color={Colors.textGray}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Referral Code (Optional)</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="gift-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Enter referral code"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
autoCapitalize="characters"
|
||||||
|
value={formData.referralCode}
|
||||||
|
onChangeText={(text) =>
|
||||||
|
setFormData({ ...formData, referralCode: text })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isSignUp && (
|
||||||
|
<TouchableOpacity style={styles.forgotPassword}>
|
||||||
|
<Text style={styles.forgotPasswordText}>Forgot Password?</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<TouchableOpacity style={styles.submitButton} onPress={handleSubmit}>
|
||||||
|
<Text style={styles.submitButtonText}>
|
||||||
|
{isSignUp ? 'Create Account' : 'Sign In'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<View style={styles.footer}>
|
||||||
|
<Text style={styles.footerText}>
|
||||||
|
{isSignUp ? 'Already have an account?' : "Don't have an account?"}
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity onPress={toggleMode}>
|
||||||
|
<Text style={styles.footerLink}>
|
||||||
|
{isSignUp ? 'Sign In' : 'Sign Up'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.navy,
|
||||||
|
},
|
||||||
|
keyboardView: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
scrollContent: {
|
||||||
|
flexGrow: 1,
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
paddingVertical: Spacing.xxxl,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: Spacing.xxxl,
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
fontSize: Typography.sizes.display,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: Colors.mint,
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
color: Colors.textLight,
|
||||||
|
},
|
||||||
|
toggleContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
padding: Spacing.xs,
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
toggleButton: {
|
||||||
|
flex: 1,
|
||||||
|
paddingVertical: Spacing.md,
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius: BorderRadius.medium,
|
||||||
|
},
|
||||||
|
toggleButtonActive: {
|
||||||
|
backgroundColor: Colors.mint,
|
||||||
|
},
|
||||||
|
toggleText: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.medium,
|
||||||
|
color: Colors.textLight,
|
||||||
|
},
|
||||||
|
toggleTextActive: {
|
||||||
|
color: Colors.navy,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
inputContainer: {
|
||||||
|
marginBottom: Spacing.lg,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: Typography.sizes.body,
|
||||||
|
fontWeight: Typography.weights.medium,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
inputWrapper: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
borderRadius: BorderRadius.medium,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: Spacing.sm,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
marginLeft: Spacing.sm,
|
||||||
|
},
|
||||||
|
forgotPassword: {
|
||||||
|
alignSelf: 'flex-end',
|
||||||
|
marginBottom: Spacing.lg,
|
||||||
|
},
|
||||||
|
forgotPasswordText: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: Colors.mint,
|
||||||
|
},
|
||||||
|
submitButton: {
|
||||||
|
backgroundColor: Colors.peach,
|
||||||
|
borderRadius: BorderRadius.xxlarge,
|
||||||
|
paddingVertical: Spacing.lg,
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
...Shadow.soft,
|
||||||
|
},
|
||||||
|
submitButtonText: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
gap: Spacing.xs,
|
||||||
|
},
|
||||||
|
footerText: {
|
||||||
|
fontSize: Typography.sizes.body,
|
||||||
|
color: Colors.textLight,
|
||||||
|
},
|
||||||
|
footerLink: {
|
||||||
|
fontSize: Typography.sizes.body,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.mint,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,794 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
TextInput,
|
||||||
|
Modal,
|
||||||
|
Alert,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import QRCode from 'react-native-qrcode-svg';
|
||||||
|
import * as Clipboard from 'expo-clipboard';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
|
||||||
|
interface BusinessStats {
|
||||||
|
totalSales: number;
|
||||||
|
todaySales: number;
|
||||||
|
pendingPayments: number;
|
||||||
|
totalCustomers: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Transaction {
|
||||||
|
id: string;
|
||||||
|
customer: string;
|
||||||
|
amount: number;
|
||||||
|
token: 'HEZ' | 'PEZ';
|
||||||
|
date: string;
|
||||||
|
status: 'completed' | 'pending' | 'failed';
|
||||||
|
invoiceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BusinessScreen({ navigation }: any) {
|
||||||
|
const [merchantAddress] = useState('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
|
||||||
|
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
||||||
|
const [paymentAmount, setPaymentAmount] = useState('');
|
||||||
|
const [selectedToken, setSelectedToken] = useState<'HEZ' | 'PEZ'>('PEZ');
|
||||||
|
const [paymentNote, setPaymentNote] = useState('');
|
||||||
|
|
||||||
|
const [stats] = useState<BusinessStats>({
|
||||||
|
totalSales: 125450,
|
||||||
|
todaySales: 3250,
|
||||||
|
pendingPayments: 850,
|
||||||
|
totalCustomers: 342,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [transactions] = useState<Transaction[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
customer: 'Ahmed Karwan',
|
||||||
|
amount: 500,
|
||||||
|
token: 'PEZ',
|
||||||
|
date: '2024-01-23 14:30',
|
||||||
|
status: 'completed',
|
||||||
|
invoiceId: 'INV-2024-001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
customer: 'Layla Sherzad',
|
||||||
|
amount: 1250,
|
||||||
|
token: 'HEZ',
|
||||||
|
date: '2024-01-23 12:15',
|
||||||
|
status: 'completed',
|
||||||
|
invoiceId: 'INV-2024-002',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
customer: 'Saman Aziz',
|
||||||
|
amount: 750,
|
||||||
|
token: 'PEZ',
|
||||||
|
date: '2024-01-23 10:45',
|
||||||
|
status: 'pending',
|
||||||
|
invoiceId: 'INV-2024-003',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
customer: 'Hana Dilshad',
|
||||||
|
amount: 350,
|
||||||
|
token: 'PEZ',
|
||||||
|
date: '2024-01-22 16:20',
|
||||||
|
status: 'completed',
|
||||||
|
invoiceId: 'INV-2024-004',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const generatePaymentQR = () => {
|
||||||
|
if (!paymentAmount || parseFloat(paymentAmount) <= 0) {
|
||||||
|
Alert.alert('Invalid Amount', 'Please enter a valid payment amount');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setShowPaymentModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyMerchantAddress = async () => {
|
||||||
|
await Clipboard.setStringAsync(merchantAddress);
|
||||||
|
Alert.alert('✓ Copied!', 'Merchant address copied to clipboard');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'completed':
|
||||||
|
return Colors.success;
|
||||||
|
case 'pending':
|
||||||
|
return Colors.warning;
|
||||||
|
case 'failed':
|
||||||
|
return Colors.error;
|
||||||
|
default:
|
||||||
|
return Colors.textGray;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentData = JSON.stringify({
|
||||||
|
merchant: merchantAddress,
|
||||||
|
amount: paymentAmount,
|
||||||
|
token: selectedToken,
|
||||||
|
note: paymentNote,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Header */}
|
||||||
|
<LinearGradient
|
||||||
|
colors={[Colors.kurdishGold, Colors.coral]}
|
||||||
|
style={styles.header}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<View style={styles.headerContent}>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.headerTitle}>Business Hub</Text>
|
||||||
|
<Text style={styles.headerSubtitle}>Merchant Payment Center</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.settingsButton}
|
||||||
|
onPress={() => Alert.alert('Settings', 'Business settings coming soon')}
|
||||||
|
>
|
||||||
|
<Ionicons name="settings-outline" size={24} color="white" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
{/* Statistics Grid */}
|
||||||
|
<View style={styles.statsContainer}>
|
||||||
|
<View style={styles.statsGrid}>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons name="cash" size={28} color={Colors.success} />
|
||||||
|
<Text style={styles.statValue}>{stats.totalSales.toLocaleString()}</Text>
|
||||||
|
<Text style={styles.statLabel}>Total Sales (PEZ)</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons name="trending-up" size={28} color={Colors.primary} />
|
||||||
|
<Text style={styles.statValue}>{stats.todaySales.toLocaleString()}</Text>
|
||||||
|
<Text style={styles.statLabel}>Today's Sales</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons name="time" size={28} color={Colors.warning} />
|
||||||
|
<Text style={styles.statValue}>{stats.pendingPayments.toLocaleString()}</Text>
|
||||||
|
<Text style={styles.statLabel}>Pending</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons name="people" size={28} color={Colors.kurdishGold} />
|
||||||
|
<Text style={styles.statValue}>{stats.totalCustomers}</Text>
|
||||||
|
<Text style={styles.statLabel}>Customers</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Quick Actions */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Quick Actions</Text>
|
||||||
|
<View style={styles.actionsGrid}>
|
||||||
|
<TouchableOpacity style={styles.actionCard} activeOpacity={0.7}>
|
||||||
|
<View style={[styles.actionIcon, { backgroundColor: Colors.primary + '20' }]}>
|
||||||
|
<Ionicons name="qr-code" size={32} color={Colors.primary} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.actionLabel}>Generate QR</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.actionCard} activeOpacity={0.7}>
|
||||||
|
<View style={[styles.actionIcon, { backgroundColor: Colors.success + '20' }]}>
|
||||||
|
<Ionicons name="document-text" size={32} color={Colors.success} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.actionLabel}>Create Invoice</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.actionCard} activeOpacity={0.7}>
|
||||||
|
<View style={[styles.actionIcon, { backgroundColor: Colors.kurdishGold + '20' }]}>
|
||||||
|
<Ionicons name="stats-chart" size={32} color={Colors.kurdishGold} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.actionLabel}>Analytics</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.actionCard} activeOpacity={0.7}>
|
||||||
|
<View style={[styles.actionIcon, { backgroundColor: Colors.coral + '20' }]}>
|
||||||
|
<Ionicons name="card" size={32} color={Colors.coral} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.actionLabel}>Payment Link</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Payment Request Form */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Create Payment Request</Text>
|
||||||
|
<View style={styles.formCard}>
|
||||||
|
<View style={styles.inputGroup}>
|
||||||
|
<Text style={styles.inputLabel}>Amount</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Enter amount"
|
||||||
|
keyboardType="decimal-pad"
|
||||||
|
value={paymentAmount}
|
||||||
|
onChangeText={setPaymentAmount}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputGroup}>
|
||||||
|
<Text style={styles.inputLabel}>Token</Text>
|
||||||
|
<View style={styles.tokenSelector}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.tokenOption,
|
||||||
|
selectedToken === 'PEZ' && styles.tokenOptionActive,
|
||||||
|
]}
|
||||||
|
onPress={() => setSelectedToken('PEZ')}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.tokenOptionText,
|
||||||
|
selectedToken === 'PEZ' && styles.tokenOptionTextActive,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
PEZ
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.tokenOption,
|
||||||
|
selectedToken === 'HEZ' && styles.tokenOptionActive,
|
||||||
|
]}
|
||||||
|
onPress={() => setSelectedToken('HEZ')}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.tokenOptionText,
|
||||||
|
selectedToken === 'HEZ' && styles.tokenOptionTextActive,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
HEZ
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputGroup}>
|
||||||
|
<Text style={styles.inputLabel}>Note (Optional)</Text>
|
||||||
|
<TextInput
|
||||||
|
style={[styles.input, styles.textArea]}
|
||||||
|
placeholder="Payment description"
|
||||||
|
multiline
|
||||||
|
numberOfLines={3}
|
||||||
|
value={paymentNote}
|
||||||
|
onChangeText={setPaymentNote}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.generateButton}
|
||||||
|
onPress={generatePaymentQR}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
>
|
||||||
|
<LinearGradient
|
||||||
|
colors={[Colors.primary, Colors.success]}
|
||||||
|
style={styles.generateButtonGradient}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 0 }}
|
||||||
|
>
|
||||||
|
<Ionicons name="qr-code" size={20} color="white" />
|
||||||
|
<Text style={styles.generateButtonText}>Generate Payment QR</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Recent Transactions */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<View style={styles.sectionHeader}>
|
||||||
|
<Text style={styles.sectionTitle}>Recent Transactions</Text>
|
||||||
|
<TouchableOpacity>
|
||||||
|
<Text style={styles.viewAllText}>View All</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{transactions.map((transaction) => (
|
||||||
|
<View key={transaction.id} style={styles.transactionCard}>
|
||||||
|
<View style={styles.transactionHeader}>
|
||||||
|
<View style={styles.transactionIcon}>
|
||||||
|
<Ionicons
|
||||||
|
name={
|
||||||
|
transaction.status === 'completed'
|
||||||
|
? 'checkmark-circle'
|
||||||
|
: transaction.status === 'pending'
|
||||||
|
? 'time'
|
||||||
|
: 'close-circle'
|
||||||
|
}
|
||||||
|
size={24}
|
||||||
|
color={getStatusColor(transaction.status)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.transactionInfo}>
|
||||||
|
<Text style={styles.transactionCustomer}>
|
||||||
|
{transaction.customer}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.transactionDate}>{transaction.date}</Text>
|
||||||
|
<Text style={styles.transactionInvoice}>
|
||||||
|
{transaction.invoiceId}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.transactionAmount}>
|
||||||
|
<Text style={styles.amountValue}>
|
||||||
|
{transaction.amount.toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.amountToken}>{transaction.token}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.statusBadge,
|
||||||
|
{ backgroundColor: getStatusColor(transaction.status) + '20' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.statusText,
|
||||||
|
{ color: getStatusColor(transaction.status) },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{transaction.status.charAt(0).toUpperCase() +
|
||||||
|
transaction.status.slice(1)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Merchant Info */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Merchant Information</Text>
|
||||||
|
<View style={styles.merchantCard}>
|
||||||
|
<View style={styles.merchantHeader}>
|
||||||
|
<Ionicons name="storefront" size={24} color={Colors.primary} />
|
||||||
|
<Text style={styles.merchantTitle}>Your Merchant Address</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.addressBox}
|
||||||
|
onPress={copyMerchantAddress}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text style={styles.addressText} numberOfLines={1}>
|
||||||
|
{merchantAddress}
|
||||||
|
</Text>
|
||||||
|
<Ionicons name="copy-outline" size={20} color={Colors.primary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.merchantNote}>
|
||||||
|
Share this address with customers to receive payments
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ height: 40 }} />
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
{/* Payment QR Modal */}
|
||||||
|
<Modal
|
||||||
|
visible={showPaymentModal}
|
||||||
|
transparent
|
||||||
|
animationType="fade"
|
||||||
|
onRequestClose={() => setShowPaymentModal(false)}
|
||||||
|
>
|
||||||
|
<View style={styles.modalOverlay}>
|
||||||
|
<View style={styles.modalContent}>
|
||||||
|
<View style={styles.modalHeader}>
|
||||||
|
<Text style={styles.modalTitle}>Payment QR Code</Text>
|
||||||
|
<TouchableOpacity onPress={() => setShowPaymentModal(false)}>
|
||||||
|
<Ionicons name="close" size={28} color={Colors.textDark} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.qrContainer}>
|
||||||
|
<QRCode value={paymentData} size={200} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.paymentDetails}>
|
||||||
|
<View style={styles.paymentDetailRow}>
|
||||||
|
<Text style={styles.paymentDetailLabel}>Amount:</Text>
|
||||||
|
<Text style={styles.paymentDetailValue}>
|
||||||
|
{paymentAmount} {selectedToken}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
{paymentNote ? (
|
||||||
|
<View style={styles.paymentDetailRow}>
|
||||||
|
<Text style={styles.paymentDetailLabel}>Note:</Text>
|
||||||
|
<Text style={styles.paymentDetailValue}>{paymentNote}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={styles.qrInstruction}>
|
||||||
|
Ask customer to scan this QR code with their PezkuwiChain app
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.doneButton}
|
||||||
|
onPress={() => setShowPaymentModal(false)}
|
||||||
|
>
|
||||||
|
<Text style={styles.doneButtonText}>Done</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingTop: 20,
|
||||||
|
paddingBottom: 24,
|
||||||
|
borderBottomLeftRadius: 24,
|
||||||
|
borderBottomRightRadius: 24,
|
||||||
|
},
|
||||||
|
headerContent: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: 'white',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
headerSubtitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
},
|
||||||
|
settingsButton: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 24,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
statsContainer: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
marginTop: -30,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
statsGrid: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
statCard: {
|
||||||
|
flex: 1,
|
||||||
|
minWidth: '47%',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 16,
|
||||||
|
alignItems: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
statValue: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginTop: 8,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
statLabel: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.textGray,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
viewAllText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.primary,
|
||||||
|
},
|
||||||
|
actionsGrid: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
actionCard: {
|
||||||
|
flex: 1,
|
||||||
|
minWidth: '47%',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 16,
|
||||||
|
alignItems: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
actionIcon: {
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
borderRadius: 16,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
actionLabel: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
formCard: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 20,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
inputGroup: {
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
inputLabel: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
borderRadius: 10,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 12,
|
||||||
|
fontSize: 15,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
textArea: {
|
||||||
|
height: 80,
|
||||||
|
textAlignVertical: 'top',
|
||||||
|
},
|
||||||
|
tokenSelector: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
tokenOption: {
|
||||||
|
flex: 1,
|
||||||
|
paddingVertical: 12,
|
||||||
|
borderRadius: 10,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
tokenOptionActive: {
|
||||||
|
backgroundColor: Colors.primary,
|
||||||
|
},
|
||||||
|
tokenOptionText: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
tokenOptionTextActive: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
generateButton: {
|
||||||
|
borderRadius: 10,
|
||||||
|
overflow: 'hidden',
|
||||||
|
marginTop: 8,
|
||||||
|
},
|
||||||
|
generateButtonGradient: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 14,
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
generateButtonText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
transactionCard: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 12,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
transactionHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
transactionIcon: {
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
transactionInfo: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
transactionCustomer: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
transactionDate: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
transactionInvoice: {
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.textGray,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
},
|
||||||
|
transactionAmount: {
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
},
|
||||||
|
amountValue: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
amountToken: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
statusBadge: {
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
statusText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
merchantCard: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 20,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
merchantHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
merchantTitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
addressBox: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: 12,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
addressText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.textDark,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
merchantNote: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.textGray,
|
||||||
|
lineHeight: 18,
|
||||||
|
},
|
||||||
|
modalOverlay: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
modalContent: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 20,
|
||||||
|
padding: 24,
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: 400,
|
||||||
|
},
|
||||||
|
modalHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
modalTitle: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
qrContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 20,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
borderRadius: 16,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
paymentDetails: {
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
paymentDetailRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
paymentDetailLabel: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
paymentDetailValue: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
qrInstruction: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.textGray,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: 20,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
doneButton: {
|
||||||
|
backgroundColor: Colors.primary,
|
||||||
|
borderRadius: 10,
|
||||||
|
paddingVertical: 14,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
doneButtonText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,626 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
Share,
|
||||||
|
Alert,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
|
||||||
|
interface Certificate {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
institution: string;
|
||||||
|
issueDate: string;
|
||||||
|
certificateId: string;
|
||||||
|
type: 'degree' | 'course' | 'skill' | 'achievement';
|
||||||
|
verified: boolean;
|
||||||
|
ipfsHash?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EducationStats {
|
||||||
|
totalCertificates: number;
|
||||||
|
verifiedCertificates: number;
|
||||||
|
skillsAcquired: number;
|
||||||
|
learningHours: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EducationScreen({ navigation }: any) {
|
||||||
|
const [stats] = useState<EducationStats>({
|
||||||
|
totalCertificates: 8,
|
||||||
|
verifiedCertificates: 6,
|
||||||
|
skillsAcquired: 15,
|
||||||
|
learningHours: 240,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [certificates] = useState<Certificate[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Bachelor of Computer Science',
|
||||||
|
institution: 'University of Kurdistan',
|
||||||
|
issueDate: '2022-06-15',
|
||||||
|
certificateId: 'UOK-CS-2022-1234',
|
||||||
|
type: 'degree',
|
||||||
|
verified: true,
|
||||||
|
ipfsHash: 'QmX7Y8Z9...',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Blockchain Development Certificate',
|
||||||
|
institution: 'Digital Kurdistan Academy',
|
||||||
|
issueDate: '2023-03-20',
|
||||||
|
certificateId: 'DKA-BC-2023-5678',
|
||||||
|
type: 'course',
|
||||||
|
verified: true,
|
||||||
|
ipfsHash: 'QmA1B2C3...',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
title: 'Kurdish Language Proficiency',
|
||||||
|
institution: 'Kurdistan Cultural Institute',
|
||||||
|
issueDate: '2023-08-10',
|
||||||
|
certificateId: 'KCI-KL-2023-9012',
|
||||||
|
type: 'skill',
|
||||||
|
verified: true,
|
||||||
|
ipfsHash: 'QmD4E5F6...',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
title: 'Smart Contract Security',
|
||||||
|
institution: 'PezkuwiChain Foundation',
|
||||||
|
issueDate: '2024-01-15',
|
||||||
|
certificateId: 'PKW-SCS-2024-3456',
|
||||||
|
type: 'course',
|
||||||
|
verified: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const getCertificateIcon = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'degree':
|
||||||
|
return 'school';
|
||||||
|
case 'course':
|
||||||
|
return 'book';
|
||||||
|
case 'skill':
|
||||||
|
return 'ribbon';
|
||||||
|
case 'achievement':
|
||||||
|
return 'trophy';
|
||||||
|
default:
|
||||||
|
return 'document';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCertificateColor = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'degree':
|
||||||
|
return Colors.primary;
|
||||||
|
case 'course':
|
||||||
|
return Colors.success;
|
||||||
|
case 'skill':
|
||||||
|
return Colors.kurdishGold;
|
||||||
|
case 'achievement':
|
||||||
|
return Colors.coral;
|
||||||
|
default:
|
||||||
|
return Colors.textGray;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const shareCertificate = async (certificate: Certificate) => {
|
||||||
|
try {
|
||||||
|
await Share.share({
|
||||||
|
message: `🎓 ${certificate.title}\n\nIssued by: ${certificate.institution}\nDate: ${certificate.issueDate}\nCertificate ID: ${certificate.certificateId}\n\nVerified on PezkuwiChain - Digital Kurdistan's Blockchain Platform`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sharing:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyCertificate = (certificate: Certificate) => {
|
||||||
|
if (certificate.verified) {
|
||||||
|
Alert.alert(
|
||||||
|
'✓ Verified Certificate',
|
||||||
|
`This certificate is verified on PezkuwiChain blockchain.\n\nIPFS Hash: ${certificate.ipfsHash}\nCertificate ID: ${certificate.certificateId}`,
|
||||||
|
[{ text: 'OK' }]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Alert.alert(
|
||||||
|
'Pending Verification',
|
||||||
|
'This certificate is pending blockchain verification. Please check back later.',
|
||||||
|
[{ text: 'OK' }]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Header */}
|
||||||
|
<LinearGradient
|
||||||
|
colors={[Colors.primary, Colors.success]}
|
||||||
|
style={styles.header}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<View style={styles.headerContent}>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.headerTitle}>Perwerde</Text>
|
||||||
|
<Text style={styles.headerSubtitle}>Education & Certificates</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.addButton}
|
||||||
|
onPress={() =>
|
||||||
|
Alert.alert(
|
||||||
|
'Add Certificate',
|
||||||
|
'Request your institution to issue a certificate on PezkuwiChain',
|
||||||
|
[{ text: 'OK' }]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Ionicons name="add" size={24} color="white" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
{/* Statistics Grid */}
|
||||||
|
<View style={styles.statsContainer}>
|
||||||
|
<View style={styles.statsGrid}>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons
|
||||||
|
name="document-text"
|
||||||
|
size={28}
|
||||||
|
color={Colors.primary}
|
||||||
|
/>
|
||||||
|
<Text style={styles.statValue}>{stats.totalCertificates}</Text>
|
||||||
|
<Text style={styles.statLabel}>Certificates</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons
|
||||||
|
name="checkmark-circle"
|
||||||
|
size={28}
|
||||||
|
color={Colors.success}
|
||||||
|
/>
|
||||||
|
<Text style={styles.statValue}>{stats.verifiedCertificates}</Text>
|
||||||
|
<Text style={styles.statLabel}>Verified</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons name="bulb" size={28} color={Colors.kurdishGold} />
|
||||||
|
<Text style={styles.statValue}>{stats.skillsAcquired}</Text>
|
||||||
|
<Text style={styles.statLabel}>Skills</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons name="time" size={28} color={Colors.coral} />
|
||||||
|
<Text style={styles.statValue}>{stats.learningHours}</Text>
|
||||||
|
<Text style={styles.statLabel}>Hours</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Certificates Section */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<View style={styles.sectionHeader}>
|
||||||
|
<Text style={styles.sectionTitle}>My Certificates</Text>
|
||||||
|
<TouchableOpacity>
|
||||||
|
<Text style={styles.viewAllText}>View All</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{certificates.map((certificate) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={certificate.id}
|
||||||
|
style={styles.certificateCard}
|
||||||
|
onPress={() => verifyCertificate(certificate)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<View style={styles.certificateHeader}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.certificateIconContainer,
|
||||||
|
{
|
||||||
|
backgroundColor:
|
||||||
|
getCertificateColor(certificate.type) + '20',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name={getCertificateIcon(certificate.type)}
|
||||||
|
size={28}
|
||||||
|
color={getCertificateColor(certificate.type)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.certificateInfo}>
|
||||||
|
<Text style={styles.certificateTitle}>
|
||||||
|
{certificate.title}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.certificateInstitution}>
|
||||||
|
{certificate.institution}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.certificateDate}>
|
||||||
|
Issued: {certificate.issueDate}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.certificateFooter}>
|
||||||
|
<View style={styles.certificateId}>
|
||||||
|
<Ionicons
|
||||||
|
name="finger-print"
|
||||||
|
size={14}
|
||||||
|
color={Colors.textGray}
|
||||||
|
/>
|
||||||
|
<Text style={styles.certificateIdText}>
|
||||||
|
{certificate.certificateId}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.certificateActions}>
|
||||||
|
{certificate.verified && (
|
||||||
|
<View style={styles.verifiedBadge}>
|
||||||
|
<Ionicons
|
||||||
|
name="shield-checkmark"
|
||||||
|
size={14}
|
||||||
|
color={Colors.success}
|
||||||
|
/>
|
||||||
|
<Text style={styles.verifiedText}>Verified</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => shareCertificate(certificate)}
|
||||||
|
style={styles.shareButton}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="share-social-outline"
|
||||||
|
size={20}
|
||||||
|
color={Colors.primary}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Learning Paths */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Recommended Learning Paths</Text>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.pathCard} activeOpacity={0.7}>
|
||||||
|
<LinearGradient
|
||||||
|
colors={[Colors.primary + '20', Colors.success + '20']}
|
||||||
|
style={styles.pathGradient}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<View style={styles.pathContent}>
|
||||||
|
<View style={styles.pathIcon}>
|
||||||
|
<Ionicons name="code-slash" size={32} color={Colors.primary} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.pathInfo}>
|
||||||
|
<Text style={styles.pathTitle}>
|
||||||
|
Blockchain Developer Path
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.pathDescription}>
|
||||||
|
Master Substrate & Polkadot development
|
||||||
|
</Text>
|
||||||
|
<View style={styles.pathMeta}>
|
||||||
|
<View style={styles.pathMetaItem}>
|
||||||
|
<Ionicons name="book" size={14} color={Colors.textGray} />
|
||||||
|
<Text style={styles.pathMetaText}>8 Courses</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.pathMetaItem}>
|
||||||
|
<Ionicons name="time" size={14} color={Colors.textGray} />
|
||||||
|
<Text style={styles.pathMetaText}>120 Hours</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Ionicons
|
||||||
|
name="chevron-forward"
|
||||||
|
size={24}
|
||||||
|
color={Colors.textGray}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.pathCard} activeOpacity={0.7}>
|
||||||
|
<LinearGradient
|
||||||
|
colors={[Colors.kurdishGold + '20', Colors.coral + '20']}
|
||||||
|
style={styles.pathGradient}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<View style={styles.pathContent}>
|
||||||
|
<View style={styles.pathIcon}>
|
||||||
|
<Ionicons name="language" size={32} color={Colors.kurdishGold} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.pathInfo}>
|
||||||
|
<Text style={styles.pathTitle}>Kurdish Culture & Heritage</Text>
|
||||||
|
<Text style={styles.pathDescription}>
|
||||||
|
Learn Kurdish language and history
|
||||||
|
</Text>
|
||||||
|
<View style={styles.pathMeta}>
|
||||||
|
<View style={styles.pathMetaItem}>
|
||||||
|
<Ionicons name="book" size={14} color={Colors.textGray} />
|
||||||
|
<Text style={styles.pathMetaText}>5 Courses</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.pathMetaItem}>
|
||||||
|
<Ionicons name="time" size={14} color={Colors.textGray} />
|
||||||
|
<Text style={styles.pathMetaText}>60 Hours</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Ionicons
|
||||||
|
name="chevron-forward"
|
||||||
|
size={24}
|
||||||
|
color={Colors.textGray}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Info Section */}
|
||||||
|
<View style={styles.infoSection}>
|
||||||
|
<View style={styles.infoCard}>
|
||||||
|
<Ionicons name="information-circle" size={24} color={Colors.primary} />
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
All certificates are stored on PezkuwiChain blockchain and IPFS,
|
||||||
|
ensuring permanent verification and authenticity.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ height: 40 }} />
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingTop: 20,
|
||||||
|
paddingBottom: 24,
|
||||||
|
borderBottomLeftRadius: 24,
|
||||||
|
borderBottomRightRadius: 24,
|
||||||
|
},
|
||||||
|
headerContent: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: 'white',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
headerSubtitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
},
|
||||||
|
addButton: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 24,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
statsContainer: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
marginTop: -30,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
statsGrid: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
statCard: {
|
||||||
|
flex: 1,
|
||||||
|
minWidth: '47%',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 16,
|
||||||
|
alignItems: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
statValue: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginTop: 8,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
statLabel: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.textGray,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
viewAllText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.primary,
|
||||||
|
},
|
||||||
|
certificateCard: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 12,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
certificateHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
certificateIconContainer: {
|
||||||
|
width: 56,
|
||||||
|
height: 56,
|
||||||
|
borderRadius: 12,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
certificateInfo: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
certificateTitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
certificateInstitution: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
certificateDate: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
certificateFooter: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingTop: 12,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: Colors.background,
|
||||||
|
},
|
||||||
|
certificateId: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
certificateIdText: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.textGray,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
},
|
||||||
|
certificateActions: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
verifiedBadge: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
backgroundColor: Colors.success + '20',
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
verifiedText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.success,
|
||||||
|
},
|
||||||
|
shareButton: {
|
||||||
|
padding: 4,
|
||||||
|
},
|
||||||
|
pathCard: {
|
||||||
|
marginBottom: 12,
|
||||||
|
borderRadius: 14,
|
||||||
|
overflow: 'hidden',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
pathGradient: {
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
pathContent: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
pathIcon: {
|
||||||
|
width: 56,
|
||||||
|
height: 56,
|
||||||
|
borderRadius: 12,
|
||||||
|
backgroundColor: 'white',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
pathInfo: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
pathTitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
pathDescription: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
pathMeta: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 16,
|
||||||
|
},
|
||||||
|
pathMetaItem: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
},
|
||||||
|
pathMetaText: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
infoSection: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
infoCard: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: Colors.primary + '10',
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: 16,
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.textDark,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,792 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
TextInput,
|
||||||
|
Alert,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
|
||||||
|
interface ExchangeRate {
|
||||||
|
hezToPez: number;
|
||||||
|
pezToHez: number;
|
||||||
|
lastUpdated: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SwapHistory {
|
||||||
|
id: string;
|
||||||
|
fromToken: 'HEZ' | 'PEZ';
|
||||||
|
toToken: 'HEZ' | 'PEZ';
|
||||||
|
fromAmount: number;
|
||||||
|
toAmount: number;
|
||||||
|
rate: number;
|
||||||
|
date: string;
|
||||||
|
txHash: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ExchangeScreen({ navigation }: any) {
|
||||||
|
const [fromToken, setFromToken] = useState<'HEZ' | 'PEZ'>('HEZ');
|
||||||
|
const [toToken, setToToken] = useState<'HEZ' | 'PEZ'>('PEZ');
|
||||||
|
const [fromAmount, setFromAmount] = useState('');
|
||||||
|
const [toAmount, setToAmount] = useState('');
|
||||||
|
const [slippage, setSlippage] = useState('0.5');
|
||||||
|
|
||||||
|
const [balances] = useState({
|
||||||
|
hez: 45750.5,
|
||||||
|
pez: 1234567,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [exchangeRate] = useState<ExchangeRate>({
|
||||||
|
hezToPez: 27.5,
|
||||||
|
pezToHez: 0.0364,
|
||||||
|
lastUpdated: new Date().toLocaleTimeString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [swapHistory] = useState<SwapHistory[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
fromToken: 'HEZ',
|
||||||
|
toToken: 'PEZ',
|
||||||
|
fromAmount: 100,
|
||||||
|
toAmount: 2750,
|
||||||
|
rate: 27.5,
|
||||||
|
date: '2024-01-23 14:30',
|
||||||
|
txHash: '0x1a2b3c...',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
fromToken: 'PEZ',
|
||||||
|
toToken: 'HEZ',
|
||||||
|
fromAmount: 5000,
|
||||||
|
toAmount: 182,
|
||||||
|
rate: 0.0364,
|
||||||
|
date: '2024-01-22 10:15',
|
||||||
|
txHash: '0x4d5e6f...',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
calculateToAmount();
|
||||||
|
}, [fromAmount, fromToken, toToken]);
|
||||||
|
|
||||||
|
const calculateToAmount = () => {
|
||||||
|
if (!fromAmount || parseFloat(fromAmount) <= 0) {
|
||||||
|
setToAmount('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = parseFloat(fromAmount);
|
||||||
|
let result = 0;
|
||||||
|
|
||||||
|
if (fromToken === 'HEZ' && toToken === 'PEZ') {
|
||||||
|
result = amount * exchangeRate.hezToPez;
|
||||||
|
} else if (fromToken === 'PEZ' && toToken === 'HEZ') {
|
||||||
|
result = amount * exchangeRate.pezToHez;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply slippage
|
||||||
|
const slippagePercent = parseFloat(slippage) / 100;
|
||||||
|
result = result * (1 - slippagePercent);
|
||||||
|
|
||||||
|
setToAmount(result.toFixed(4));
|
||||||
|
};
|
||||||
|
|
||||||
|
const swapTokens = () => {
|
||||||
|
const newFromToken = toToken;
|
||||||
|
const newToToken = fromToken;
|
||||||
|
setFromToken(newFromToken);
|
||||||
|
setToToken(newToToken);
|
||||||
|
setFromAmount(toAmount);
|
||||||
|
};
|
||||||
|
|
||||||
|
const executeSwap = () => {
|
||||||
|
if (!fromAmount || parseFloat(fromAmount) <= 0) {
|
||||||
|
Alert.alert('Invalid Amount', 'Please enter a valid amount to swap');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = parseFloat(fromAmount);
|
||||||
|
const currentBalance = fromToken === 'HEZ' ? balances.hez : balances.pez;
|
||||||
|
|
||||||
|
if (amount > currentBalance) {
|
||||||
|
Alert.alert('Insufficient Balance', `You don't have enough ${fromToken}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Alert.alert(
|
||||||
|
'Confirm Swap',
|
||||||
|
`Swap ${fromAmount} ${fromToken} for ${toAmount} ${toToken}?\n\nRate: 1 ${fromToken} = ${
|
||||||
|
fromToken === 'HEZ' ? exchangeRate.hezToPez : exchangeRate.pezToHez
|
||||||
|
} ${toToken}\nSlippage: ${slippage}%`,
|
||||||
|
[
|
||||||
|
{ text: 'Cancel', style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: 'Confirm',
|
||||||
|
onPress: () => {
|
||||||
|
Alert.alert(
|
||||||
|
'Success!',
|
||||||
|
`Swap executed successfully!\n\nTransaction hash: 0x${Math.random()
|
||||||
|
.toString(16)
|
||||||
|
.substr(2, 8)}...`
|
||||||
|
);
|
||||||
|
setFromAmount('');
|
||||||
|
setToAmount('');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setMaxAmount = () => {
|
||||||
|
const maxBalance = fromToken === 'HEZ' ? balances.hez : balances.pez;
|
||||||
|
setFromAmount(maxBalance.toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCurrentRate = () => {
|
||||||
|
return fromToken === 'HEZ' ? exchangeRate.hezToPez : exchangeRate.pezToHez;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Header */}
|
||||||
|
<LinearGradient
|
||||||
|
colors={[Colors.primary, Colors.kurdishGold]}
|
||||||
|
style={styles.header}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<View style={styles.headerContent}>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.headerTitle}>Exchange</Text>
|
||||||
|
<Text style={styles.headerSubtitle}>Swap HEZ ↔ PEZ Tokens</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.historyButton}
|
||||||
|
onPress={() => Alert.alert('History', 'Full swap history coming soon')}
|
||||||
|
>
|
||||||
|
<Ionicons name="time-outline" size={24} color="white" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
{/* Exchange Rate Card */}
|
||||||
|
<View style={styles.rateContainer}>
|
||||||
|
<View style={styles.rateCard}>
|
||||||
|
<View style={styles.rateHeader}>
|
||||||
|
<Text style={styles.rateTitle}>Current Exchange Rate</Text>
|
||||||
|
<View style={styles.updateBadge}>
|
||||||
|
<Ionicons name="sync" size={12} color={Colors.success} />
|
||||||
|
<Text style={styles.updateText}>Live</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={styles.rateContent}>
|
||||||
|
<View style={styles.rateItem}>
|
||||||
|
<Text style={styles.rateLabel}>1 HEZ =</Text>
|
||||||
|
<Text style={styles.rateValue}>{exchangeRate.hezToPez} PEZ</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.rateDivider} />
|
||||||
|
<View style={styles.rateItem}>
|
||||||
|
<Text style={styles.rateLabel}>1 PEZ =</Text>
|
||||||
|
<Text style={styles.rateValue}>{exchangeRate.pezToHez} HEZ</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.rateUpdate}>
|
||||||
|
Last updated: {exchangeRate.lastUpdated}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Swap Interface */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Swap Tokens</Text>
|
||||||
|
|
||||||
|
{/* From Token */}
|
||||||
|
<View style={styles.swapCard}>
|
||||||
|
<View style={styles.swapHeader}>
|
||||||
|
<Text style={styles.swapLabel}>From</Text>
|
||||||
|
<Text style={styles.balanceText}>
|
||||||
|
Balance: {fromToken === 'HEZ' ? balances.hez.toLocaleString() : balances.pez.toLocaleString()} {fromToken}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<TextInput
|
||||||
|
style={styles.amountInput}
|
||||||
|
placeholder="0.00"
|
||||||
|
keyboardType="decimal-pad"
|
||||||
|
value={fromAmount}
|
||||||
|
onChangeText={setFromAmount}
|
||||||
|
/>
|
||||||
|
<View style={styles.tokenSelector}>
|
||||||
|
<View style={styles.tokenBadge}>
|
||||||
|
<Text style={styles.tokenText}>{fromToken}</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity onPress={setMaxAmount} style={styles.maxButton}>
|
||||||
|
<Text style={styles.maxButtonText}>MAX</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Swap Button */}
|
||||||
|
<View style={styles.swapButtonContainer}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.swapIconButton}
|
||||||
|
onPress={swapTokens}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Ionicons name="swap-vertical" size={24} color={Colors.primary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* To Token */}
|
||||||
|
<View style={styles.swapCard}>
|
||||||
|
<View style={styles.swapHeader}>
|
||||||
|
<Text style={styles.swapLabel}>To</Text>
|
||||||
|
<Text style={styles.balanceText}>
|
||||||
|
Balance: {toToken === 'HEZ' ? balances.hez.toLocaleString() : balances.pez.toLocaleString()} {toToken}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<TextInput
|
||||||
|
style={[styles.amountInput, styles.amountInputDisabled]}
|
||||||
|
placeholder="0.00"
|
||||||
|
value={toAmount}
|
||||||
|
editable={false}
|
||||||
|
/>
|
||||||
|
<View style={styles.tokenSelector}>
|
||||||
|
<View style={styles.tokenBadge}>
|
||||||
|
<Text style={styles.tokenText}>{toToken}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Swap Details */}
|
||||||
|
{fromAmount && toAmount && (
|
||||||
|
<View style={styles.detailsCard}>
|
||||||
|
<View style={styles.detailRow}>
|
||||||
|
<Text style={styles.detailLabel}>Rate</Text>
|
||||||
|
<Text style={styles.detailValue}>
|
||||||
|
1 {fromToken} = {getCurrentRate()} {toToken}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.detailRow}>
|
||||||
|
<Text style={styles.detailLabel}>Slippage Tolerance</Text>
|
||||||
|
<View style={styles.slippageSelector}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.slippageOption,
|
||||||
|
slippage === '0.5' && styles.slippageOptionActive,
|
||||||
|
]}
|
||||||
|
onPress={() => setSlippage('0.5')}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.slippageText,
|
||||||
|
slippage === '0.5' && styles.slippageTextActive,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
0.5%
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.slippageOption,
|
||||||
|
slippage === '1.0' && styles.slippageOptionActive,
|
||||||
|
]}
|
||||||
|
onPress={() => setSlippage('1.0')}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.slippageText,
|
||||||
|
slippage === '1.0' && styles.slippageTextActive,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
1.0%
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.slippageOption,
|
||||||
|
slippage === '2.0' && styles.slippageOptionActive,
|
||||||
|
]}
|
||||||
|
onPress={() => setSlippage('2.0')}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.slippageText,
|
||||||
|
slippage === '2.0' && styles.slippageTextActive,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
2.0%
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={styles.detailRow}>
|
||||||
|
<Text style={styles.detailLabel}>Network Fee</Text>
|
||||||
|
<Text style={styles.detailValue}>~0.01 HEZ</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Execute Swap Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.executeButton}
|
||||||
|
onPress={executeSwap}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
>
|
||||||
|
<LinearGradient
|
||||||
|
colors={[Colors.primary, Colors.success]}
|
||||||
|
style={styles.executeButtonGradient}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 0 }}
|
||||||
|
>
|
||||||
|
<Ionicons name="swap-horizontal" size={20} color="white" />
|
||||||
|
<Text style={styles.executeButtonText}>Swap Tokens</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Recent Swaps */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<View style={styles.sectionHeader}>
|
||||||
|
<Text style={styles.sectionTitle}>Recent Swaps</Text>
|
||||||
|
<TouchableOpacity>
|
||||||
|
<Text style={styles.viewAllText}>View All</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{swapHistory.map((swap) => (
|
||||||
|
<View key={swap.id} style={styles.historyCard}>
|
||||||
|
<View style={styles.historyHeader}>
|
||||||
|
<View style={styles.historyIcon}>
|
||||||
|
<Ionicons
|
||||||
|
name="swap-horizontal"
|
||||||
|
size={24}
|
||||||
|
color={Colors.primary}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.historyInfo}>
|
||||||
|
<Text style={styles.historyTitle}>
|
||||||
|
{swap.fromToken} → {swap.toToken}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.historyDate}>{swap.date}</Text>
|
||||||
|
<Text style={styles.historyHash}>Tx: {swap.txHash}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.historyAmount}>
|
||||||
|
<Text style={styles.historyFromAmount}>
|
||||||
|
-{swap.fromAmount} {swap.fromToken}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.historyToAmount}>
|
||||||
|
+{swap.toAmount} {swap.toToken}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={styles.historyFooter}>
|
||||||
|
<Text style={styles.historyRate}>
|
||||||
|
Rate: 1 {swap.fromToken} = {swap.rate} {swap.toToken}
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity>
|
||||||
|
<Ionicons
|
||||||
|
name="open-outline"
|
||||||
|
size={16}
|
||||||
|
color={Colors.primary}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Info Section */}
|
||||||
|
<View style={styles.infoSection}>
|
||||||
|
<View style={styles.infoCard}>
|
||||||
|
<Ionicons name="information-circle" size={24} color={Colors.primary} />
|
||||||
|
<View style={styles.infoContent}>
|
||||||
|
<Text style={styles.infoTitle}>About Token Swap</Text>
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
Exchange rates are determined by the on-chain liquidity pool. HEZ is the security layer token, while PEZ is the governance token. Swap fees support network validators and liquidity providers.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ height: 40 }} />
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingTop: 20,
|
||||||
|
paddingBottom: 24,
|
||||||
|
borderBottomLeftRadius: 24,
|
||||||
|
borderBottomRightRadius: 24,
|
||||||
|
},
|
||||||
|
headerContent: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: 'white',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
headerSubtitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
},
|
||||||
|
historyButton: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 24,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
rateContainer: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
marginTop: -30,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
rateCard: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 20,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
rateHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
rateTitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
updateBadge: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
backgroundColor: Colors.success + '20',
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
updateText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.success,
|
||||||
|
},
|
||||||
|
rateContent: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
rateItem: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
rateLabel: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
rateValue: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.primary,
|
||||||
|
},
|
||||||
|
rateDivider: {
|
||||||
|
width: 1,
|
||||||
|
height: 40,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
rateUpdate: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.textGray,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
viewAllText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.primary,
|
||||||
|
},
|
||||||
|
swapCard: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 16,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
swapHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
swapLabel: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
balanceText: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
inputContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
amountInput: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
amountInputDisabled: {
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
tokenSelector: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
tokenBadge: {
|
||||||
|
backgroundColor: Colors.primary + '20',
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderRadius: 10,
|
||||||
|
},
|
||||||
|
tokenText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.primary,
|
||||||
|
},
|
||||||
|
maxButton: {
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
maxButtonText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.primary,
|
||||||
|
},
|
||||||
|
swapButtonContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
marginVertical: -12,
|
||||||
|
zIndex: 10,
|
||||||
|
},
|
||||||
|
swapIconButton: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 24,
|
||||||
|
backgroundColor: 'white',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 3,
|
||||||
|
},
|
||||||
|
detailsCard: {
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: 16,
|
||||||
|
marginTop: 16,
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
detailRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
detailLabel: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
detailValue: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
slippageSelector: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
slippageOption: {
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: 'white',
|
||||||
|
},
|
||||||
|
slippageOptionActive: {
|
||||||
|
backgroundColor: Colors.primary,
|
||||||
|
},
|
||||||
|
slippageText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
slippageTextActive: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
executeButton: {
|
||||||
|
borderRadius: 12,
|
||||||
|
overflow: 'hidden',
|
||||||
|
marginTop: 20,
|
||||||
|
},
|
||||||
|
executeButtonGradient: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 16,
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
executeButtonText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
historyCard: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 12,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
historyHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
historyIcon: {
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
borderRadius: 22,
|
||||||
|
backgroundColor: Colors.primary + '20',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
historyInfo: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
historyTitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
historyDate: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
historyHash: {
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.textGray,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
},
|
||||||
|
historyAmount: {
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
},
|
||||||
|
historyFromAmount: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.error,
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
historyToAmount: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.success,
|
||||||
|
},
|
||||||
|
historyFooter: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingTop: 12,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: Colors.background,
|
||||||
|
},
|
||||||
|
historyRate: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
infoSection: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
infoCard: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: Colors.primary + '10',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 16,
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
infoContent: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
infoTitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.textDark,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,416 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
|
Alert,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
|
||||||
|
interface GovernanceService {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
nameKu: string;
|
||||||
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
|
color: string;
|
||||||
|
description: string;
|
||||||
|
route?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GovernanceScreen({ navigation }: any) {
|
||||||
|
const [isHemwelati, setIsHemwelati] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkKYCStatus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const checkKYCStatus = async () => {
|
||||||
|
try {
|
||||||
|
const kycData = await AsyncStorage.getItem('kyc_data');
|
||||||
|
const kycStatus = await AsyncStorage.getItem('kyc_status');
|
||||||
|
|
||||||
|
if (kycData && kycStatus === 'approved') {
|
||||||
|
setIsHemwelati(true);
|
||||||
|
} else {
|
||||||
|
setIsHemwelati(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking KYC status:', error);
|
||||||
|
setIsHemwelati(false);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Main governance services (not ministries)
|
||||||
|
const services: GovernanceService[] = [
|
||||||
|
{
|
||||||
|
id: 'presidency',
|
||||||
|
name: 'Presidency',
|
||||||
|
nameKu: 'Serokayetî',
|
||||||
|
icon: 'business',
|
||||||
|
color: '#E74C3C',
|
||||||
|
description: 'Office of the President',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'parliament',
|
||||||
|
name: 'Parliament',
|
||||||
|
nameKu: 'Meclîs',
|
||||||
|
icon: 'people',
|
||||||
|
color: '#3498DB',
|
||||||
|
description: 'Legislative Assembly - 201 MPs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ministries',
|
||||||
|
name: 'Ministries',
|
||||||
|
nameKu: 'Wezaretî',
|
||||||
|
icon: 'briefcase',
|
||||||
|
color: '#9B59B6',
|
||||||
|
description: 'All Cabinet Ministries',
|
||||||
|
route: 'Ministries',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'voting',
|
||||||
|
name: 'Voting',
|
||||||
|
nameKu: 'Dengdan',
|
||||||
|
icon: 'checkmark-circle',
|
||||||
|
color: '#1ABC9C',
|
||||||
|
description: 'Proposals & Democratic Voting',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'p2p',
|
||||||
|
name: 'P2P Services',
|
||||||
|
nameKu: 'Karûbarên P2P',
|
||||||
|
icon: 'people-circle',
|
||||||
|
color: '#26A69A',
|
||||||
|
description: 'Peer-to-Peer Transactions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'b2b',
|
||||||
|
name: 'B2B Services',
|
||||||
|
nameKu: 'Karûbarên B2B',
|
||||||
|
icon: 'business-outline',
|
||||||
|
color: '#FF9800',
|
||||||
|
description: 'Business-to-Business Platform',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'digital',
|
||||||
|
name: 'Digital Infrastructure',
|
||||||
|
nameKu: 'Binyata Dîjîtal',
|
||||||
|
icon: 'server',
|
||||||
|
color: '#26C6DA',
|
||||||
|
description: 'Technology & Innovation Hub',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'citizenship',
|
||||||
|
name: 'Citizenship Affairs',
|
||||||
|
nameKu: 'Karûbarên Hemwelatî',
|
||||||
|
icon: 'card',
|
||||||
|
color: '#66BB6A',
|
||||||
|
description: 'Identity & Registration Services',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'treasury',
|
||||||
|
name: 'Public Treasury',
|
||||||
|
nameKu: 'Xizêneya Giştî',
|
||||||
|
icon: 'wallet',
|
||||||
|
color: '#F39C12',
|
||||||
|
description: 'National Budget & Spending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'judiciary',
|
||||||
|
name: 'Judiciary',
|
||||||
|
nameKu: 'Dadwerî',
|
||||||
|
icon: 'scale',
|
||||||
|
color: '#34495E',
|
||||||
|
description: 'Court System & Justice',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'elections',
|
||||||
|
name: 'Elections',
|
||||||
|
nameKu: 'Hilbijartinî',
|
||||||
|
icon: 'ballot',
|
||||||
|
color: '#5C6BC0',
|
||||||
|
description: 'Electoral Commission',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ombudsman',
|
||||||
|
name: 'Ombudsman',
|
||||||
|
nameKu: 'Parêzerê Mafan',
|
||||||
|
icon: 'shield-checkmark',
|
||||||
|
color: '#EC407A',
|
||||||
|
description: 'Citizens Rights Protection',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleServicePress = (service: GovernanceService) => {
|
||||||
|
if (!isHemwelati) {
|
||||||
|
Alert.alert(
|
||||||
|
'Access Restricted',
|
||||||
|
'You must complete Identity KYC verification to access Governance features.',
|
||||||
|
[
|
||||||
|
{ text: 'Cancel', style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: 'Complete KYC',
|
||||||
|
onPress: () => navigation.navigate('Identity'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to specific route if available
|
||||||
|
if (service.route) {
|
||||||
|
navigation.navigate(service.route);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show placeholder for other services
|
||||||
|
Alert.alert(
|
||||||
|
service.name,
|
||||||
|
`${service.nameKu}\n\n${service.description}\n\nThis feature is under development.`,
|
||||||
|
[{ text: 'OK' }]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
<Text style={styles.loadingText}>Loading...</Text>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isHemwelati) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={styles.restrictedContainer}>
|
||||||
|
<Ionicons name="lock-closed" size={80} color={Colors.textLight} />
|
||||||
|
<Text style={styles.restrictedTitle}>Access Restricted</Text>
|
||||||
|
<Text style={styles.restrictedText}>
|
||||||
|
Governance features are only available to verified Hemwelatî (Digital Citizens).
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.restrictedText}>
|
||||||
|
Complete your Identity KYC verification to access:
|
||||||
|
</Text>
|
||||||
|
<View style={styles.featureList}>
|
||||||
|
<Text style={styles.featureItem}>• Vote on proposals</Text>
|
||||||
|
<Text style={styles.featureItem}>• Participate in governance</Text>
|
||||||
|
<Text style={styles.featureItem}>• Access government services</Text>
|
||||||
|
<Text style={styles.featureItem}>• View parliamentary sessions</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.kycButton}
|
||||||
|
onPress={() => navigation.navigate('Identity')}
|
||||||
|
>
|
||||||
|
<Ionicons name="checkmark-circle" size={20} color="#FFFFFF" />
|
||||||
|
<Text style={styles.kycButtonText}>Complete Identity KYC</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<View style={styles.headerContent}>
|
||||||
|
<Text style={styles.headerTitle}>Welati Governance</Text>
|
||||||
|
<Text style={styles.headerSubtitle}>Komara Kurdistanê</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.verifiedBadge}>
|
||||||
|
<Ionicons name="checkmark-circle" size={20} color="#27AE60" />
|
||||||
|
<Text style={styles.verifiedText}>Verified Citizen</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Services Grid */}
|
||||||
|
<View style={styles.grid}>
|
||||||
|
{services.map((service) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={service.id}
|
||||||
|
style={styles.serviceCard}
|
||||||
|
onPress={() => handleServicePress(service)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<View style={[styles.iconContainer, { backgroundColor: service.color }]}>
|
||||||
|
<Ionicons name={service.icon} size={32} color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.serviceName}>{service.name}</Text>
|
||||||
|
<Text style={styles.serviceNameKu}>{service.nameKu}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Info Footer */}
|
||||||
|
<View style={styles.infoFooter}>
|
||||||
|
<Ionicons name="information-circle" size={20} color={Colors.primary} />
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
As a verified Hemwelatî, you have full access to all governance features.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
loadingContainer: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
loadingText: {
|
||||||
|
fontSize: Typography.sizes.lg,
|
||||||
|
color: Colors.textLight,
|
||||||
|
},
|
||||||
|
restrictedContainer: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: Spacing.xl,
|
||||||
|
},
|
||||||
|
restrictedTitle: {
|
||||||
|
fontSize: Typography.sizes.xxl,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
},
|
||||||
|
restrictedText: {
|
||||||
|
fontSize: Typography.sizes.md,
|
||||||
|
color: Colors.textLight,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
lineHeight: 22,
|
||||||
|
},
|
||||||
|
featureList: {
|
||||||
|
marginTop: Spacing.md,
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
featureItem: {
|
||||||
|
fontSize: Typography.sizes.md,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: Spacing.xs,
|
||||||
|
},
|
||||||
|
kycButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: Colors.primary,
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
paddingVertical: Spacing.md,
|
||||||
|
borderRadius: BorderRadius.lg,
|
||||||
|
...Shadow.medium,
|
||||||
|
},
|
||||||
|
kycButtonText: {
|
||||||
|
fontSize: Typography.sizes.lg,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
marginLeft: Spacing.sm,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
paddingHorizontal: Spacing.lg,
|
||||||
|
paddingVertical: Spacing.md,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#E0E0E0',
|
||||||
|
...Shadow.small,
|
||||||
|
},
|
||||||
|
headerContent: {
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: Typography.sizes.xxl,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
headerSubtitle: {
|
||||||
|
fontSize: Typography.sizes.md,
|
||||||
|
color: Colors.textLight,
|
||||||
|
marginTop: Spacing.xs,
|
||||||
|
},
|
||||||
|
verifiedBadge: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#E8F5E9',
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: Spacing.xs,
|
||||||
|
borderRadius: BorderRadius.md,
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
},
|
||||||
|
verifiedText: {
|
||||||
|
fontSize: Typography.sizes.sm,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: '#27AE60',
|
||||||
|
marginLeft: Spacing.xs,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
padding: Spacing.md,
|
||||||
|
},
|
||||||
|
serviceCard: {
|
||||||
|
width: '31%',
|
||||||
|
marginHorizontal: '1%',
|
||||||
|
marginBottom: Spacing.lg,
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: BorderRadius.lg,
|
||||||
|
padding: Spacing.md,
|
||||||
|
alignItems: 'center',
|
||||||
|
...Shadow.small,
|
||||||
|
},
|
||||||
|
iconContainer: {
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
borderRadius: BorderRadius.lg,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
serviceName: {
|
||||||
|
fontSize: Typography.sizes.xs,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: Spacing.xs,
|
||||||
|
},
|
||||||
|
serviceNameKu: {
|
||||||
|
fontSize: Typography.sizes.xs,
|
||||||
|
color: Colors.textLight,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
infoFooter: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#E3F2FD',
|
||||||
|
margin: Spacing.lg,
|
||||||
|
padding: Spacing.md,
|
||||||
|
borderRadius: BorderRadius.md,
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: Typography.sizes.sm,
|
||||||
|
color: Colors.primary,
|
||||||
|
marginLeft: Spacing.sm,
|
||||||
|
lineHeight: 18,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { View, Text, StyleSheet, TouchableOpacity, SafeAreaView, ActivityIndicator } from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
import { kycService } from '../../services/kycService';
|
||||||
|
|
||||||
|
export default function GovernanceScreen({ navigation }: any) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [hasAccess, setHasAccess] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkAccess();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const checkAccess = async () => {
|
||||||
|
try {
|
||||||
|
const access = await kycService.hasGovernanceAccess();
|
||||||
|
setHasAccess(access);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to check governance access:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<ActivityIndicator size="large" color={Colors.mint} />
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user doesn't have access (KYC not approved)
|
||||||
|
if (!hasAccess) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.title}>Governance (Welati)</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.lockedCard}>
|
||||||
|
<Ionicons name="lock-closed" size={60} color={Colors.textGray} />
|
||||||
|
<Text style={styles.lockedTitle}>Access Restricted</Text>
|
||||||
|
<Text style={styles.lockedSubtitle}>
|
||||||
|
You need to complete Identity KYC verification to access Governance features.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.requirementsCard}>
|
||||||
|
<Text style={styles.requirementsTitle}>Requirements:</Text>
|
||||||
|
<View style={styles.requirementItem}>
|
||||||
|
<Ionicons name="person-outline" size={20} color={Colors.teal} />
|
||||||
|
<Text style={styles.requirementText}>Complete Identity KYC form</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.requirementItem}>
|
||||||
|
<Ionicons name="checkmark-circle-outline" size={20} color={Colors.teal} />
|
||||||
|
<Text style={styles.requirementText}>Get KYC approval on blockchain</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.requirementItem}>
|
||||||
|
<Ionicons name="card-outline" size={20} color={Colors.teal} />
|
||||||
|
<Text style={styles.requirementText}>Receive Kurdistan Digital Citizen card</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.completeButton}
|
||||||
|
onPress={() => navigation.navigate('Identity')}
|
||||||
|
>
|
||||||
|
<Text style={styles.completeButtonText}>Complete Identity KYC</Text>
|
||||||
|
<Ionicons name="arrow-forward" size={20} color="#FFFFFF" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user has access, show governance features
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.title}>Governance (Welati)</Text>
|
||||||
|
<View style={styles.citizenBadge}>
|
||||||
|
<Ionicons name="shield-checkmark" size={16} color={Colors.mint} />
|
||||||
|
<Text style={styles.citizenBadgeText}>Verified Citizen</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.content}>
|
||||||
|
<Text style={styles.sectionTitle}>Active Proposals</Text>
|
||||||
|
<Text style={styles.comingSoon}>Coming soon...</Text>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: { flex: 1, backgroundColor: Colors.background },
|
||||||
|
header: { padding: Spacing.xl, borderBottomWidth: 1, borderBottomColor: '#F0F0F0' },
|
||||||
|
title: { fontSize: 28, fontWeight: '700', color: Colors.textDark },
|
||||||
|
content: { flex: 1, padding: Spacing.xl },
|
||||||
|
lockedCard: { backgroundColor: Colors.card, padding: Spacing.xxxl, borderRadius: BorderRadius.xlarge, alignItems: 'center', ...Shadow.soft },
|
||||||
|
lockedTitle: { fontSize: 24, fontWeight: '700', color: Colors.textDark, marginTop: Spacing.lg },
|
||||||
|
lockedSubtitle: { fontSize: 16, color: Colors.textGray, marginTop: Spacing.sm, textAlign: 'center', lineHeight: 24 },
|
||||||
|
requirementsCard: { backgroundColor: Colors.teal + '20', padding: Spacing.xl, borderRadius: BorderRadius.large, marginTop: Spacing.xl },
|
||||||
|
requirementsTitle: { fontSize: 18, fontWeight: '600', color: Colors.textDark, marginBottom: Spacing.md },
|
||||||
|
requirementItem: { flexDirection: 'row', alignItems: 'center', marginTop: Spacing.sm, gap: Spacing.sm },
|
||||||
|
requirementText: { fontSize: 16, color: Colors.textDark },
|
||||||
|
completeButton: { flexDirection: 'row', backgroundColor: Colors.teal, borderRadius: BorderRadius.xxlarge, paddingVertical: Spacing.lg, paddingHorizontal: Spacing.xxxl, alignItems: 'center', justifyContent: 'center', marginTop: Spacing.xl, gap: Spacing.sm, ...Shadow.soft },
|
||||||
|
completeButtonText: { fontSize: 18, fontWeight: '600', color: '#FFFFFF' },
|
||||||
|
citizenBadge: { flexDirection: 'row', alignItems: 'center', backgroundColor: Colors.mint + '20', paddingVertical: Spacing.sm, paddingHorizontal: Spacing.md, borderRadius: BorderRadius.large, marginTop: Spacing.sm, gap: Spacing.xs, alignSelf: 'flex-start' },
|
||||||
|
citizenBadgeText: { fontSize: 14, fontWeight: '600', color: Colors.mint },
|
||||||
|
sectionTitle: { fontSize: 20, fontWeight: '600', color: Colors.textDark, marginBottom: Spacing.md },
|
||||||
|
comingSoon: { fontSize: 16, color: Colors.textGray, textAlign: 'center', marginTop: Spacing.xl },
|
||||||
|
});
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
|
Alert,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
|
||||||
|
interface Ministry {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
nameKu: string;
|
||||||
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
|
color: string;
|
||||||
|
description: string;
|
||||||
|
minister?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MinistriesScreen({ navigation }: any) {
|
||||||
|
const ministries: Ministry[] = [
|
||||||
|
{
|
||||||
|
id: 'finance',
|
||||||
|
name: 'Finance',
|
||||||
|
nameKu: 'Maliye',
|
||||||
|
icon: 'cash',
|
||||||
|
color: '#F39C12',
|
||||||
|
description: 'Treasury, Budget & Economic Policy',
|
||||||
|
minister: 'Dr. Heval Azad',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'justice',
|
||||||
|
name: 'Justice',
|
||||||
|
nameKu: 'Adalet',
|
||||||
|
icon: 'scale',
|
||||||
|
color: '#34495E',
|
||||||
|
description: 'Legal System & Courts',
|
||||||
|
minister: 'Av. Leyla Şoreş',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'health',
|
||||||
|
name: 'Health',
|
||||||
|
nameKu: 'Tenduristî',
|
||||||
|
icon: 'medical',
|
||||||
|
color: '#E91E63',
|
||||||
|
description: 'Healthcare Services & Public Health',
|
||||||
|
minister: 'Dr. Roj Berxwedan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'education',
|
||||||
|
name: 'Education',
|
||||||
|
nameKu: 'Perwerde',
|
||||||
|
icon: 'school',
|
||||||
|
color: '#5C6BC0',
|
||||||
|
description: 'Education System & Universities',
|
||||||
|
minister: 'Prof. Aram Zana',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'commerce',
|
||||||
|
name: 'Commerce',
|
||||||
|
nameKu: 'Bazirganî',
|
||||||
|
icon: 'cart',
|
||||||
|
color: '#26A69A',
|
||||||
|
description: 'Trade, Business & Industry',
|
||||||
|
minister: 'Berivan Qazi',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'media',
|
||||||
|
name: 'Media & Communications',
|
||||||
|
nameKu: 'Medya û Ragihandin',
|
||||||
|
icon: 'newspaper',
|
||||||
|
color: '#FF9800',
|
||||||
|
description: 'Press, Broadcasting & Information',
|
||||||
|
minister: 'Jiyan Newroz',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'culture',
|
||||||
|
name: 'Culture & Arts',
|
||||||
|
nameKu: 'Çand û Huner',
|
||||||
|
icon: 'color-palette',
|
||||||
|
color: '#EC407A',
|
||||||
|
description: 'Cultural Heritage & Arts',
|
||||||
|
minister: 'Şêrko Dilan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tourism',
|
||||||
|
name: 'Tourism',
|
||||||
|
nameKu: 'Gerîyan',
|
||||||
|
icon: 'airplane',
|
||||||
|
color: '#00ACC1',
|
||||||
|
description: 'Tourism Development & Promotion',
|
||||||
|
minister: 'Azad Çiya',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'foreign',
|
||||||
|
name: 'Foreign Affairs',
|
||||||
|
nameKu: 'Karûbarên Derve',
|
||||||
|
icon: 'globe',
|
||||||
|
color: '#7E57C2',
|
||||||
|
description: 'International Relations & Diplomacy',
|
||||||
|
minister: 'Dilşa Rojava',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'interior',
|
||||||
|
name: 'Interior',
|
||||||
|
nameKu: 'Karûbarên Navxwe',
|
||||||
|
icon: 'shield',
|
||||||
|
color: '#8D6E63',
|
||||||
|
description: 'Internal Security & Public Order',
|
||||||
|
minister: 'Baran Zagros',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'defense',
|
||||||
|
name: 'Defense',
|
||||||
|
nameKu: 'Bergiranî',
|
||||||
|
icon: 'shield-checkmark',
|
||||||
|
color: '#455A64',
|
||||||
|
description: 'National Defense & Security',
|
||||||
|
minister: 'Gen. Kendal Şoreş',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'agriculture',
|
||||||
|
name: 'Agriculture',
|
||||||
|
nameKu: 'Çandinî',
|
||||||
|
icon: 'leaf',
|
||||||
|
color: '#8BC34A',
|
||||||
|
description: 'Farming, Food Security & Rural Development',
|
||||||
|
minister: 'Hêvî Berxwedan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'environment',
|
||||||
|
name: 'Environment',
|
||||||
|
nameKu: 'Jîngeha',
|
||||||
|
icon: 'earth',
|
||||||
|
color: '#66BB6A',
|
||||||
|
description: 'Environmental Protection & Climate',
|
||||||
|
minister: 'Çiya Newroz',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'energy',
|
||||||
|
name: 'Energy',
|
||||||
|
nameKu: 'Wize',
|
||||||
|
icon: 'flash',
|
||||||
|
color: '#FFA726',
|
||||||
|
description: 'Energy Resources & Infrastructure',
|
||||||
|
minister: 'Roj Azad',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'transport',
|
||||||
|
name: 'Transport',
|
||||||
|
nameKu: 'Veguhestin',
|
||||||
|
icon: 'car',
|
||||||
|
color: '#42A5F5',
|
||||||
|
description: 'Transportation & Infrastructure',
|
||||||
|
minister: 'Beritan Qazi',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'labor',
|
||||||
|
name: 'Labor & Social Affairs',
|
||||||
|
nameKu: 'Kar û Karûbarên Civakî',
|
||||||
|
icon: 'people',
|
||||||
|
color: '#AB47BC',
|
||||||
|
description: 'Employment & Social Welfare',
|
||||||
|
minister: 'Heval Dilan',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleMinistryPress = (ministry: Ministry) => {
|
||||||
|
Alert.alert(
|
||||||
|
ministry.name,
|
||||||
|
`${ministry.nameKu}\n\nMinister: ${ministry.minister}\n\n${ministry.description}\n\nThis feature is under development.`,
|
||||||
|
[{ text: 'OK' }]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.backButton}>
|
||||||
|
<Ionicons name="arrow-back" size={24} color={Colors.textDark} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={styles.headerContent}>
|
||||||
|
<Text style={styles.headerTitle}>Ministries</Text>
|
||||||
|
<Text style={styles.headerSubtitle}>Wezaretî</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Ministries Grid */}
|
||||||
|
<View style={styles.grid}>
|
||||||
|
{ministries.map((ministry) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={ministry.id}
|
||||||
|
style={styles.ministryCard}
|
||||||
|
onPress={() => handleMinistryPress(ministry)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<View style={[styles.iconContainer, { backgroundColor: ministry.color }]}>
|
||||||
|
<Ionicons name={ministry.icon} size={32} color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.ministryName}>{ministry.name}</Text>
|
||||||
|
<Text style={styles.ministryNameKu}>{ministry.nameKu}</Text>
|
||||||
|
{ministry.minister && (
|
||||||
|
<Text style={styles.ministerName}>{ministry.minister}</Text>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Info Footer */}
|
||||||
|
<View style={styles.infoFooter}>
|
||||||
|
<Ionicons name="information-circle" size={20} color={Colors.primary} />
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
Cabinet of {ministries.length} ministries serving the people of Kurdistan.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
paddingHorizontal: Spacing.lg,
|
||||||
|
paddingVertical: Spacing.md,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#E0E0E0',
|
||||||
|
...Shadow.small,
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
marginRight: Spacing.md,
|
||||||
|
},
|
||||||
|
headerContent: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: Typography.sizes.xxl,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
headerSubtitle: {
|
||||||
|
fontSize: Typography.sizes.md,
|
||||||
|
color: Colors.textLight,
|
||||||
|
marginTop: Spacing.xs,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
padding: Spacing.md,
|
||||||
|
},
|
||||||
|
ministryCard: {
|
||||||
|
width: '31%',
|
||||||
|
marginHorizontal: '1%',
|
||||||
|
marginBottom: Spacing.lg,
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: BorderRadius.lg,
|
||||||
|
padding: Spacing.md,
|
||||||
|
alignItems: 'center',
|
||||||
|
...Shadow.small,
|
||||||
|
},
|
||||||
|
iconContainer: {
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
borderRadius: BorderRadius.lg,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
ministryName: {
|
||||||
|
fontSize: Typography.sizes.xs,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: Spacing.xs,
|
||||||
|
},
|
||||||
|
ministryNameKu: {
|
||||||
|
fontSize: Typography.sizes.xs,
|
||||||
|
color: Colors.textLight,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: Spacing.xs,
|
||||||
|
},
|
||||||
|
ministerName: {
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.primary,
|
||||||
|
textAlign: 'center',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
},
|
||||||
|
infoFooter: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#E3F2FD',
|
||||||
|
margin: Spacing.lg,
|
||||||
|
padding: Spacing.md,
|
||||||
|
borderRadius: BorderRadius.md,
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: Typography.sizes.sm,
|
||||||
|
color: Colors.primary,
|
||||||
|
marginLeft: Spacing.sm,
|
||||||
|
lineHeight: 18,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
ScrollView,
|
||||||
|
SafeAreaView,
|
||||||
|
Image,
|
||||||
|
} from 'react-native';
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow, IconSizes } from '../../constants/theme';
|
||||||
|
import { blockchainService } from '../../services/blockchain';
|
||||||
|
import { Balance } from '../../types';
|
||||||
|
|
||||||
|
interface QuickAction {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
|
color: string;
|
||||||
|
onPress: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HomeScreen({ navigation }: any) {
|
||||||
|
const [balance, setBalance] = useState<Balance | null>(null);
|
||||||
|
const [trustScore] = useState(750);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadBalance();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadBalance = async () => {
|
||||||
|
try {
|
||||||
|
// Try to connect to blockchain
|
||||||
|
const connected = await blockchainService.connect();
|
||||||
|
if (connected) {
|
||||||
|
const bal = await blockchainService.getBalances(
|
||||||
|
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'
|
||||||
|
);
|
||||||
|
setBalance(bal);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Using mock data');
|
||||||
|
// Use mock data
|
||||||
|
setBalance({
|
||||||
|
hez: '45,750.5',
|
||||||
|
pez: '1,234,567',
|
||||||
|
hezStaked: '30,000',
|
||||||
|
hezUsd: '45,234',
|
||||||
|
pezUsd: '123,456',
|
||||||
|
governancePower: '2.5',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const quickActions: QuickAction[] = [
|
||||||
|
{
|
||||||
|
id: 'send',
|
||||||
|
label: 'Send',
|
||||||
|
icon: 'arrow-forward',
|
||||||
|
color: Colors.coral,
|
||||||
|
onPress: () => navigation.navigate('Send', { token: 'HEZ' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'receive',
|
||||||
|
label: 'Receive',
|
||||||
|
icon: 'arrow-down',
|
||||||
|
color: Colors.blue,
|
||||||
|
onPress: () => navigation.navigate('Receive', { token: 'HEZ' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'vote',
|
||||||
|
label: 'Vote',
|
||||||
|
icon: 'checkmark-circle',
|
||||||
|
color: Colors.mint,
|
||||||
|
onPress: () => navigation.navigate('Governance'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'proposals',
|
||||||
|
label: 'Proposals',
|
||||||
|
icon: 'bulb',
|
||||||
|
color: Colors.peach,
|
||||||
|
onPress: () => navigation.navigate('Governance'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'identity',
|
||||||
|
label: 'Identity',
|
||||||
|
icon: 'person',
|
||||||
|
color: Colors.teal,
|
||||||
|
onPress: () => navigation.navigate('Identity'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'certificates',
|
||||||
|
label: 'Certificates',
|
||||||
|
icon: 'school',
|
||||||
|
color: Colors.gold,
|
||||||
|
onPress: () => navigation.navigate('Education'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'exchange',
|
||||||
|
label: 'Exchange',
|
||||||
|
icon: 'swap-horizontal',
|
||||||
|
color: Colors.cyan,
|
||||||
|
onPress: () => navigation.navigate('Exchange'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rewards',
|
||||||
|
label: 'Rewards',
|
||||||
|
icon: 'star',
|
||||||
|
color: Colors.lavender,
|
||||||
|
onPress: () => navigation.navigate('Wallet'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trust',
|
||||||
|
label: 'Trust',
|
||||||
|
icon: 'heart',
|
||||||
|
color: Colors.emerald,
|
||||||
|
onPress: () => navigation.navigate('Profile'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Header */}
|
||||||
|
<LinearGradient
|
||||||
|
colors={Colors.gradients.header}
|
||||||
|
style={styles.header}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<View style={styles.headerTop}>
|
||||||
|
{/* Profile Avatar */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.profileSection}
|
||||||
|
onPress={() => navigation.navigate('Profile')}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<View style={styles.avatar}>
|
||||||
|
<Ionicons name="person" size={24} color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Trust Score Badge */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.trustBadge}
|
||||||
|
onPress={() => navigation.navigate('TrustScore')}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Ionicons name="star" size={12} color="#FFFFFF" />
|
||||||
|
<Text style={styles.trustScore}>{trustScore}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Action Icons */}
|
||||||
|
<View style={styles.headerActions}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.headerIcon}
|
||||||
|
onPress={() => navigation.navigate('QRScanner')}
|
||||||
|
>
|
||||||
|
<Ionicons name="qr-code-outline" size={24} color="#FFFFFF" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.headerIcon}
|
||||||
|
onPress={() => navigation.navigate('Notifications')}
|
||||||
|
>
|
||||||
|
<Ionicons name="notifications-outline" size={24} color="#FFFFFF" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.headerIcon}
|
||||||
|
onPress={() => navigation.navigate('Profile')}
|
||||||
|
>
|
||||||
|
<Ionicons name="settings-outline" size={24} color="#FFFFFF" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
{/* Balance Card */}
|
||||||
|
<View style={styles.balanceCardContainer}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.balanceCard}
|
||||||
|
onPress={() => navigation.navigate('Wallet')}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
>
|
||||||
|
<View style={styles.balanceRow}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.balanceItem}
|
||||||
|
onPress={() => navigation.navigate('Wallet', { tab: 'HEZ' })}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text style={styles.balanceLabel}>HEZ Balance</Text>
|
||||||
|
<Text style={styles.balanceAmount}>{balance?.hez || '0'}</Text>
|
||||||
|
<View style={styles.underline} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.balanceItem}
|
||||||
|
onPress={() => navigation.navigate('Wallet', { tab: 'PEZ' })}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text style={styles.balanceLabel}>PEZ Balance</Text>
|
||||||
|
<Text style={styles.balanceAmountSecondary}>{balance?.pez || '0'}</Text>
|
||||||
|
<View style={[styles.underline, { backgroundColor: Colors.peach }]} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Quick Actions */}
|
||||||
|
<View style={styles.quickActionsSection}>
|
||||||
|
<Text style={styles.sectionTitle}>Quick Actions</Text>
|
||||||
|
|
||||||
|
<View style={styles.quickActionsGrid}>
|
||||||
|
{quickActions.map((action) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={action.id}
|
||||||
|
style={styles.quickActionButton}
|
||||||
|
onPress={action.onPress}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<View style={[styles.quickActionIcon, { backgroundColor: action.color }]}>
|
||||||
|
<Ionicons name={action.icon} size={24} color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.quickActionLabel}>{action.label}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
paddingTop: Spacing.xl,
|
||||||
|
paddingBottom: Spacing.xxxl * 2,
|
||||||
|
borderBottomLeftRadius: BorderRadius.xxlarge,
|
||||||
|
borderBottomRightRadius: BorderRadius.xxlarge,
|
||||||
|
},
|
||||||
|
headerTop: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
profileSection: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: Spacing.sm,
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 25,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.3)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
trustBadge: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: Colors.gold,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: Spacing.xs,
|
||||||
|
borderRadius: BorderRadius.round,
|
||||||
|
gap: Spacing.xs,
|
||||||
|
},
|
||||||
|
trustScore: {
|
||||||
|
fontSize: Typography.sizes.body,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
headerActions: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: Spacing.md,
|
||||||
|
},
|
||||||
|
headerIcon: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
balanceCardContainer: {
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
marginTop: -Spacing.xxxl,
|
||||||
|
},
|
||||||
|
balanceCard: {
|
||||||
|
backgroundColor: '#F5F3FF',
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
padding: Spacing.xl,
|
||||||
|
...Shadow.soft,
|
||||||
|
},
|
||||||
|
balanceRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
balanceItem: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
balanceLabel: {
|
||||||
|
fontSize: Typography.sizes.body,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginBottom: Spacing.xs,
|
||||||
|
},
|
||||||
|
balanceAmount: {
|
||||||
|
fontSize: Typography.sizes.hero,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: Spacing.xs,
|
||||||
|
},
|
||||||
|
balanceAmountSecondary: {
|
||||||
|
fontSize: Typography.sizes.heading,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: Spacing.xs,
|
||||||
|
},
|
||||||
|
underline: {
|
||||||
|
width: 80,
|
||||||
|
height: 3,
|
||||||
|
backgroundColor: Colors.teal,
|
||||||
|
borderRadius: 2,
|
||||||
|
},
|
||||||
|
quickActionsSection: {
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: Spacing.lg,
|
||||||
|
},
|
||||||
|
quickActionsGrid: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: Spacing.md,
|
||||||
|
},
|
||||||
|
quickActionButton: {
|
||||||
|
width: '31%',
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
padding: Spacing.lg,
|
||||||
|
alignItems: 'center',
|
||||||
|
...Shadow.soft,
|
||||||
|
},
|
||||||
|
quickActionIcon: {
|
||||||
|
width: IconSizes.xxlarge,
|
||||||
|
height: IconSizes.xxlarge,
|
||||||
|
borderRadius: IconSizes.xxlarge / 2,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
quickActionLabel: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: Colors.textGray,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
|
ActivityIndicator,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
|
||||||
|
interface Notification {
|
||||||
|
id: string;
|
||||||
|
type: 'transaction' | 'governance' | 'reward' | 'system';
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
timestamp: number;
|
||||||
|
read: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NotificationsScreen({ navigation }: any) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadNotifications();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadNotifications = async () => {
|
||||||
|
// Mock data - replace with actual API call
|
||||||
|
setTimeout(() => {
|
||||||
|
setNotifications([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'transaction',
|
||||||
|
title: 'Transaction Confirmed',
|
||||||
|
message: 'You received 1,000 PEZ from 5Grw...KutQY',
|
||||||
|
timestamp: Date.now() - 3600000,
|
||||||
|
read: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'governance',
|
||||||
|
title: 'New Proposal',
|
||||||
|
message: 'Proposal #47: Increase PEZ rewards by 10%',
|
||||||
|
timestamp: Date.now() - 7200000,
|
||||||
|
read: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: 'reward',
|
||||||
|
title: 'Staking Rewards',
|
||||||
|
message: 'You earned 245 PEZ from staking',
|
||||||
|
timestamp: Date.now() - 86400000,
|
||||||
|
read: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
type: 'system',
|
||||||
|
title: 'KYC Approved',
|
||||||
|
message: 'Your Kurdistan Citizen Card is ready',
|
||||||
|
timestamp: Date.now() - 172800000,
|
||||||
|
read: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
setLoading(false);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNotificationIcon = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'transaction':
|
||||||
|
return 'swap-horizontal';
|
||||||
|
case 'governance':
|
||||||
|
return 'megaphone';
|
||||||
|
case 'reward':
|
||||||
|
return 'star';
|
||||||
|
case 'system':
|
||||||
|
return 'information-circle';
|
||||||
|
default:
|
||||||
|
return 'notifications';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNotificationColor = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'transaction':
|
||||||
|
return Colors.blue;
|
||||||
|
case 'governance':
|
||||||
|
return Colors.peach;
|
||||||
|
case 'reward':
|
||||||
|
return Colors.gold;
|
||||||
|
case 'system':
|
||||||
|
return Colors.teal;
|
||||||
|
default:
|
||||||
|
return Colors.textGray;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (timestamp: number) => {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const now = new Date();
|
||||||
|
const diff = now.getTime() - date.getTime();
|
||||||
|
const hours = Math.floor(diff / (1000 * 60 * 60));
|
||||||
|
const days = Math.floor(hours / 24);
|
||||||
|
|
||||||
|
if (hours < 1) return 'Just now';
|
||||||
|
if (hours < 24) return `${hours}h ago`;
|
||||||
|
if (days === 1) return 'Yesterday';
|
||||||
|
return `${days}d ago`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const markAsRead = (id: string) => {
|
||||||
|
setNotifications((prev) =>
|
||||||
|
prev.map((notif) => (notif.id === id ? { ...notif, read: true } : notif))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const unreadCount = notifications.filter((n) => !n.read).length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.backButton}>
|
||||||
|
<Ionicons name="arrow-back" size={24} color={Colors.textDark} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.headerTitle}>Notifications</Text>
|
||||||
|
{unreadCount > 0 && (
|
||||||
|
<View style={styles.badge}>
|
||||||
|
<Text style={styles.badgeText}>{unreadCount}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
||||||
|
{loading ? (
|
||||||
|
<ActivityIndicator size="large" color={Colors.teal} style={{ marginTop: 50 }} />
|
||||||
|
) : notifications.length === 0 ? (
|
||||||
|
<View style={styles.emptyState}>
|
||||||
|
<Ionicons name="notifications-off-outline" size={80} color={Colors.textGray} />
|
||||||
|
<Text style={styles.emptyText}>No notifications yet</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
notifications.map((notification) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={notification.id}
|
||||||
|
style={[
|
||||||
|
styles.notificationCard,
|
||||||
|
!notification.read && styles.unreadCard,
|
||||||
|
]}
|
||||||
|
onPress={() => markAsRead(notification.id)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.iconContainer,
|
||||||
|
{ backgroundColor: getNotificationColor(notification.type) + '20' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name={getNotificationIcon(notification.type)}
|
||||||
|
size={24}
|
||||||
|
color={getNotificationColor(notification.type)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.notificationContent}>
|
||||||
|
<View style={styles.notificationHeader}>
|
||||||
|
<Text style={styles.notificationTitle}>{notification.title}</Text>
|
||||||
|
{!notification.read && <View style={styles.unreadDot} />}
|
||||||
|
</View>
|
||||||
|
<Text style={styles.notificationMessage}>{notification.message}</Text>
|
||||||
|
<Text style={styles.notificationTime}>{formatTime(notification.timestamp)}</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
paddingVertical: Spacing.lg,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#F0F0F0',
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: Spacing.md,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
badge: {
|
||||||
|
backgroundColor: Colors.coral,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 12,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
badgeText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
padding: Spacing.lg,
|
||||||
|
},
|
||||||
|
notificationCard: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
padding: Spacing.lg,
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
...Shadow.small,
|
||||||
|
},
|
||||||
|
unreadCard: {
|
||||||
|
borderLeftWidth: 4,
|
||||||
|
borderLeftColor: Colors.teal,
|
||||||
|
},
|
||||||
|
iconContainer: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 25,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: Spacing.md,
|
||||||
|
},
|
||||||
|
notificationContent: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
notificationHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
notificationTitle: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
unreadDot: {
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: Colors.teal,
|
||||||
|
},
|
||||||
|
notificationMessage: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
notificationTime: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: Colors.textGray,
|
||||||
|
opacity: 0.7,
|
||||||
|
},
|
||||||
|
emptyState: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: 100,
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,459 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
SafeAreaView,
|
||||||
|
Image,
|
||||||
|
ActivityIndicator,
|
||||||
|
ScrollView,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import QRCode from 'react-native-qrcode-svg';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
import { kycService } from '../../services/kycService';
|
||||||
|
import { KurdistanCitizen, REGION_LABELS } from '../../types/kyc';
|
||||||
|
|
||||||
|
export default function CitizenCardScreen({ navigation }: any) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [citizen, setCitizen] = useState<KurdistanCitizen | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadCitizen();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadCitizen = async () => {
|
||||||
|
try {
|
||||||
|
const data = await kycService.getCitizen();
|
||||||
|
setCitizen(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load citizen data:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<ActivityIndicator size="large" color={Colors.teal} />
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!citizen) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<Text style={styles.errorText}>No citizen data found</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.button}
|
||||||
|
onPress={() => navigation.navigate('IdentityKYCForm')}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>Complete KYC</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (timestamp: number) => {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
return date.toLocaleDateString('en-GB');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.backButton}>
|
||||||
|
<Ionicons name="arrow-back" size={24} color={Colors.textDark} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.headerTitle}>Digital Hemwelatî</Text>
|
||||||
|
<TouchableOpacity style={styles.shareButton}>
|
||||||
|
<Ionicons name="share-outline" size={24} color={Colors.textDark} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Official Citizen Card */}
|
||||||
|
<View style={styles.card}>
|
||||||
|
{/* Burgundy Header */}
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
|
<Text style={styles.headerText1}>KOMARA KURDISTANÊ</Text>
|
||||||
|
<Text style={styles.headerText2}>HEMWELATÎ</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Main Content - White Background */}
|
||||||
|
<View style={styles.cardBody}>
|
||||||
|
{/* Photo and Info Section */}
|
||||||
|
<View style={styles.topSection}>
|
||||||
|
{/* Photo */}
|
||||||
|
<View style={styles.photoContainer}>
|
||||||
|
{citizen.photo ? (
|
||||||
|
<Image source={{ uri: citizen.photo }} style={styles.photo} />
|
||||||
|
) : (
|
||||||
|
<View style={styles.photoPlaceholder}>
|
||||||
|
<Ionicons name="person" size={50} color="#999" />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Info List */}
|
||||||
|
<View style={styles.infoList}>
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<Text style={styles.infoLabel}>NAV/NAME</Text>
|
||||||
|
<Text style={styles.infoValue}>{citizen.fullName}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{citizen.fatherName && (
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<Text style={styles.infoLabel}>BAV/FATHER</Text>
|
||||||
|
<Text style={styles.infoValue}>{citizen.fatherName}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{citizen.grandfatherName && (
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<Text style={styles.infoLabel}>DAPÎR/GRANDFATHER</Text>
|
||||||
|
<Text style={styles.infoValue}>{citizen.grandfatherName}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{citizen.greatGrandfatherName && (
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<Text style={styles.infoLabel}>DAPÎRA BIRA/G.GRANDFATHER</Text>
|
||||||
|
<Text style={styles.infoValue}>{citizen.greatGrandfatherName}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{citizen.motherName && (
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<Text style={styles.infoLabel}>DAY/MOTHER</Text>
|
||||||
|
<Text style={styles.infoValue}>{citizen.motherName}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<Text style={styles.infoLabel}>ZEWICÎN/MARITAL</Text>
|
||||||
|
<Text style={styles.infoValue}>
|
||||||
|
{citizen.maritalStatus === 'married' ? 'Zewicî' : 'Nezewicî'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{citizen.maritalStatus === 'married' && citizen.spouseName && (
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<Text style={styles.infoLabel}>HEVJÎN/SPOUSE</Text>
|
||||||
|
<Text style={styles.infoValue}>{citizen.spouseName}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{citizen.children && citizen.children.length > 0 && (
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<Text style={styles.infoLabel}>ZAROK/CHILDREN</Text>
|
||||||
|
<Text style={styles.infoValue}>{citizen.children.length}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* PEZ Sun Logo */}
|
||||||
|
<View style={styles.sunContainer}>
|
||||||
|
<Ionicons name="sunny" size={80} color="#D4A017" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Region and Citizen ID */}
|
||||||
|
<View style={styles.middleSection}>
|
||||||
|
<View style={styles.regionRow}>
|
||||||
|
<Text style={styles.regionLabel}>HEREM/REGION</Text>
|
||||||
|
<Text style={styles.regionValue}>{REGION_LABELS[citizen.region].en}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.citizenIdRow}>
|
||||||
|
<Text style={styles.citizenIdLabel}>JIMARA HEMWELATÎ/CITIZEN ID</Text>
|
||||||
|
<Text style={styles.citizenIdValue}>{citizen.citizenId}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Green Footer */}
|
||||||
|
<View style={styles.cardFooter}>
|
||||||
|
<View style={styles.footerLeft}>
|
||||||
|
<View style={styles.footerRow}>
|
||||||
|
<Text style={styles.footerLabel}>NASNAMA DIJÎTAL/DIGITAL ID</Text>
|
||||||
|
<Text style={styles.footerValue}>{citizen.citizenId.replace(/-/g, '')}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.footerRow}>
|
||||||
|
<Text style={styles.footerLabel}>JIMARA VEKIRAN/ACCOUNT#</Text>
|
||||||
|
<Text style={styles.footerValue}>{citizen.qrCode.substring(0, 12)}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.footerRow}>
|
||||||
|
<Text style={styles.footerLabel}>DAXWAZ/ISSUED</Text>
|
||||||
|
<Text style={styles.footerValue}>{formatDate(citizen.approvedAt)}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.footerRight}>
|
||||||
|
<QRCode value={citizen.qrCode} size={70} backgroundColor="transparent" color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Status Badge */}
|
||||||
|
<View style={styles.statusBadge}>
|
||||||
|
<Ionicons name="checkmark-circle" size={24} color={Colors.mint} />
|
||||||
|
<Text style={styles.statusText}>Verified Kurdistan Citizen</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Info Text */}
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
This official digital citizenship card grants you full access to Governance (Welati) and all citizen-only features on PezkuwiChain.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<View style={styles.actions}>
|
||||||
|
<TouchableOpacity style={styles.actionButton}>
|
||||||
|
<Ionicons name="download-outline" size={24} color={Colors.teal} />
|
||||||
|
<Text style={styles.actionText}>Download</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.actionButton}>
|
||||||
|
<Ionicons name="qr-code-outline" size={24} color={Colors.teal} />
|
||||||
|
<Text style={styles.actionText}>Show QR</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.actionButton}>
|
||||||
|
<Ionicons name="share-social-outline" size={24} color={Colors.teal} />
|
||||||
|
<Text style={styles.actionText}>Share</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
paddingVertical: Spacing.lg,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#F0F0F0',
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
shareButton: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
padding: Spacing.xl,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: BorderRadius.xlarge,
|
||||||
|
overflow: 'hidden',
|
||||||
|
...Shadow.medium,
|
||||||
|
},
|
||||||
|
cardHeader: {
|
||||||
|
backgroundColor: '#8B1538', // Burgundy
|
||||||
|
paddingVertical: Spacing.xl,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
headerText1: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#D4A017', // Gold
|
||||||
|
letterSpacing: 1,
|
||||||
|
},
|
||||||
|
headerText2: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#FFFFFF',
|
||||||
|
letterSpacing: 2,
|
||||||
|
marginTop: 4,
|
||||||
|
},
|
||||||
|
cardBody: {
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
padding: Spacing.lg,
|
||||||
|
},
|
||||||
|
topSection: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
},
|
||||||
|
photoContainer: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
borderRadius: 50,
|
||||||
|
overflow: 'hidden',
|
||||||
|
backgroundColor: '#D0D0D0',
|
||||||
|
marginRight: Spacing.md,
|
||||||
|
},
|
||||||
|
photo: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
photoPlaceholder: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#D0D0D0',
|
||||||
|
},
|
||||||
|
infoList: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
infoRow: {
|
||||||
|
marginBottom: 6,
|
||||||
|
},
|
||||||
|
infoLabel: {
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#333',
|
||||||
|
letterSpacing: 0.3,
|
||||||
|
},
|
||||||
|
infoValue: {
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#000',
|
||||||
|
},
|
||||||
|
sunContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
marginVertical: Spacing.md,
|
||||||
|
},
|
||||||
|
middleSection: {
|
||||||
|
marginTop: Spacing.sm,
|
||||||
|
},
|
||||||
|
regionRow: {
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
regionLabel: {
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#333',
|
||||||
|
},
|
||||||
|
regionValue: {
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#000',
|
||||||
|
},
|
||||||
|
citizenIdRow: {
|
||||||
|
marginTop: Spacing.sm,
|
||||||
|
},
|
||||||
|
citizenIdLabel: {
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#333',
|
||||||
|
},
|
||||||
|
citizenIdValue: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#000',
|
||||||
|
letterSpacing: 1,
|
||||||
|
},
|
||||||
|
cardFooter: {
|
||||||
|
backgroundColor: '#007A3D', // Green
|
||||||
|
flexDirection: 'row',
|
||||||
|
padding: Spacing.lg,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
footerLeft: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
footerRow: {
|
||||||
|
marginBottom: 6,
|
||||||
|
},
|
||||||
|
footerLabel: {
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#FFFFFF',
|
||||||
|
opacity: 0.9,
|
||||||
|
},
|
||||||
|
footerValue: {
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#D4A017', // Gold
|
||||||
|
},
|
||||||
|
footerRight: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
statusBadge: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: Colors.mint + '20',
|
||||||
|
padding: Spacing.md,
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
gap: Spacing.sm,
|
||||||
|
},
|
||||||
|
statusText: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.mint,
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: Colors.textGray,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
actionButton: {
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: Spacing.sm,
|
||||||
|
},
|
||||||
|
actionText: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: Colors.teal,
|
||||||
|
fontWeight: Typography.weights.medium,
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
color: Colors.textGray,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
backgroundColor: Colors.teal,
|
||||||
|
paddingVertical: Spacing.lg,
|
||||||
|
paddingHorizontal: Spacing.xxxl,
|
||||||
|
borderRadius: BorderRadius.xxlarge,
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,435 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TextInput,
|
||||||
|
TouchableOpacity,
|
||||||
|
ScrollView,
|
||||||
|
SafeAreaView,
|
||||||
|
Alert,
|
||||||
|
ActivityIndicator,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { Picker } from '@react-native-picker/picker';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
import { KYCFormData, Region, MaritalStatus, REGION_LABELS } from '../../types/kyc';
|
||||||
|
import { kycService } from '../../services/kycService';
|
||||||
|
|
||||||
|
export default function IdentityKYCFormScreen({ navigation }: any) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [formData, setFormData] = useState<KYCFormData>({
|
||||||
|
fullName: '',
|
||||||
|
fatherName: '',
|
||||||
|
grandfatherName: '',
|
||||||
|
greatGrandfatherName: '',
|
||||||
|
motherName: '',
|
||||||
|
maritalStatus: 'single',
|
||||||
|
region: 'basur',
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateField = (field: keyof KYCFormData, value: any) => {
|
||||||
|
setFormData({ ...formData, [field]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChild = (index: number, name: string) => {
|
||||||
|
const children = formData.children || [];
|
||||||
|
children[index] = { name, order: index + 1 };
|
||||||
|
setFormData({ ...formData, children });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
// Validation
|
||||||
|
if (!formData.fullName || !formData.fatherName || !formData.motherName) {
|
||||||
|
Alert.alert('Error', 'Please fill in all required fields');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.maritalStatus === 'married' && !formData.spouseName) {
|
||||||
|
Alert.alert('Error', 'Please enter your spouse name');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// Submit KYC
|
||||||
|
const submission = await kycService.submitKYC(formData, null); // TODO: Add signer
|
||||||
|
|
||||||
|
Alert.alert(
|
||||||
|
'KYC Submitted',
|
||||||
|
'Your KYC application has been submitted for review. You will be notified once approved.',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'OK',
|
||||||
|
onPress: () => navigation.goBack(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('Error', 'Failed to submit KYC. Please try again.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderChildInputs = () => {
|
||||||
|
if (!formData.numberOfChildren || formData.numberOfChildren === 0) return null;
|
||||||
|
|
||||||
|
return Array.from({ length: formData.numberOfChildren }, (_, index) => (
|
||||||
|
<View key={index} style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>{index + 1}. Child's Name</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="person-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder={`${index + 1}. child's name`}
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
value={formData.children?.[index]?.name || ''}
|
||||||
|
onChangeText={(text) => updateChild(index, text)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.backButton}>
|
||||||
|
<Ionicons name="arrow-back" size={24} color={Colors.textDark} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.headerTitle}>Identity KYC</Text>
|
||||||
|
<View style={{ width: 24 }} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
|
||||||
|
<View style={styles.infoCard}>
|
||||||
|
<Ionicons name="information-circle" size={24} color={Colors.teal} />
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
Complete this form to become a verified Kurdistan Digital Citizen and access Governance features.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Personal Information */}
|
||||||
|
<Text style={styles.sectionTitle}>Personal Information</Text>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Full Name *</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="person-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Your full name"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
value={formData.fullName}
|
||||||
|
onChangeText={(text) => updateField('fullName', text)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Father's Name *</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="man-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Your father's name"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
value={formData.fatherName}
|
||||||
|
onChangeText={(text) => updateField('fatherName', text)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Grandfather's Name</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="man-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Your grandfather's name"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
value={formData.grandfatherName}
|
||||||
|
onChangeText={(text) => updateField('grandfatherName', text)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Great-Grandfather's Name</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="man-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Your great-grandfather's name"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
value={formData.greatGrandfatherName}
|
||||||
|
onChangeText={(text) => updateField('greatGrandfatherName', text)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Mother's Name *</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="woman-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Your mother's name"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
value={formData.motherName}
|
||||||
|
onChangeText={(text) => updateField('motherName', text)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Marital Status */}
|
||||||
|
<Text style={styles.sectionTitle}>Marital Status</Text>
|
||||||
|
|
||||||
|
<View style={styles.radioGroup}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.radioButton,
|
||||||
|
formData.maritalStatus === 'single' && styles.radioButtonActive,
|
||||||
|
]}
|
||||||
|
onPress={() => updateField('maritalStatus', 'single')}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name={formData.maritalStatus === 'single' ? 'radio-button-on' : 'radio-button-off'}
|
||||||
|
size={24}
|
||||||
|
color={formData.maritalStatus === 'single' ? Colors.teal : Colors.textGray}
|
||||||
|
/>
|
||||||
|
<Text style={styles.radioLabel}>Single</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.radioButton,
|
||||||
|
formData.maritalStatus === 'married' && styles.radioButtonActive,
|
||||||
|
]}
|
||||||
|
onPress={() => updateField('maritalStatus', 'married')}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name={formData.maritalStatus === 'married' ? 'radio-button-on' : 'radio-button-off'}
|
||||||
|
size={24}
|
||||||
|
color={formData.maritalStatus === 'married' ? Colors.teal : Colors.textGray}
|
||||||
|
/>
|
||||||
|
<Text style={styles.radioLabel}>Married</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{formData.maritalStatus === 'married' && (
|
||||||
|
<>
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Spouse's Name *</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="heart-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Your spouse's name"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
value={formData.spouseName}
|
||||||
|
onChangeText={(text) => updateField('spouseName', text)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Number of Children</Text>
|
||||||
|
<View style={styles.inputWrapper}>
|
||||||
|
<Ionicons name="people-outline" size={20} color={Colors.textGray} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="0"
|
||||||
|
placeholderTextColor={Colors.textLight}
|
||||||
|
keyboardType="number-pad"
|
||||||
|
value={formData.numberOfChildren?.toString() || ''}
|
||||||
|
onChangeText={(text) => {
|
||||||
|
const num = parseInt(text) || 0;
|
||||||
|
updateField('numberOfChildren', num);
|
||||||
|
if (num > 0) {
|
||||||
|
updateField('children', Array(num).fill({ name: '', order: 0 }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{renderChildInputs()}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Region */}
|
||||||
|
<Text style={styles.sectionTitle}>Region</Text>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Text style={styles.label}>Select Your Region *</Text>
|
||||||
|
<View style={styles.pickerWrapper}>
|
||||||
|
<Picker
|
||||||
|
selectedValue={formData.region}
|
||||||
|
onValueChange={(value) => updateField('region', value)}
|
||||||
|
style={styles.picker}
|
||||||
|
>
|
||||||
|
{Object.entries(REGION_LABELS).map(([key, value]) => (
|
||||||
|
<Picker.Item key={key} label={value.en} value={key} />
|
||||||
|
))}
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.submitButton, loading && styles.submitButtonDisabled]}
|
||||||
|
onPress={handleSubmit}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<ActivityIndicator color="#FFFFFF" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Text style={styles.submitButtonText}>Submit KYC Application</Text>
|
||||||
|
<Ionicons name="arrow-forward" size={20} color="#FFFFFF" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={{ height: 40 }} />
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
paddingVertical: Spacing.lg,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#F0F0F0',
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
scrollView: {
|
||||||
|
flex: 1,
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
},
|
||||||
|
infoCard: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: Colors.teal + '20',
|
||||||
|
padding: Spacing.lg,
|
||||||
|
borderRadius: BorderRadius.medium,
|
||||||
|
marginVertical: Spacing.lg,
|
||||||
|
gap: Spacing.md,
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: Colors.textDark,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
},
|
||||||
|
inputContainer: {
|
||||||
|
marginBottom: Spacing.lg,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: Typography.sizes.body,
|
||||||
|
fontWeight: Typography.weights.medium,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
inputWrapper: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
borderRadius: BorderRadius.medium,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: Spacing.sm,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#E0E0E0',
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginLeft: Spacing.sm,
|
||||||
|
},
|
||||||
|
radioGroup: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: Spacing.md,
|
||||||
|
marginBottom: Spacing.lg,
|
||||||
|
},
|
||||||
|
radioButton: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
padding: Spacing.lg,
|
||||||
|
borderRadius: BorderRadius.medium,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#E0E0E0',
|
||||||
|
gap: Spacing.sm,
|
||||||
|
},
|
||||||
|
radioButtonActive: {
|
||||||
|
borderColor: Colors.teal,
|
||||||
|
backgroundColor: Colors.teal + '10',
|
||||||
|
},
|
||||||
|
radioLabel: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
pickerWrapper: {
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
borderRadius: BorderRadius.medium,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#E0E0E0',
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
picker: {
|
||||||
|
height: 50,
|
||||||
|
},
|
||||||
|
submitButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: Colors.teal,
|
||||||
|
borderRadius: BorderRadius.xxlarge,
|
||||||
|
paddingVertical: Spacing.lg,
|
||||||
|
paddingHorizontal: Spacing.xxxl,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
gap: Spacing.sm,
|
||||||
|
...Shadow.soft,
|
||||||
|
},
|
||||||
|
submitButtonDisabled: {
|
||||||
|
opacity: 0.6,
|
||||||
|
},
|
||||||
|
submitButtonText: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { View, Text, StyleSheet, TouchableOpacity, SafeAreaView, ActivityIndicator } from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
import { kycService } from '../../services/kycService';
|
||||||
|
import { KYCStatus } from '../../types/kyc';
|
||||||
|
|
||||||
|
export default function IdentityScreen({ navigation }: any) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [kycStatus, setKycStatus] = useState<KYCStatus>({
|
||||||
|
started: false,
|
||||||
|
submitted: false,
|
||||||
|
approved: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadKYCStatus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadKYCStatus = async () => {
|
||||||
|
try {
|
||||||
|
const status = await kycService.getKYCStatus();
|
||||||
|
setKycStatus(status);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load KYC status:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<ActivityIndicator size="large" color={Colors.teal} />
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If KYC approved, show citizen card access
|
||||||
|
if (kycStatus.approved && kycStatus.citizen) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.title}>Digital Identity</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.approvedCard}>
|
||||||
|
<Ionicons name="checkmark-circle" size={60} color={Colors.mint} />
|
||||||
|
<Text style={styles.approvedTitle}>KYC Approved</Text>
|
||||||
|
<Text style={styles.approvedSubtitle}>You are a verified Kurdistan Digital Citizen</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.viewCardButton}
|
||||||
|
onPress={() => navigation.navigate('CitizenCard')}
|
||||||
|
>
|
||||||
|
<Ionicons name="card-outline" size={24} color="#FFFFFF" />
|
||||||
|
<Text style={styles.viewCardButtonText}>View Citizen Card</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={styles.infoBox}>
|
||||||
|
<Ionicons name="information-circle-outline" size={24} color={Colors.teal} />
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
Your citizen card grants you access to Governance (Welati) and other citizen-only features.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If KYC submitted but not approved
|
||||||
|
if (kycStatus.submitted && !kycStatus.approved) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.title}>Digital Identity</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.pendingCard}>
|
||||||
|
<Ionicons name="time-outline" size={60} color={Colors.gold} />
|
||||||
|
<Text style={styles.pendingTitle}>KYC Under Review</Text>
|
||||||
|
<Text style={styles.pendingSubtitle}>
|
||||||
|
Your application is being reviewed. You will be notified once approved.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.refreshButton} onPress={loadKYCStatus}>
|
||||||
|
<Ionicons name="refresh-outline" size={20} color={Colors.teal} />
|
||||||
|
<Text style={styles.refreshButtonText}>Check Status</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If KYC not started
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.title}>Digital Identity</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.welcomeCard}>
|
||||||
|
<Ionicons name="person-add-outline" size={60} color={Colors.teal} />
|
||||||
|
<Text style={styles.welcomeTitle}>Become a Digital Citizen</Text>
|
||||||
|
<Text style={styles.welcomeSubtitle}>
|
||||||
|
Complete KYC verification to access Governance (Welati) and become a verified Kurdistan Digital Citizen.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.benefitsCard}>
|
||||||
|
<Text style={styles.benefitsTitle}>Benefits:</Text>
|
||||||
|
<View style={styles.benefitItem}>
|
||||||
|
<Ionicons name="checkmark-circle" size={20} color={Colors.mint} />
|
||||||
|
<Text style={styles.benefitText}>Vote on governance proposals</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.benefitItem}>
|
||||||
|
<Ionicons name="checkmark-circle" size={20} color={Colors.mint} />
|
||||||
|
<Text style={styles.benefitText}>Access Parliamentary NFT elections</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.benefitItem}>
|
||||||
|
<Ionicons name="checkmark-circle" size={20} color={Colors.mint} />
|
||||||
|
<Text style={styles.benefitText}>Digital citizenship certificate</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.benefitItem}>
|
||||||
|
<Ionicons name="checkmark-circle" size={20} color={Colors.mint} />
|
||||||
|
<Text style={styles.benefitText}>Verified trust score</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.startButton}
|
||||||
|
onPress={() => navigation.navigate('IdentityKYCForm')}
|
||||||
|
>
|
||||||
|
<Text style={styles.startButtonText}>Start KYC Verification</Text>
|
||||||
|
<Ionicons name="arrow-forward" size={20} color="#FFFFFF" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
padding: Spacing.xl,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#F0F0F0',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
padding: Spacing.xl,
|
||||||
|
},
|
||||||
|
welcomeCard: {
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
padding: Spacing.xxxl,
|
||||||
|
borderRadius: BorderRadius.xlarge,
|
||||||
|
alignItems: 'center',
|
||||||
|
...Shadow.soft,
|
||||||
|
},
|
||||||
|
welcomeTitle: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
welcomeSubtitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginTop: Spacing.md,
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 24,
|
||||||
|
},
|
||||||
|
benefitsCard: {
|
||||||
|
backgroundColor: Colors.mint + '20',
|
||||||
|
padding: Spacing.xl,
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
},
|
||||||
|
benefitsTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
},
|
||||||
|
benefitItem: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: Spacing.sm,
|
||||||
|
gap: Spacing.sm,
|
||||||
|
},
|
||||||
|
benefitText: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
startButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: Colors.teal,
|
||||||
|
borderRadius: BorderRadius.xxlarge,
|
||||||
|
paddingVertical: Spacing.lg,
|
||||||
|
paddingHorizontal: Spacing.xxxl,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
gap: Spacing.sm,
|
||||||
|
...Shadow.soft,
|
||||||
|
},
|
||||||
|
startButtonText: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
approvedCard: {
|
||||||
|
backgroundColor: Colors.mint + '20',
|
||||||
|
padding: Spacing.xxxl,
|
||||||
|
borderRadius: BorderRadius.xlarge,
|
||||||
|
alignItems: 'center',
|
||||||
|
...Shadow.soft,
|
||||||
|
},
|
||||||
|
approvedTitle: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.mint,
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
},
|
||||||
|
approvedSubtitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginTop: Spacing.sm,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
viewCardButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: Colors.teal,
|
||||||
|
borderRadius: BorderRadius.xxlarge,
|
||||||
|
paddingVertical: Spacing.lg,
|
||||||
|
paddingHorizontal: Spacing.xxxl,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
gap: Spacing.sm,
|
||||||
|
...Shadow.soft,
|
||||||
|
},
|
||||||
|
viewCardButtonText: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
infoBox: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: Colors.teal + '20',
|
||||||
|
padding: Spacing.lg,
|
||||||
|
borderRadius: BorderRadius.medium,
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
gap: Spacing.md,
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.textDark,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
pendingCard: {
|
||||||
|
backgroundColor: Colors.gold + '20',
|
||||||
|
padding: Spacing.xxxl,
|
||||||
|
borderRadius: BorderRadius.xlarge,
|
||||||
|
alignItems: 'center',
|
||||||
|
...Shadow.soft,
|
||||||
|
},
|
||||||
|
pendingTitle: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.gold,
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
},
|
||||||
|
pendingSubtitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginTop: Spacing.sm,
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 24,
|
||||||
|
},
|
||||||
|
refreshButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
paddingVertical: Spacing.md,
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
gap: Spacing.sm,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: Colors.teal,
|
||||||
|
},
|
||||||
|
refreshButtonText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.teal,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,410 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
|
Switch,
|
||||||
|
Alert,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
id: string;
|
||||||
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
onPress: () => void;
|
||||||
|
showArrow?: boolean;
|
||||||
|
showSwitch?: boolean;
|
||||||
|
switchValue?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ProfileScreen({ navigation }: any) {
|
||||||
|
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
|
||||||
|
const [biometricEnabled, setBiometricEnabled] = useState(false);
|
||||||
|
|
||||||
|
const accountMenuItems: MenuItem[] = [
|
||||||
|
{
|
||||||
|
id: 'edit-profile',
|
||||||
|
icon: 'person-outline',
|
||||||
|
title: 'Edit Profile',
|
||||||
|
subtitle: 'Update your personal information',
|
||||||
|
onPress: () => Alert.alert('Edit Profile', 'Coming soon'),
|
||||||
|
showArrow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'identity',
|
||||||
|
icon: 'card-outline',
|
||||||
|
title: 'Digital Identity',
|
||||||
|
subtitle: 'View your Kurdistan Citizen Card',
|
||||||
|
onPress: () => navigation.navigate('Identity'),
|
||||||
|
showArrow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trust-score',
|
||||||
|
icon: 'star-outline',
|
||||||
|
title: 'Trust Score',
|
||||||
|
subtitle: '750 points',
|
||||||
|
onPress: () => navigation.navigate('TrustScore'),
|
||||||
|
showArrow: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const securityMenuItems: MenuItem[] = [
|
||||||
|
{
|
||||||
|
id: 'change-password',
|
||||||
|
icon: 'lock-closed-outline',
|
||||||
|
title: 'Change Password',
|
||||||
|
onPress: () => Alert.alert('Change Password', 'Coming soon'),
|
||||||
|
showArrow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'biometric',
|
||||||
|
icon: 'finger-print-outline',
|
||||||
|
title: 'Biometric Authentication',
|
||||||
|
onPress: () => {},
|
||||||
|
showSwitch: true,
|
||||||
|
switchValue: biometricEnabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'backup',
|
||||||
|
icon: 'cloud-upload-outline',
|
||||||
|
title: 'Backup Wallet',
|
||||||
|
subtitle: 'Secure your recovery phrase',
|
||||||
|
onPress: () => Alert.alert('Backup Wallet', 'This feature will help you backup your wallet securely.'),
|
||||||
|
showArrow: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const preferencesMenuItems: MenuItem[] = [
|
||||||
|
{
|
||||||
|
id: 'notifications',
|
||||||
|
icon: 'notifications-outline',
|
||||||
|
title: 'Push Notifications',
|
||||||
|
onPress: () => {},
|
||||||
|
showSwitch: true,
|
||||||
|
switchValue: notificationsEnabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'language',
|
||||||
|
icon: 'language-outline',
|
||||||
|
title: 'Language',
|
||||||
|
subtitle: 'English',
|
||||||
|
onPress: () => navigation.navigate('LanguageSelection'),
|
||||||
|
showArrow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'currency',
|
||||||
|
icon: 'cash-outline',
|
||||||
|
title: 'Currency',
|
||||||
|
subtitle: 'USD',
|
||||||
|
onPress: () => Alert.alert('Currency', 'Currency selection coming soon'),
|
||||||
|
showArrow: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const supportMenuItems: MenuItem[] = [
|
||||||
|
{
|
||||||
|
id: 'help',
|
||||||
|
icon: 'help-circle-outline',
|
||||||
|
title: 'Help Center',
|
||||||
|
onPress: () => Alert.alert('Help Center', 'Visit help.pezkuwichain.io'),
|
||||||
|
showArrow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'about',
|
||||||
|
icon: 'information-circle-outline',
|
||||||
|
title: 'About PezkuwiChain',
|
||||||
|
subtitle: 'Version 1.0.0',
|
||||||
|
onPress: () => Alert.alert('About', 'PezkuwiChain v1.0.0\nBuilding the future of Kurdish digital infrastructure'),
|
||||||
|
showArrow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'terms',
|
||||||
|
icon: 'document-text-outline',
|
||||||
|
title: 'Terms & Privacy',
|
||||||
|
onPress: () => Alert.alert('Terms', 'View terms and privacy policy'),
|
||||||
|
showArrow: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
Alert.alert(
|
||||||
|
'Logout',
|
||||||
|
'Are you sure you want to logout?',
|
||||||
|
[
|
||||||
|
{ text: 'Cancel', style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: 'Logout',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: () => navigation.navigate('LanguageSelection'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderMenuItem = (item: MenuItem) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={item.id}
|
||||||
|
style={styles.menuItem}
|
||||||
|
onPress={item.onPress}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<View style={styles.menuIconContainer}>
|
||||||
|
<Ionicons name={item.icon} size={24} color={Colors.teal} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.menuContent}>
|
||||||
|
<Text style={styles.menuTitle}>{item.title}</Text>
|
||||||
|
{item.subtitle && <Text style={styles.menuSubtitle}>{item.subtitle}</Text>}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{item.showArrow && (
|
||||||
|
<Ionicons name="chevron-forward" size={20} color={Colors.textGray} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.showSwitch && (
|
||||||
|
<Switch
|
||||||
|
value={item.switchValue}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
if (item.id === 'notifications') {
|
||||||
|
setNotificationsEnabled(value);
|
||||||
|
} else if (item.id === 'biometric') {
|
||||||
|
setBiometricEnabled(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
trackColor={{ false: '#D0D0D0', true: Colors.teal }}
|
||||||
|
thumbColor="#FFFFFF"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Profile Header */}
|
||||||
|
<LinearGradient
|
||||||
|
colors={Colors.gradients.header}
|
||||||
|
style={styles.header}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<View style={styles.profileInfo}>
|
||||||
|
<View style={styles.avatar}>
|
||||||
|
<Ionicons name="person" size={40} color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.userInfo}>
|
||||||
|
<Text style={styles.userName}>Satoshi Qazi Muhammed</Text>
|
||||||
|
<Text style={styles.userEmail}>satoshi@pezkuwichain.io</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.editButton}
|
||||||
|
onPress={() => Alert.alert('Edit Profile', 'Coming soon')}
|
||||||
|
>
|
||||||
|
<Ionicons name="create-outline" size={20} color="#FFFFFF" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<View style={styles.statsContainer}>
|
||||||
|
<View style={styles.statItem}>
|
||||||
|
<Text style={styles.statValue}>750</Text>
|
||||||
|
<Text style={styles.statLabel}>Trust Score</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.statDivider} />
|
||||||
|
|
||||||
|
<View style={styles.statItem}>
|
||||||
|
<Text style={styles.statValue}>24</Text>
|
||||||
|
<Text style={styles.statLabel}>Referrals</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.statDivider} />
|
||||||
|
|
||||||
|
<View style={styles.statItem}>
|
||||||
|
<Text style={styles.statValue}>5</Text>
|
||||||
|
<Text style={styles.statLabel}>Certificates</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
{/* Account Section */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Account</Text>
|
||||||
|
{accountMenuItems.map(renderMenuItem)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Security Section */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Security</Text>
|
||||||
|
{securityMenuItems.map(renderMenuItem)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Preferences Section */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Preferences</Text>
|
||||||
|
{preferencesMenuItems.map(renderMenuItem)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Support Section */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Support</Text>
|
||||||
|
{supportMenuItems.map(renderMenuItem)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Logout Button */}
|
||||||
|
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
|
||||||
|
<Ionicons name="log-out-outline" size={24} color={Colors.coral} />
|
||||||
|
<Text style={styles.logoutText}>Logout</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={{ height: 40 }} />
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
paddingTop: Spacing.xxxl,
|
||||||
|
paddingBottom: Spacing.xl,
|
||||||
|
borderBottomLeftRadius: BorderRadius.xxlarge,
|
||||||
|
borderBottomRightRadius: BorderRadius.xxlarge,
|
||||||
|
},
|
||||||
|
profileInfo: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
width: 70,
|
||||||
|
height: 70,
|
||||||
|
borderRadius: 35,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.3)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderWidth: 3,
|
||||||
|
borderColor: '#FFFFFF',
|
||||||
|
},
|
||||||
|
userInfo: {
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: Spacing.lg,
|
||||||
|
},
|
||||||
|
userName: {
|
||||||
|
fontSize: Typography.sizes.xlarge,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
userEmail: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
},
|
||||||
|
editButton: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.3)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
statsContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
padding: Spacing.lg,
|
||||||
|
},
|
||||||
|
statItem: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
statValue: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: Typography.weights.bold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
statLabel: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
},
|
||||||
|
statDivider: {
|
||||||
|
width: 1,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.3)',
|
||||||
|
marginHorizontal: Spacing.md,
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
menuItem: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
padding: Spacing.lg,
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
...Shadow.small,
|
||||||
|
},
|
||||||
|
menuIconContainer: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: Colors.teal + '20',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: Spacing.md,
|
||||||
|
},
|
||||||
|
menuContent: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
menuTitle: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.medium,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
menuSubtitle: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
logoutButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: Colors.coral + '20',
|
||||||
|
marginHorizontal: Spacing.xl,
|
||||||
|
marginTop: Spacing.xxxl,
|
||||||
|
padding: Spacing.lg,
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
gap: Spacing.sm,
|
||||||
|
},
|
||||||
|
logoutText: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.coral,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet, SafeAreaView } from 'react-native';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
|
||||||
|
export default function ProfileScreen() {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<Text style={styles.title}>Profile</Text>
|
||||||
|
<Text style={styles.subtitle}>Coming soon...</Text>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: { flex: 1, backgroundColor: Colors.background, justifyContent: 'center', alignItems: 'center' },
|
||||||
|
title: { fontSize: 28, fontWeight: '700', color: Colors.textDark, marginBottom: 8 },
|
||||||
|
subtitle: { fontSize: 16, color: Colors.textGray },
|
||||||
|
});
|
||||||
@@ -0,0 +1,378 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
|
ActivityIndicator,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
|
||||||
|
interface TrustActivity {
|
||||||
|
id: string;
|
||||||
|
type: 'vote' | 'proposal' | 'stake' | 'community';
|
||||||
|
description: string;
|
||||||
|
points: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TrustScoreScreen({ navigation }: any) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [trustScore] = useState(750);
|
||||||
|
const [activities, setActivities] = useState<TrustActivity[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadTrustData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadTrustData = async () => {
|
||||||
|
// Mock data - replace with blockchain data
|
||||||
|
setTimeout(() => {
|
||||||
|
setActivities([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'vote',
|
||||||
|
description: 'Voted on Proposal #42',
|
||||||
|
points: 10,
|
||||||
|
timestamp: Date.now() - 86400000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'stake',
|
||||||
|
description: 'Staked 10,000 HEZ',
|
||||||
|
points: 50,
|
||||||
|
timestamp: Date.now() - 172800000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: 'proposal',
|
||||||
|
description: 'Created Proposal #45',
|
||||||
|
points: 25,
|
||||||
|
timestamp: Date.now() - 259200000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
type: 'community',
|
||||||
|
description: 'Referred 3 new citizens',
|
||||||
|
points: 30,
|
||||||
|
timestamp: Date.now() - 345600000,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
setLoading(false);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActivityIcon = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'vote':
|
||||||
|
return 'checkmark-circle';
|
||||||
|
case 'proposal':
|
||||||
|
return 'bulb';
|
||||||
|
case 'stake':
|
||||||
|
return 'lock-closed';
|
||||||
|
case 'community':
|
||||||
|
return 'people';
|
||||||
|
default:
|
||||||
|
return 'star';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActivityColor = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'vote':
|
||||||
|
return Colors.mint;
|
||||||
|
case 'proposal':
|
||||||
|
return Colors.peach;
|
||||||
|
case 'stake':
|
||||||
|
return Colors.blue;
|
||||||
|
case 'community':
|
||||||
|
return Colors.lavender;
|
||||||
|
default:
|
||||||
|
return Colors.teal;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (timestamp: number) => {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const now = new Date();
|
||||||
|
const diff = now.getTime() - date.getTime();
|
||||||
|
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (days === 0) return 'Today';
|
||||||
|
if (days === 1) return 'Yesterday';
|
||||||
|
return `${days} days ago`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.backButton}>
|
||||||
|
<Ionicons name="arrow-back" size={24} color={Colors.textDark} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.headerTitle}>Trust Score</Text>
|
||||||
|
<View style={{ width: 40 }} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Trust Score Card */}
|
||||||
|
<LinearGradient
|
||||||
|
colors={[Colors.gold, '#E8C896']}
|
||||||
|
style={styles.scoreCard}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<Ionicons name="star" size={60} color="#FFFFFF" />
|
||||||
|
<Text style={styles.scoreValue}>{trustScore}</Text>
|
||||||
|
<Text style={styles.scoreLabel}>Your Trust Score</Text>
|
||||||
|
|
||||||
|
<View style={styles.scoreBadge}>
|
||||||
|
<Text style={styles.badgeText}>🏆 Gold Tier</Text>
|
||||||
|
</View>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
{/* Benefits */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Benefits</Text>
|
||||||
|
|
||||||
|
<View style={styles.benefitCard}>
|
||||||
|
<Ionicons name="trending-up" size={24} color={Colors.mint} />
|
||||||
|
<View style={styles.benefitText}>
|
||||||
|
<Text style={styles.benefitTitle}>Higher PEZ Rewards</Text>
|
||||||
|
<Text style={styles.benefitDescription}>
|
||||||
|
Earn 1.5x more PEZ rewards from staking
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.benefitCard}>
|
||||||
|
<Ionicons name="shield-checkmark" size={24} color={Colors.blue} />
|
||||||
|
<View style={styles.benefitText}>
|
||||||
|
<Text style={styles.benefitTitle}>Validator Priority</Text>
|
||||||
|
<Text style={styles.benefitDescription}>
|
||||||
|
Higher chance of being selected as validator
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.benefitCard}>
|
||||||
|
<Ionicons name="megaphone" size={24} color={Colors.peach} />
|
||||||
|
<View style={styles.benefitText}>
|
||||||
|
<Text style={styles.benefitTitle}>Governance Weight</Text>
|
||||||
|
<Text style={styles.benefitDescription}>
|
||||||
|
Your votes carry more weight in proposals
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Recent Activities */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Recent Activities</Text>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<ActivityIndicator size="large" color={Colors.teal} />
|
||||||
|
) : (
|
||||||
|
activities.map((activity) => (
|
||||||
|
<View key={activity.id} style={styles.activityCard}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.activityIcon,
|
||||||
|
{ backgroundColor: getActivityColor(activity.type) + '20' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name={getActivityIcon(activity.type)}
|
||||||
|
size={24}
|
||||||
|
color={getActivityColor(activity.type)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.activityInfo}>
|
||||||
|
<Text style={styles.activityDescription}>{activity.description}</Text>
|
||||||
|
<Text style={styles.activityDate}>{formatDate(activity.timestamp)}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.activityPoints}>
|
||||||
|
<Text style={styles.pointsValue}>+{activity.points}</Text>
|
||||||
|
<Text style={styles.pointsLabel}>points</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* How to Increase */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>How to Increase Trust Score</Text>
|
||||||
|
|
||||||
|
<View style={styles.tipCard}>
|
||||||
|
<Text style={styles.tipText}>✓ Vote on governance proposals</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.tipCard}>
|
||||||
|
<Text style={styles.tipText}>✓ Stake HEZ tokens</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.tipCard}>
|
||||||
|
<Text style={styles.tipText}>✓ Create valuable proposals</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.tipCard}>
|
||||||
|
<Text style={styles.tipText}>✓ Refer new citizens</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.tipCard}>
|
||||||
|
<Text style={styles.tipText}>✓ Maintain long-term participation</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingHorizontal: Spacing.xl,
|
||||||
|
paddingVertical: Spacing.lg,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#F0F0F0',
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
padding: Spacing.xl,
|
||||||
|
},
|
||||||
|
scoreCard: {
|
||||||
|
borderRadius: BorderRadius.xlarge,
|
||||||
|
padding: Spacing.xxxl,
|
||||||
|
alignItems: 'center',
|
||||||
|
...Shadow.large,
|
||||||
|
},
|
||||||
|
scoreValue: {
|
||||||
|
fontSize: 64,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#FFFFFF',
|
||||||
|
marginTop: Spacing.md,
|
||||||
|
},
|
||||||
|
scoreLabel: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
opacity: 0.9,
|
||||||
|
},
|
||||||
|
scoreBadge: {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.3)',
|
||||||
|
paddingHorizontal: Spacing.lg,
|
||||||
|
paddingVertical: Spacing.sm,
|
||||||
|
borderRadius: BorderRadius.xxlarge,
|
||||||
|
marginTop: Spacing.lg,
|
||||||
|
},
|
||||||
|
badgeText: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
marginTop: Spacing.xxxl,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: Spacing.lg,
|
||||||
|
},
|
||||||
|
benefitCard: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
padding: Spacing.lg,
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
...Shadow.small,
|
||||||
|
},
|
||||||
|
benefitText: {
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: Spacing.md,
|
||||||
|
},
|
||||||
|
benefitTitle: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
benefitDescription: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
activityCard: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: Colors.card,
|
||||||
|
padding: Spacing.lg,
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
alignItems: 'center',
|
||||||
|
...Shadow.small,
|
||||||
|
},
|
||||||
|
activityIcon: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 25,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
activityInfo: {
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: Spacing.md,
|
||||||
|
},
|
||||||
|
activityDescription: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
fontWeight: Typography.weights.medium,
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
activityDate: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
activityPoints: {
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
},
|
||||||
|
pointsValue: {
|
||||||
|
fontSize: Typography.sizes.large,
|
||||||
|
fontWeight: Typography.weights.semibold,
|
||||||
|
color: Colors.mint,
|
||||||
|
},
|
||||||
|
pointsLabel: {
|
||||||
|
fontSize: Typography.sizes.small,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
tipCard: {
|
||||||
|
backgroundColor: Colors.mint + '10',
|
||||||
|
padding: Spacing.md,
|
||||||
|
borderRadius: BorderRadius.medium,
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
tipText: {
|
||||||
|
fontSize: Typography.sizes.medium,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,587 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
Share,
|
||||||
|
Alert,
|
||||||
|
} from 'react-native';
|
||||||
|
import * as Clipboard from 'expo-clipboard';
|
||||||
|
import QRCode from 'react-native-qrcode-svg';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
|
||||||
|
interface ReferralStats {
|
||||||
|
totalReferrals: number;
|
||||||
|
activeReferrals: number;
|
||||||
|
totalRewards: number;
|
||||||
|
pendingRewards: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Referral {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
joinDate: string;
|
||||||
|
status: 'active' | 'pending' | 'inactive';
|
||||||
|
rewardEarned: number;
|
||||||
|
tier: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ReferralScreen() {
|
||||||
|
const [referralCode] = useState('PKW-2024-KURD-5X7Y');
|
||||||
|
const [stats] = useState<ReferralStats>({
|
||||||
|
totalReferrals: 12,
|
||||||
|
activeReferrals: 8,
|
||||||
|
totalRewards: 2450,
|
||||||
|
pendingRewards: 350,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [referrals] = useState<Referral[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'Ahmed Karwan',
|
||||||
|
joinDate: '2024-01-15',
|
||||||
|
status: 'active',
|
||||||
|
rewardEarned: 500,
|
||||||
|
tier: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'Layla Sherzad',
|
||||||
|
joinDate: '2024-01-20',
|
||||||
|
status: 'active',
|
||||||
|
rewardEarned: 500,
|
||||||
|
tier: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
name: 'Saman Aziz',
|
||||||
|
joinDate: '2024-02-05',
|
||||||
|
status: 'active',
|
||||||
|
rewardEarned: 250,
|
||||||
|
tier: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
name: 'Hana Dilshad',
|
||||||
|
joinDate: '2024-02-18',
|
||||||
|
status: 'pending',
|
||||||
|
rewardEarned: 0,
|
||||||
|
tier: 1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const copyToClipboard = async () => {
|
||||||
|
await Clipboard.setStringAsync(referralCode);
|
||||||
|
Alert.alert('✓ Copied!', 'Referral code copied to clipboard');
|
||||||
|
};
|
||||||
|
|
||||||
|
const shareReferralCode = async () => {
|
||||||
|
try {
|
||||||
|
await Share.share({
|
||||||
|
message: `Join PezkuwiChain - Digital Kurdistan's Blockchain Platform!\n\nUse my referral code: ${referralCode}\n\nEarn rewards and be part of building Digital Kurdistan! 🦅`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sharing:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'active':
|
||||||
|
return Colors.success;
|
||||||
|
case 'pending':
|
||||||
|
return Colors.warning;
|
||||||
|
case 'inactive':
|
||||||
|
return Colors.textGray;
|
||||||
|
default:
|
||||||
|
return Colors.textGray;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTierReward = (tier: number) => {
|
||||||
|
switch (tier) {
|
||||||
|
case 1:
|
||||||
|
return '500 PEZ';
|
||||||
|
case 2:
|
||||||
|
return '250 PEZ';
|
||||||
|
case 3:
|
||||||
|
return '100 PEZ';
|
||||||
|
default:
|
||||||
|
return '50 PEZ';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.headerTitle}>Referral Program</Text>
|
||||||
|
<Text style={styles.headerSubtitle}>
|
||||||
|
Invite friends and earn rewards together
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Referral Code Card */}
|
||||||
|
<View style={styles.codeCard}>
|
||||||
|
<Text style={styles.codeLabel}>Your Referral Code</Text>
|
||||||
|
<View style={styles.qrContainer}>
|
||||||
|
<QRCode value={referralCode} size={120} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.codeBox}>
|
||||||
|
<Text style={styles.codeText}>{referralCode}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.actionButtons}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.actionButton}
|
||||||
|
onPress={copyToClipboard}
|
||||||
|
>
|
||||||
|
<Ionicons name="copy-outline" size={20} color={Colors.primary} />
|
||||||
|
<Text style={styles.actionButtonText}>Copy Code</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.actionButton, styles.shareButton]}
|
||||||
|
onPress={shareReferralCode}
|
||||||
|
>
|
||||||
|
<Ionicons name="share-social-outline" size={20} color="white" />
|
||||||
|
<Text style={styles.shareButtonText}>Share</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Statistics Grid */}
|
||||||
|
<View style={styles.statsGrid}>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons name="people-outline" size={28} color={Colors.primary} />
|
||||||
|
<Text style={styles.statValue}>{stats.totalReferrals}</Text>
|
||||||
|
<Text style={styles.statLabel}>Total Referrals</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
size={28}
|
||||||
|
color={Colors.success}
|
||||||
|
/>
|
||||||
|
<Text style={styles.statValue}>{stats.activeReferrals}</Text>
|
||||||
|
<Text style={styles.statLabel}>Active</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons name="trophy-outline" size={28} color={Colors.kurdishGold} />
|
||||||
|
<Text style={styles.statValue}>{stats.totalRewards}</Text>
|
||||||
|
<Text style={styles.statLabel}>Total Rewards (PEZ)</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statCard}>
|
||||||
|
<Ionicons name="time-outline" size={28} color={Colors.warning} />
|
||||||
|
<Text style={styles.statValue}>{stats.pendingRewards}</Text>
|
||||||
|
<Text style={styles.statLabel}>Pending (PEZ)</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Reward Tiers */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Reward Tiers</Text>
|
||||||
|
<View style={styles.tierCard}>
|
||||||
|
<View style={styles.tierRow}>
|
||||||
|
<View style={styles.tierBadge}>
|
||||||
|
<Text style={styles.tierBadgeText}>Tier 1</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.tierDescription}>Direct Referrals</Text>
|
||||||
|
<Text style={styles.tierReward}>500 PEZ</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.tierRow}>
|
||||||
|
<View style={[styles.tierBadge, { backgroundColor: Colors.success }]}>
|
||||||
|
<Text style={styles.tierBadgeText}>Tier 2</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.tierDescription}>2nd Level Referrals</Text>
|
||||||
|
<Text style={styles.tierReward}>250 PEZ</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.tierRow}>
|
||||||
|
<View style={[styles.tierBadge, { backgroundColor: Colors.kurdishGold }]}>
|
||||||
|
<Text style={styles.tierBadgeText}>Tier 3</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.tierDescription}>3rd Level Referrals</Text>
|
||||||
|
<Text style={styles.tierReward}>100 PEZ</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Referral List */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Your Referrals</Text>
|
||||||
|
{referrals.map((referral) => (
|
||||||
|
<View key={referral.id} style={styles.referralCard}>
|
||||||
|
<View style={styles.referralHeader}>
|
||||||
|
<View style={styles.avatarCircle}>
|
||||||
|
<Text style={styles.avatarText}>
|
||||||
|
{referral.name.charAt(0)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.referralInfo}>
|
||||||
|
<Text style={styles.referralName}>{referral.name}</Text>
|
||||||
|
<Text style={styles.referralDate}>
|
||||||
|
Joined: {referral.joinDate}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.statusBadge,
|
||||||
|
{ backgroundColor: getStatusColor(referral.status) + '20' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.statusText,
|
||||||
|
{ color: getStatusColor(referral.status) },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{referral.status.charAt(0).toUpperCase() +
|
||||||
|
referral.status.slice(1)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={styles.referralFooter}>
|
||||||
|
<View style={styles.tierInfo}>
|
||||||
|
<Ionicons
|
||||||
|
name="ribbon-outline"
|
||||||
|
size={16}
|
||||||
|
color={Colors.primary}
|
||||||
|
/>
|
||||||
|
<Text style={styles.tierText}>Tier {referral.tier}</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.rewardText}>
|
||||||
|
Earned: {referral.rewardEarned} PEZ
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* How It Works */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>How It Works</Text>
|
||||||
|
<View style={styles.infoCard}>
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<View style={styles.stepNumber}>
|
||||||
|
<Text style={styles.stepNumberText}>1</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
Share your unique referral code with friends
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<View style={styles.stepNumber}>
|
||||||
|
<Text style={styles.stepNumberText}>2</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
They sign up and complete KYC verification
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<View style={styles.stepNumber}>
|
||||||
|
<Text style={styles.stepNumberText}>3</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
Both of you earn PEZ rewards based on tier level
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<View style={styles.stepNumber}>
|
||||||
|
<Text style={styles.stepNumberText}>4</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
Rewards are distributed automatically to your wallet
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ height: 40 }} />
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingTop: 20,
|
||||||
|
paddingBottom: 16,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
headerSubtitle: {
|
||||||
|
fontSize: 15,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
codeCard: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
marginHorizontal: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 24,
|
||||||
|
alignItems: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
codeLabel: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
qrContainer: {
|
||||||
|
padding: 16,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
borderRadius: 12,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
codeBox: {
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingVertical: 12,
|
||||||
|
borderRadius: 10,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
codeText: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.primary,
|
||||||
|
letterSpacing: 1,
|
||||||
|
},
|
||||||
|
actionButtons: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 12,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
actionButton: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 12,
|
||||||
|
borderRadius: 10,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
gap: 6,
|
||||||
|
},
|
||||||
|
actionButtonText: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.primary,
|
||||||
|
},
|
||||||
|
shareButton: {
|
||||||
|
backgroundColor: Colors.primary,
|
||||||
|
},
|
||||||
|
shareButtonText: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
statsGrid: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
gap: 12,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
statCard: {
|
||||||
|
flex: 1,
|
||||||
|
minWidth: '47%',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 16,
|
||||||
|
alignItems: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
statValue: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginTop: 8,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
statLabel: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.textGray,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
tierCard: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 16,
|
||||||
|
gap: 12,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
tierRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
tierBadge: {
|
||||||
|
backgroundColor: Colors.primary,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: 8,
|
||||||
|
minWidth: 70,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
tierBadgeText: {
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
tierDescription: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
tierReward: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.kurdishGold,
|
||||||
|
},
|
||||||
|
referralCard: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 12,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
referralHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
avatarCircle: {
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
borderRadius: 22,
|
||||||
|
backgroundColor: Colors.primary + '20',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
avatarText: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.primary,
|
||||||
|
},
|
||||||
|
referralInfo: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
referralName: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
referralDate: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
statusBadge: {
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
statusText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
referralFooter: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingTop: 12,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: Colors.background,
|
||||||
|
},
|
||||||
|
tierInfo: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
},
|
||||||
|
tierText: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.textDark,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
rewardText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.success,
|
||||||
|
},
|
||||||
|
infoCard: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 20,
|
||||||
|
gap: 16,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.04,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
infoRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
stepNumber: {
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
borderRadius: 14,
|
||||||
|
backgroundColor: Colors.primary,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
stepNumberText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.textDark,
|
||||||
|
lineHeight: 20,
|
||||||
|
paddingTop: 4,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,501 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import SendModal from '../../components/SendModal';
|
||||||
|
import ReceiveModal from '../../components/ReceiveModal';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
|
TextInput,
|
||||||
|
Image,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
|
||||||
|
interface CoinRow {
|
||||||
|
symbol: string;
|
||||||
|
name: string;
|
||||||
|
amount: string;
|
||||||
|
network: string;
|
||||||
|
icon: any;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WalletScreen({ navigation, route }: any) {
|
||||||
|
const [withdrawalAddresses, setWithdrawalAddresses] = useState<{ [key: string]: string }>({});
|
||||||
|
const [sendModalVisible, setSendModalVisible] = useState(false);
|
||||||
|
const [receiveModalVisible, setReceiveModalVisible] = useState(false);
|
||||||
|
const [selectedToken, setSelectedToken] = useState<CoinRow | null>(null);
|
||||||
|
const [totalReward] = useState('50000');
|
||||||
|
const [trustScore] = useState('150000');
|
||||||
|
const [totalReferral] = useState('500');
|
||||||
|
const [circleScore] = useState('30000');
|
||||||
|
const [eduScore] = useState('1000');
|
||||||
|
const [stakeAmount] = useState('1000');
|
||||||
|
|
||||||
|
const coins: CoinRow[] = [
|
||||||
|
{
|
||||||
|
symbol: 'PEZ',
|
||||||
|
name: 'PezkuwiChain',
|
||||||
|
amount: '100',
|
||||||
|
network: 'op',
|
||||||
|
icon: require('../../../assets/tokens/pez.png'),
|
||||||
|
color: '#E8C896',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'HEZ',
|
||||||
|
name: 'Hemwelatî',
|
||||||
|
amount: '100',
|
||||||
|
network: 'op',
|
||||||
|
icon: require('../../../assets/tokens/hez.png'),
|
||||||
|
color: '#98D8C8',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleWithdraw = (coin: CoinRow) => {
|
||||||
|
setSelectedToken(coin);
|
||||||
|
setSendModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeposit = (coin: CoinRow) => {
|
||||||
|
setSelectedToken(coin);
|
||||||
|
setReceiveModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
{/* Colorful Top Band */}
|
||||||
|
<View style={styles.topBand}>
|
||||||
|
<View style={[styles.bandSegment, { backgroundColor: '#E74C3C' }]} />
|
||||||
|
<View style={[styles.bandSegment, { backgroundColor: '#27AE60' }]} />
|
||||||
|
<View style={[styles.bandSegment, { backgroundColor: '#3498DB' }]} />
|
||||||
|
<View style={[styles.bandSegment, { backgroundColor: '#F39C12' }]} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
||||||
|
<View style={styles.mainSection}>
|
||||||
|
{/* NFT Card - Top Right */}
|
||||||
|
<View style={styles.nftCard}>
|
||||||
|
<View style={styles.nftHeader}>
|
||||||
|
<Text style={styles.nftLabel}>NFT</Text>
|
||||||
|
<Text style={styles.nftId}>123456784444444444444444912</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.nftBody}>
|
||||||
|
<View style={styles.nftAvatar}>
|
||||||
|
<Ionicons name="person" size={40} color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.nftName}>Satoshi Qazi M.</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.nftFooter}>
|
||||||
|
<Ionicons name="trophy" size={20} color="#D4A017" />
|
||||||
|
<Text style={styles.nftRewardLabel}>TOTAL REWARD</Text>
|
||||||
|
<Text style={styles.nftRewardValue}>{totalReward}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Coins Table */}
|
||||||
|
<View style={styles.tableContainer}>
|
||||||
|
{/* Table Header */}
|
||||||
|
<View style={styles.tableHeader}>
|
||||||
|
<Text style={[styles.headerCell, { flex: 1 }]}>coin</Text>
|
||||||
|
<Text style={[styles.headerCell, { flex: 1 }]}>amount</Text>
|
||||||
|
<Text style={[styles.headerCell, { flex: 1 }]}>Network</Text>
|
||||||
|
<Text style={[styles.headerCell, { flex: 3 }]}>Witdrawal adress</Text>
|
||||||
|
<Text style={[styles.headerCell, { flex: 1.5 }]}>withdraw deposit</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Table Rows */}
|
||||||
|
{coins.map((coin) => (
|
||||||
|
<View key={coin.symbol} style={styles.tableRow}>
|
||||||
|
{/* Coin */}
|
||||||
|
<View style={[styles.cell, { flex: 1 }]}>
|
||||||
|
<Image source={coin.icon} style={styles.coinIcon} />
|
||||||
|
<Text style={styles.coinSymbol}>{coin.symbol}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Amount */}
|
||||||
|
<View style={[styles.cell, { flex: 1 }]}>
|
||||||
|
<Text style={styles.cellText}>{coin.amount}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Network */}
|
||||||
|
<View style={[styles.cell, { flex: 1 }]}>
|
||||||
|
<Text style={styles.cellText}>{coin.network}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Withdrawal Address */}
|
||||||
|
<View style={[styles.cell, { flex: 3 }]}>
|
||||||
|
<TextInput
|
||||||
|
style={styles.addressInput}
|
||||||
|
placeholder="Withdrawal adresinizi yazin"
|
||||||
|
placeholderTextColor="#999"
|
||||||
|
value={withdrawalAddresses[coin.symbol] || ''}
|
||||||
|
onChangeText={(text) =>
|
||||||
|
setWithdrawalAddresses({ ...withdrawalAddresses, [coin.symbol]: text })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Withdraw/Deposit Buttons */}
|
||||||
|
<View style={[styles.cell, { flex: 1.5, flexDirection: 'row', gap: 8 }]}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.actionButton}
|
||||||
|
onPress={() => handleWithdraw(coin)}
|
||||||
|
>
|
||||||
|
<View style={styles.withdrawButton}>
|
||||||
|
<Ionicons name="arrow-down" size={20} color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.actionButton}
|
||||||
|
onPress={() => handleDeposit(coin)}
|
||||||
|
>
|
||||||
|
<View style={styles.depositButton}>
|
||||||
|
<Ionicons name="arrow-up" size={20} color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Bottom Stats Cards */}
|
||||||
|
<View style={styles.statsSection}>
|
||||||
|
{/* Stake/Unstake Card */}
|
||||||
|
<View style={[styles.statsCard, { backgroundColor: '#FFFFFF' }]}>
|
||||||
|
<TouchableOpacity style={styles.stakeButton}>
|
||||||
|
<Text style={styles.stakeLabel}>STAKE</Text>
|
||||||
|
<Text style={styles.stakeValue}>{stakeAmount}</Text>
|
||||||
|
<View style={styles.stakeIcon}>
|
||||||
|
<Ionicons name="arrow-up" size={16} color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={styles.stakeDivider} />
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.unstakeButton}>
|
||||||
|
<Text style={styles.unstakeLabel}>UNSTAKE</Text>
|
||||||
|
<Text style={styles.unstakeValue}>{stakeAmount}</Text>
|
||||||
|
<View style={styles.unstakeIcon}>
|
||||||
|
<Ionicons name="arrow-down" size={16} color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Trust Score */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.statsCard, styles.scoreCard]}
|
||||||
|
onPress={() => navigation.navigate('TrustScore')}
|
||||||
|
>
|
||||||
|
<Ionicons name="shield-checkmark" size={32} color="#D4A017" />
|
||||||
|
<Text style={styles.scoreLabel}>TRUST SCORE</Text>
|
||||||
|
<Text style={styles.scoreValue}>{trustScore}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Total Referral */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.statsCard, styles.scoreCard]}
|
||||||
|
onPress={() => navigation.navigate('Referral')}
|
||||||
|
>
|
||||||
|
<Ionicons name="people" size={32} color="#D4A017" />
|
||||||
|
<Text style={styles.scoreLabel}>TOTAL REFERAL</Text>
|
||||||
|
<Text style={styles.scoreValue}>{totalReferral}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Circle Score */}
|
||||||
|
<TouchableOpacity style={[styles.statsCard, styles.scoreCard]}>
|
||||||
|
<Ionicons name="radio-button-on" size={32} color="#D4A017" />
|
||||||
|
<Text style={styles.scoreLabel}>CIRCLE SCORE</Text>
|
||||||
|
<Text style={styles.scoreValue}>{circleScore}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Edu Score */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.statsCard, styles.scoreCard]}
|
||||||
|
onPress={() => navigation.navigate('Education')}
|
||||||
|
>
|
||||||
|
<Ionicons name="school" size={32} color="#D4A017" />
|
||||||
|
<Text style={styles.scoreLabel}>EDU. SCORE</Text>
|
||||||
|
<Text style={styles.scoreValue}>{eduScore}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
{/* Send Modal */}
|
||||||
|
{selectedToken && (
|
||||||
|
<SendModal
|
||||||
|
visible={sendModalVisible}
|
||||||
|
onClose={() => setSendModalVisible(false)}
|
||||||
|
token={{
|
||||||
|
symbol: selectedToken.symbol,
|
||||||
|
name: selectedToken.name,
|
||||||
|
balance: selectedToken.amount,
|
||||||
|
icon: selectedToken.icon,
|
||||||
|
color: selectedToken.color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Receive Modal */}
|
||||||
|
{selectedToken && (
|
||||||
|
<ReceiveModal
|
||||||
|
visible={receiveModalVisible}
|
||||||
|
onClose={() => setReceiveModalVisible(false)}
|
||||||
|
token={{
|
||||||
|
symbol: selectedToken.symbol,
|
||||||
|
name: selectedToken.name,
|
||||||
|
icon: selectedToken.icon,
|
||||||
|
color: selectedToken.color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#E8F0F7',
|
||||||
|
},
|
||||||
|
topBand: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: 8,
|
||||||
|
},
|
||||||
|
bandSegment: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
mainSection: {
|
||||||
|
padding: Spacing.lg,
|
||||||
|
position: 'relative',
|
||||||
|
},
|
||||||
|
nftCard: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: Spacing.lg,
|
||||||
|
right: Spacing.lg,
|
||||||
|
width: 140,
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
padding: Spacing.md,
|
||||||
|
...Shadow.medium,
|
||||||
|
zIndex: 10,
|
||||||
|
},
|
||||||
|
nftHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
nftLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
nftId: {
|
||||||
|
fontSize: 8,
|
||||||
|
color: Colors.textGray,
|
||||||
|
},
|
||||||
|
nftBody: {
|
||||||
|
alignItems: 'center',
|
||||||
|
marginVertical: Spacing.sm,
|
||||||
|
},
|
||||||
|
nftAvatar: {
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
borderRadius: 30,
|
||||||
|
backgroundColor: Colors.teal,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: Spacing.xs,
|
||||||
|
},
|
||||||
|
nftName: {
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
nftFooter: {
|
||||||
|
alignItems: 'center',
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: '#F0F0F0',
|
||||||
|
paddingTop: Spacing.sm,
|
||||||
|
},
|
||||||
|
nftRewardLabel: {
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textGray,
|
||||||
|
marginTop: 4,
|
||||||
|
},
|
||||||
|
nftRewardValue: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
tableContainer: {
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
padding: Spacing.md,
|
||||||
|
marginRight: 160,
|
||||||
|
...Shadow.small,
|
||||||
|
},
|
||||||
|
tableHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
paddingBottom: Spacing.sm,
|
||||||
|
borderBottomWidth: 2,
|
||||||
|
borderBottomColor: '#E0E0E0',
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
headerCell: {
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
tableRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
paddingVertical: Spacing.md,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#F0F0F0',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
cell: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
coinIcon: {
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
coinSymbol: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
cellText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
addressInput: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
borderRadius: BorderRadius.small,
|
||||||
|
paddingHorizontal: Spacing.sm,
|
||||||
|
paddingVertical: Spacing.xs,
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
actionButton: {
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 8,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
withdrawButton: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
backgroundColor: '#E74C3C',
|
||||||
|
borderRadius: 8,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
depositButton: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
backgroundColor: '#27AE60',
|
||||||
|
borderRadius: 8,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
statsSection: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
padding: Spacing.lg,
|
||||||
|
gap: Spacing.md,
|
||||||
|
},
|
||||||
|
statsCard: {
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
borderRadius: BorderRadius.large,
|
||||||
|
padding: Spacing.lg,
|
||||||
|
...Shadow.small,
|
||||||
|
},
|
||||||
|
stakeButton: {
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
stakeLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
stakeValue: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginVertical: Spacing.xs,
|
||||||
|
},
|
||||||
|
stakeIcon: {
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
borderRadius: 14,
|
||||||
|
backgroundColor: '#27AE60',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
stakeDivider: {
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: '#E0E0E0',
|
||||||
|
marginVertical: Spacing.md,
|
||||||
|
},
|
||||||
|
unstakeButton: {
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
unstakeLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
},
|
||||||
|
unstakeValue: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginVertical: Spacing.xs,
|
||||||
|
},
|
||||||
|
unstakeIcon: {
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
borderRadius: 14,
|
||||||
|
backgroundColor: '#E74C3C',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
scoreCard: {
|
||||||
|
alignItems: 'center',
|
||||||
|
minWidth: 110,
|
||||||
|
},
|
||||||
|
scoreLabel: {
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginTop: Spacing.sm,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
scoreValue: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: Colors.textDark,
|
||||||
|
marginTop: Spacing.xs,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet, TouchableOpacity, ScrollView, SafeAreaView } from 'react-native';
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import Colors from '../../constants/colors';
|
||||||
|
import { Typography, Spacing, BorderRadius, Shadow } from '../../constants/theme';
|
||||||
|
|
||||||
|
export default function WalletScreen({ navigation }: any) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
|
<Text style={styles.title}>Wallet</Text>
|
||||||
|
|
||||||
|
{/* HEZ Card */}
|
||||||
|
<LinearGradient
|
||||||
|
colors={Colors.gradients.hezCard}
|
||||||
|
style={styles.tokenCard}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<View style={styles.tokenHeader}>
|
||||||
|
<Text style={styles.tokenName}>HEZ</Text>
|
||||||
|
<Text style={styles.tokenSubtitle}>The People's Currency</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.tokenBalance}>45,750.5</Text>
|
||||||
|
<Text style={styles.tokenUsd}>$45,234 USD</Text>
|
||||||
|
<Text style={styles.tokenStaked}>Staked: 30,000 HEZ</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
{/* PEZ Card */}
|
||||||
|
<LinearGradient
|
||||||
|
colors={Colors.gradients.pezCard}
|
||||||
|
style={styles.tokenCard}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<View style={styles.tokenHeader}>
|
||||||
|
<Text style={styles.tokenName}>PEZ</Text>
|
||||||
|
<Text style={styles.tokenSubtitle}>Governance Token</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.tokenBalance}>1,234,567</Text>
|
||||||
|
<Text style={styles.tokenUsd}>$123,456 USD</Text>
|
||||||
|
<Text style={styles.tokenStaked}>Governance Power: 2.5%</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<View style={styles.actions}>
|
||||||
|
<TouchableOpacity style={[styles.actionButton, { backgroundColor: Colors.peach }]}>
|
||||||
|
<Ionicons name="arrow-forward" size={24} color="#FFFFFF" />
|
||||||
|
<Text style={styles.actionText}>Send</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={[styles.actionButton, { backgroundColor: Colors.teal }]}>
|
||||||
|
<Ionicons name="arrow-down" size={24} color="#FFFFFF" />
|
||||||
|
<Text style={styles.actionText}>Receive</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Transactions */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Recent Transactions</Text>
|
||||||
|
<View style={styles.transactionItem}>
|
||||||
|
<View style={styles.transactionIcon}>
|
||||||
|
<Ionicons name="arrow-forward" size={20} color={Colors.coral} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.transactionInfo}>
|
||||||
|
<Text style={styles.transactionTitle}>Sent HEZ</Text>
|
||||||
|
<Text style={styles.transactionDate}>Yesterday</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.transactionAmount}>-500 HEZ</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: { flex: 1, backgroundColor: Colors.background },
|
||||||
|
title: { fontSize: 28, fontWeight: '700', color: Colors.textDark, padding: 20 },
|
||||||
|
tokenCard: { margin: 20, padding: 24, borderRadius: 20, ...Shadow.soft },
|
||||||
|
tokenHeader: { marginBottom: 16 },
|
||||||
|
tokenName: { fontSize: 24, fontWeight: '700', color: '#FFFFFF' },
|
||||||
|
tokenSubtitle: { fontSize: 14, color: 'rgba(255,255,255,0.8)' },
|
||||||
|
tokenBalance: { fontSize: 36, fontWeight: '700', color: '#FFFFFF', marginBottom: 8 },
|
||||||
|
tokenUsd: { fontSize: 18, color: 'rgba(255,255,255,0.9)', marginBottom: 8 },
|
||||||
|
tokenStaked: { fontSize: 14, color: 'rgba(255,255,255,0.8)' },
|
||||||
|
actions: { flexDirection: 'row', paddingHorizontal: 20, gap: 12 },
|
||||||
|
actionButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', padding: 16, borderRadius: 16, gap: 8 },
|
||||||
|
actionText: { fontSize: 16, fontWeight: '600', color: '#FFFFFF' },
|
||||||
|
section: { padding: 20 },
|
||||||
|
sectionTitle: { fontSize: 18, fontWeight: '600', color: Colors.textDark, marginBottom: 16 },
|
||||||
|
transactionItem: { flexDirection: 'row', alignItems: 'center', backgroundColor: Colors.card, padding: 16, borderRadius: 12, marginBottom: 12 },
|
||||||
|
transactionIcon: { width: 40, height: 40, borderRadius: 20, backgroundColor: Colors.background, justifyContent: 'center', alignItems: 'center', marginRight: 12 },
|
||||||
|
transactionInfo: { flex: 1 },
|
||||||
|
transactionTitle: { fontSize: 16, fontWeight: '600', color: Colors.textDark },
|
||||||
|
transactionDate: { fontSize: 14, color: Colors.textGray, marginTop: 4 },
|
||||||
|
transactionAmount: { fontSize: 16, fontWeight: '600', color: Colors.coral },
|
||||||
|
});
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
/**
|
||||||
|
* PezkuwiChain Blockchain Service
|
||||||
|
* Handles all interactions with the PezkuwiChain blockchain via Polkadot.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ApiPromise, WsProvider } from '@polkadot/api';
|
||||||
|
import { CURRENT_CHAIN_CONFIG, ASSET_IDS } from '../constants/blockchain';
|
||||||
|
import { Balance, Transaction, Proposal } from '../types';
|
||||||
|
|
||||||
|
class BlockchainService {
|
||||||
|
private api: ApiPromise | null = null;
|
||||||
|
private provider: WsProvider | null = null;
|
||||||
|
private isConnected: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize connection to PezkuwiChain
|
||||||
|
*/
|
||||||
|
async connect(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
console.log(`Connecting to ${CURRENT_CHAIN_CONFIG.name}...`);
|
||||||
|
console.log(`RPC URL: ${CURRENT_CHAIN_CONFIG.rpcUrl}`);
|
||||||
|
|
||||||
|
this.provider = new WsProvider(CURRENT_CHAIN_CONFIG.rpcUrl);
|
||||||
|
this.api = await ApiPromise.create({ provider: this.provider });
|
||||||
|
|
||||||
|
await this.api.isReady;
|
||||||
|
this.isConnected = true;
|
||||||
|
|
||||||
|
console.log('✅ Connected to PezkuwiChain');
|
||||||
|
console.log(`Chain: ${await this.api.rpc.system.chain()}`);
|
||||||
|
console.log(`Node: ${await this.api.rpc.system.name()}`);
|
||||||
|
console.log(`Version: ${await this.api.rpc.system.version()}`);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to connect to PezkuwiChain:', error);
|
||||||
|
this.isConnected = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from blockchain
|
||||||
|
*/
|
||||||
|
async disconnect(): Promise<void> {
|
||||||
|
if (this.api) {
|
||||||
|
await this.api.disconnect();
|
||||||
|
this.api = null;
|
||||||
|
this.provider = null;
|
||||||
|
this.isConnected = false;
|
||||||
|
console.log('Disconnected from PezkuwiChain');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if connected to blockchain
|
||||||
|
*/
|
||||||
|
isApiConnected(): boolean {
|
||||||
|
return this.isConnected && this.api !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get API instance (throws if not connected)
|
||||||
|
*/
|
||||||
|
private getApi(): ApiPromise {
|
||||||
|
if (!this.api || !this.isConnected) {
|
||||||
|
throw new Error('Not connected to blockchain. Call connect() first.');
|
||||||
|
}
|
||||||
|
return this.api;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get account balances (HEZ and PEZ)
|
||||||
|
*/
|
||||||
|
async getBalances(address: string): Promise<Balance> {
|
||||||
|
try {
|
||||||
|
const api = this.getApi();
|
||||||
|
|
||||||
|
// Get HEZ balance (native token)
|
||||||
|
const { data: hezBalance } = await api.query.system.account(address);
|
||||||
|
const hezFree = hezBalance.free.toString();
|
||||||
|
const hezReserved = hezBalance.reserved.toString();
|
||||||
|
|
||||||
|
// Get PEZ balance (asset)
|
||||||
|
const pezBalance = await api.query.assets.account(ASSET_IDS.PEZ, address);
|
||||||
|
const pezFree = pezBalance.isSome ? pezBalance.unwrap().balance.toString() : '0';
|
||||||
|
|
||||||
|
// Get staked HEZ
|
||||||
|
const stakingInfo = await api.query.staking.ledger(address);
|
||||||
|
const hezStaked = stakingInfo.isSome ? stakingInfo.unwrap().active.toString() : '0';
|
||||||
|
|
||||||
|
// Calculate governance power (PEZ balance as percentage)
|
||||||
|
const totalPezSupply = '5000000000000000000000'; // 5 billion
|
||||||
|
const governancePower = (parseFloat(pezFree) / parseFloat(totalPezSupply) * 100).toFixed(2);
|
||||||
|
|
||||||
|
// Mock USD values (would come from price oracle in production)
|
||||||
|
const hezUsd = (parseFloat(hezFree) / 1e12 * 1.0).toFixed(2);
|
||||||
|
const pezUsd = (parseFloat(pezFree) / 1e12 * 0.1).toFixed(2);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hez: this.formatBalance(hezFree, 12),
|
||||||
|
pez: this.formatBalance(pezFree, 12),
|
||||||
|
hezStaked: this.formatBalance(hezStaked, 12),
|
||||||
|
hezUsd,
|
||||||
|
pezUsd,
|
||||||
|
governancePower,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching balances:', error);
|
||||||
|
// Return mock data if blockchain not available
|
||||||
|
return this.getMockBalances();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get transaction history
|
||||||
|
*/
|
||||||
|
async getTransactions(address: string, limit: number = 10): Promise<Transaction[]> {
|
||||||
|
try {
|
||||||
|
// This would query blockchain events and filter for transfers
|
||||||
|
// For now, return mock data
|
||||||
|
return this.getMockTransactions();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching transactions:', error);
|
||||||
|
return this.getMockTransactions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active governance proposals
|
||||||
|
*/
|
||||||
|
async getProposals(): Promise<Proposal[]> {
|
||||||
|
try {
|
||||||
|
const api = this.getApi();
|
||||||
|
|
||||||
|
// Query welati pallet for active proposals
|
||||||
|
const proposals = await api.query.welati.proposals.entries();
|
||||||
|
|
||||||
|
return proposals.map(([key, value]: any) => {
|
||||||
|
const proposalId = key.args[0].toNumber();
|
||||||
|
const proposal = value.unwrap();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: proposalId,
|
||||||
|
title: `Proposal ${proposalId}`,
|
||||||
|
description: proposal.description?.toString() || 'No description',
|
||||||
|
proposer: proposal.proposer.toString(),
|
||||||
|
votingDeadline: proposal.deadline.toNumber(),
|
||||||
|
yesVotes: proposal.yesVotes.toString(),
|
||||||
|
noVotes: proposal.noVotes.toString(),
|
||||||
|
status: proposal.status.toString() as any,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching proposals:', error);
|
||||||
|
return this.getMockProposals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send HEZ or PEZ tokens
|
||||||
|
*/
|
||||||
|
async sendTokens(
|
||||||
|
from: string,
|
||||||
|
to: string,
|
||||||
|
amount: string,
|
||||||
|
token: 'HEZ' | 'PEZ',
|
||||||
|
signer: any
|
||||||
|
): Promise<string> {
|
||||||
|
try {
|
||||||
|
const api = this.getApi();
|
||||||
|
|
||||||
|
let tx;
|
||||||
|
if (token === 'HEZ') {
|
||||||
|
tx = api.tx.balances.transfer(to, amount);
|
||||||
|
} else {
|
||||||
|
tx = api.tx.assets.transfer(ASSET_IDS.PEZ, to, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = await tx.signAndSend(signer);
|
||||||
|
return hash.toString();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending tokens:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stake HEZ tokens
|
||||||
|
*/
|
||||||
|
async stakeTokens(
|
||||||
|
address: string,
|
||||||
|
amount: string,
|
||||||
|
signer: any
|
||||||
|
): Promise<string> {
|
||||||
|
try {
|
||||||
|
const api = this.getApi();
|
||||||
|
const tx = api.tx.staking.bond(address, amount, 'Staked');
|
||||||
|
const hash = await tx.signAndSend(signer);
|
||||||
|
return hash.toString();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error staking tokens:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vote on governance proposal
|
||||||
|
*/
|
||||||
|
async voteOnProposal(
|
||||||
|
proposalId: number,
|
||||||
|
vote: 'yes' | 'no',
|
||||||
|
amount: string,
|
||||||
|
signer: any
|
||||||
|
): Promise<string> {
|
||||||
|
try {
|
||||||
|
const api = this.getApi();
|
||||||
|
const tx = api.tx.welati.vote(proposalId, vote === 'yes', amount);
|
||||||
|
const hash = await tx.signAndSend(signer);
|
||||||
|
return hash.toString();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error voting on proposal:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format balance with decimals
|
||||||
|
*/
|
||||||
|
private formatBalance(balance: string, decimals: number): string {
|
||||||
|
const value = parseFloat(balance) / Math.pow(10, decimals);
|
||||||
|
return value.toLocaleString('en-US', { maximumFractionDigits: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock data for development/testing
|
||||||
|
*/
|
||||||
|
private getMockBalances(): Balance {
|
||||||
|
return {
|
||||||
|
hez: '45,750.5',
|
||||||
|
pez: '1,234,567',
|
||||||
|
hezStaked: '30,000',
|
||||||
|
hezUsd: '45,234',
|
||||||
|
pezUsd: '123,456',
|
||||||
|
governancePower: '2.5',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMockTransactions(): Transaction[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'send' as any,
|
||||||
|
amount: '500',
|
||||||
|
token: 'HEZ',
|
||||||
|
from: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
|
||||||
|
to: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
|
||||||
|
timestamp: Date.now() - 86400000,
|
||||||
|
blockNumber: 123456,
|
||||||
|
hash: '0x1234567890abcdef',
|
||||||
|
status: 'confirmed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'receive' as any,
|
||||||
|
amount: '300',
|
||||||
|
token: 'PEZ',
|
||||||
|
from: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
|
||||||
|
to: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
|
||||||
|
timestamp: Date.now() - 172800000,
|
||||||
|
blockNumber: 123450,
|
||||||
|
hash: '0xabcdef1234567890',
|
||||||
|
status: 'confirmed',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMockProposals(): Proposal[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'Proposal 1',
|
||||||
|
description: 'Description of proposal 1',
|
||||||
|
proposer: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
|
||||||
|
votingDeadline: Date.now() + 172800000,
|
||||||
|
yesVotes: '10400',
|
||||||
|
noVotes: '4600',
|
||||||
|
status: 'active',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'Proposal 2',
|
||||||
|
description: 'Description of proposal 2',
|
||||||
|
proposer: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
|
||||||
|
votingDeadline: Date.now() + 432000000,
|
||||||
|
yesVotes: '198',
|
||||||
|
noVotes: '0',
|
||||||
|
status: 'active',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export singleton instance
|
||||||
|
export const blockchainService = new BlockchainService();
|
||||||
|
export default blockchainService;
|
||||||
|
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
/**
|
||||||
|
* KYC Service
|
||||||
|
* Handles Identity-KYC form submission, encryption, and blockchain interaction
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as SecureStore from 'expo-secure-store';
|
||||||
|
import { KYCFormData, KYCSubmission, KurdistanCitizen, KYCStatus } from '../types/kyc';
|
||||||
|
import { blockchainService } from './blockchain';
|
||||||
|
|
||||||
|
const KYC_DATA_KEY = 'pezkuwi_kyc_data';
|
||||||
|
const CITIZEN_DATA_KEY = 'pezkuwi_citizen_data';
|
||||||
|
const KYC_STATUS_KEY = 'pezkuwi_kyc_status';
|
||||||
|
|
||||||
|
class KYCService {
|
||||||
|
/**
|
||||||
|
* Generate hash from KYC data
|
||||||
|
* This hash will be submitted to blockchain
|
||||||
|
*/
|
||||||
|
private async generateHash(data: KYCFormData): Promise<string> {
|
||||||
|
// In production, use proper cryptographic hash (SHA-256)
|
||||||
|
const dataString = JSON.stringify(data);
|
||||||
|
|
||||||
|
// Simple hash for development (replace with proper crypto in production)
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < dataString.length; i++) {
|
||||||
|
const char = dataString.charCodeAt(i);
|
||||||
|
hash = (hash << 5) - hash + char;
|
||||||
|
hash = hash & hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `0x${Math.abs(hash).toString(16).padStart(64, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save KYC form data locally (encrypted)
|
||||||
|
*/
|
||||||
|
async saveKYCData(data: KYCFormData): Promise<void> {
|
||||||
|
try {
|
||||||
|
const encrypted = JSON.stringify(data);
|
||||||
|
await SecureStore.setItemAsync(KYC_DATA_KEY, encrypted);
|
||||||
|
console.log('✅ KYC data saved locally (encrypted)');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to save KYC data:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get saved KYC data
|
||||||
|
*/
|
||||||
|
async getKYCData(): Promise<KYCFormData | null> {
|
||||||
|
try {
|
||||||
|
const encrypted = await SecureStore.getItemAsync(KYC_DATA_KEY);
|
||||||
|
if (!encrypted) return null;
|
||||||
|
|
||||||
|
return JSON.parse(encrypted);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to get KYC data:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit KYC to blockchain
|
||||||
|
* Only hash is sent, not the actual data
|
||||||
|
*/
|
||||||
|
async submitKYC(data: KYCFormData, signer: any): Promise<KYCSubmission> {
|
||||||
|
try {
|
||||||
|
// 1. Save data locally first
|
||||||
|
await this.saveKYCData(data);
|
||||||
|
|
||||||
|
// 2. Generate hash
|
||||||
|
const dataHash = await this.generateHash(data);
|
||||||
|
console.log('📝 Generated KYC hash:', dataHash);
|
||||||
|
|
||||||
|
// 3. Submit hash to blockchain (identity-kyc pallet)
|
||||||
|
// TODO: Replace with actual blockchain call when testnet is active
|
||||||
|
const txHash = await this.submitHashToBlockchain(dataHash, signer);
|
||||||
|
|
||||||
|
const submission: KYCSubmission = {
|
||||||
|
dataHash,
|
||||||
|
submittedAt: Date.now(),
|
||||||
|
txHash,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4. Update KYC status
|
||||||
|
await this.updateKYCStatus({ started: true, submitted: true, approved: false });
|
||||||
|
|
||||||
|
console.log('✅ KYC submitted to blockchain');
|
||||||
|
return submission;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to submit KYC:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit hash to blockchain (identity-kyc pallet)
|
||||||
|
*/
|
||||||
|
private async submitHashToBlockchain(dataHash: string, signer: any): Promise<string> {
|
||||||
|
try {
|
||||||
|
// Check if blockchain is connected
|
||||||
|
if (!blockchainService.isApiConnected()) {
|
||||||
|
console.log('⚠️ Blockchain not connected, using mock submission');
|
||||||
|
// Mock transaction hash for development
|
||||||
|
return `0x${Math.random().toString(16).substr(2, 64)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Actual blockchain submission
|
||||||
|
// const api = blockchainService.getApi();
|
||||||
|
// const tx = api.tx.identityKyc.submitKyc(dataHash);
|
||||||
|
// const hash = await tx.signAndSend(signer);
|
||||||
|
// return hash.toString();
|
||||||
|
|
||||||
|
// Mock for now
|
||||||
|
return `0x${Math.random().toString(16).substr(2, 64)}`;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to submit to blockchain:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check KYC approval status on blockchain
|
||||||
|
*/
|
||||||
|
async checkApprovalStatus(address: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
// TODO: Query blockchain for approval status
|
||||||
|
// const api = blockchainService.getApi();
|
||||||
|
// const approval = await api.query.identityKyc.approvals(address);
|
||||||
|
// return approval.isSome;
|
||||||
|
|
||||||
|
// Mock: Auto-approve after 5 seconds (for development)
|
||||||
|
const status = await this.getKYCStatus();
|
||||||
|
if (status.submitted) {
|
||||||
|
const timeSinceSubmission = Date.now() - (status.citizen?.approvedAt || 0);
|
||||||
|
return timeSinceSubmission > 5000; // 5 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to check approval status:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Kurdistan Digital Citizen certificate
|
||||||
|
* Called after KYC is approved on blockchain
|
||||||
|
*/
|
||||||
|
async generateCitizen(data: KYCFormData, dataHash: string): Promise<KurdistanCitizen> {
|
||||||
|
try {
|
||||||
|
// Generate unique Citizen ID
|
||||||
|
const citizenId = `KRD-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
|
||||||
|
|
||||||
|
// Generate QR code data
|
||||||
|
const qrData = JSON.stringify({
|
||||||
|
citizenId,
|
||||||
|
name: data.fullName,
|
||||||
|
region: data.region,
|
||||||
|
hash: dataHash,
|
||||||
|
});
|
||||||
|
|
||||||
|
const citizen: KurdistanCitizen = {
|
||||||
|
citizenId,
|
||||||
|
fullName: data.fullName,
|
||||||
|
photo: data.photo || '',
|
||||||
|
region: data.region,
|
||||||
|
kycApproved: true,
|
||||||
|
approvedAt: Date.now(),
|
||||||
|
dataHash,
|
||||||
|
qrCode: qrData,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save citizen data locally (encrypted)
|
||||||
|
await SecureStore.setItemAsync(CITIZEN_DATA_KEY, JSON.stringify(citizen));
|
||||||
|
|
||||||
|
// Update KYC status
|
||||||
|
await this.updateKYCStatus({
|
||||||
|
started: true,
|
||||||
|
submitted: true,
|
||||||
|
approved: true,
|
||||||
|
citizen,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ Kurdistan Digital Citizen generated:', citizenId);
|
||||||
|
return citizen;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to generate citizen:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get citizen data
|
||||||
|
*/
|
||||||
|
async getCitizen(): Promise<KurdistanCitizen | null> {
|
||||||
|
try {
|
||||||
|
const data = await SecureStore.getItemAsync(CITIZEN_DATA_KEY);
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to get citizen data:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get KYC status
|
||||||
|
*/
|
||||||
|
async getKYCStatus(): Promise<KYCStatus> {
|
||||||
|
try {
|
||||||
|
const statusData = await SecureStore.getItemAsync(KYC_STATUS_KEY);
|
||||||
|
if (!statusData) {
|
||||||
|
return { started: false, submitted: false, approved: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(statusData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to get KYC status:', error);
|
||||||
|
return { started: false, submitted: false, approved: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update KYC status
|
||||||
|
*/
|
||||||
|
private async updateKYCStatus(status: KYCStatus): Promise<void> {
|
||||||
|
try {
|
||||||
|
await SecureStore.setItemAsync(KYC_STATUS_KEY, JSON.stringify(status));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to update KYC status:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user has access to Governance
|
||||||
|
*/
|
||||||
|
async hasGovernanceAccess(): Promise<boolean> {
|
||||||
|
const status = await this.getKYCStatus();
|
||||||
|
return status.approved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all KYC data (for testing)
|
||||||
|
*/
|
||||||
|
async clearKYCData(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await SecureStore.deleteItemAsync(KYC_DATA_KEY);
|
||||||
|
await SecureStore.deleteItemAsync(CITIZEN_DATA_KEY);
|
||||||
|
await SecureStore.deleteItemAsync(KYC_STATUS_KEY);
|
||||||
|
console.log('✅ KYC data cleared');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to clear KYC data:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const kycService = new KYCService();
|
||||||
|
export default kycService;
|
||||||
|
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
/**
|
||||||
|
* PezkuwiChain Mobile App - Type Definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// User Types
|
||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
address: string;
|
||||||
|
trustScore: number;
|
||||||
|
kycLevel: number;
|
||||||
|
avatar?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Balance Types
|
||||||
|
export interface Balance {
|
||||||
|
hez: string;
|
||||||
|
pez: string;
|
||||||
|
hezStaked: string;
|
||||||
|
hezUsd: string;
|
||||||
|
pezUsd: string;
|
||||||
|
governancePower: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction Types
|
||||||
|
export enum TransactionType {
|
||||||
|
SEND = 'send',
|
||||||
|
RECEIVE = 'receive',
|
||||||
|
STAKE = 'stake',
|
||||||
|
UNSTAKE = 'unstake',
|
||||||
|
REWARD = 'reward',
|
||||||
|
GOVERNANCE = 'governance',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Transaction {
|
||||||
|
id: string;
|
||||||
|
type: TransactionType;
|
||||||
|
amount: string;
|
||||||
|
token: 'HEZ' | 'PEZ';
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
timestamp: number;
|
||||||
|
blockNumber: number;
|
||||||
|
hash: string;
|
||||||
|
status: 'pending' | 'confirmed' | 'failed';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Governance Types
|
||||||
|
export interface Proposal {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
proposer: string;
|
||||||
|
votingDeadline: number;
|
||||||
|
yesVotes: string;
|
||||||
|
noVotes: string;
|
||||||
|
status: 'active' | 'passed' | 'rejected' | 'executed';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Vote {
|
||||||
|
proposalId: number;
|
||||||
|
voter: string;
|
||||||
|
vote: 'yes' | 'no';
|
||||||
|
amount: string;
|
||||||
|
conviction: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parliamentary NFT Types
|
||||||
|
export interface ParliamentaryNFT {
|
||||||
|
tokenId: number;
|
||||||
|
owner: string;
|
||||||
|
monthlySalary: string;
|
||||||
|
electionDate: number;
|
||||||
|
termEnd: number;
|
||||||
|
status: 'active' | 'inactive';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identity Types
|
||||||
|
export interface DigitalIdentity {
|
||||||
|
idNumber: string;
|
||||||
|
name: string;
|
||||||
|
photo: string;
|
||||||
|
trustScore: number;
|
||||||
|
kycLevel: number;
|
||||||
|
verifications: {
|
||||||
|
identity: boolean;
|
||||||
|
education: boolean;
|
||||||
|
kyc: boolean;
|
||||||
|
};
|
||||||
|
qrCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Education Certificate Types
|
||||||
|
export interface Certificate {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
institution: string;
|
||||||
|
issueDate: number;
|
||||||
|
certificateType: 'bachelor' | 'master' | 'phd' | 'diploma' | 'certificate';
|
||||||
|
verified: boolean;
|
||||||
|
nftId?: number;
|
||||||
|
qrCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Referral Types
|
||||||
|
export interface Referral {
|
||||||
|
code: string;
|
||||||
|
totalReferrals: number;
|
||||||
|
activeReferrals: number;
|
||||||
|
rewardsEarned: string;
|
||||||
|
referrals: ReferralUser[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReferralUser {
|
||||||
|
name: string;
|
||||||
|
avatar: string;
|
||||||
|
joinDate: number;
|
||||||
|
reward: string;
|
||||||
|
status: 'active' | 'inactive';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Business Types
|
||||||
|
export interface MerchantDashboard {
|
||||||
|
monthlyRevenue: string;
|
||||||
|
transactions: number;
|
||||||
|
customers: number;
|
||||||
|
recentTransactions: MerchantTransaction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MerchantTransaction {
|
||||||
|
customerName: string;
|
||||||
|
amount: string;
|
||||||
|
token: 'HEZ' | 'PEZ';
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange Types
|
||||||
|
export interface ExchangeRate {
|
||||||
|
from: 'HEZ' | 'PEZ';
|
||||||
|
to: 'HEZ' | 'PEZ';
|
||||||
|
rate: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SwapTransaction {
|
||||||
|
fromToken: 'HEZ' | 'PEZ';
|
||||||
|
toToken: 'HEZ' | 'PEZ';
|
||||||
|
fromAmount: string;
|
||||||
|
toAmount: string;
|
||||||
|
rate: number;
|
||||||
|
slippage: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigation Types
|
||||||
|
export type RootStackParamList = {
|
||||||
|
LanguageSelection: undefined;
|
||||||
|
SignIn: undefined;
|
||||||
|
SignUp: undefined;
|
||||||
|
MainTabs: undefined;
|
||||||
|
Send: { token: 'HEZ' | 'PEZ' };
|
||||||
|
Receive: { token: 'HEZ' | 'PEZ' };
|
||||||
|
ProposalDetail: { proposalId: number };
|
||||||
|
CertificateDetail: { certificateId: string };
|
||||||
|
QRScanner: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MainTabsParamList = {
|
||||||
|
Home: undefined;
|
||||||
|
Wallet: undefined;
|
||||||
|
Governance: undefined;
|
||||||
|
Referral: undefined;
|
||||||
|
Profile: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Blockchain Types
|
||||||
|
export interface ChainConfig {
|
||||||
|
name: string;
|
||||||
|
rpcUrl: string;
|
||||||
|
chainId: string;
|
||||||
|
genesisHash: string;
|
||||||
|
decimals: {
|
||||||
|
hez: number;
|
||||||
|
pez: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Response Types
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
success: boolean;
|
||||||
|
data?: T;
|
||||||
|
error?: string;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* Identity KYC Types
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Region = 'basur' | 'bakur' | 'rojava' | 'rojhelat' | 'kurdistan_a_sor' | 'diaspora';
|
||||||
|
|
||||||
|
export type MaritalStatus = 'single' | 'married';
|
||||||
|
|
||||||
|
export interface Child {
|
||||||
|
name: string;
|
||||||
|
order: number; // 1st child, 2nd child, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KYCFormData {
|
||||||
|
// Personal Information
|
||||||
|
fullName: string;
|
||||||
|
fatherName: string;
|
||||||
|
grandfatherName: string;
|
||||||
|
greatGrandfatherName: string;
|
||||||
|
motherName: string;
|
||||||
|
|
||||||
|
// Marital Status
|
||||||
|
maritalStatus: MaritalStatus;
|
||||||
|
spouseName?: string;
|
||||||
|
numberOfChildren?: number;
|
||||||
|
children?: Child[];
|
||||||
|
|
||||||
|
// Region
|
||||||
|
region: Region;
|
||||||
|
|
||||||
|
// Photo (base64 or file URI)
|
||||||
|
photo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KYCSubmission {
|
||||||
|
// Hash of the KYC data (sent to blockchain)
|
||||||
|
dataHash: string;
|
||||||
|
|
||||||
|
// Timestamp
|
||||||
|
submittedAt: number;
|
||||||
|
|
||||||
|
// Blockchain transaction hash
|
||||||
|
txHash?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KurdistanCitizen {
|
||||||
|
// Citizen ID (generated after KYC approval)
|
||||||
|
citizenId: string;
|
||||||
|
|
||||||
|
// Personal Info (stored locally, encrypted)
|
||||||
|
fullName: string;
|
||||||
|
photo: string;
|
||||||
|
region: Region;
|
||||||
|
|
||||||
|
// KYC Status
|
||||||
|
kycApproved: boolean;
|
||||||
|
approvedAt: number;
|
||||||
|
|
||||||
|
// Blockchain reference (only hash)
|
||||||
|
dataHash: string;
|
||||||
|
|
||||||
|
// QR Code for verification
|
||||||
|
qrCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KYCStatus {
|
||||||
|
// Has user started KYC?
|
||||||
|
started: boolean;
|
||||||
|
|
||||||
|
// Has user submitted KYC?
|
||||||
|
submitted: boolean;
|
||||||
|
|
||||||
|
// Is KYC approved on blockchain?
|
||||||
|
approved: boolean;
|
||||||
|
|
||||||
|
// Citizen data (only if approved)
|
||||||
|
citizen?: KurdistanCitizen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region labels for UI
|
||||||
|
export const REGION_LABELS: Record<Region, { en: string; ku: string }> = {
|
||||||
|
basur: { en: 'Başur (South Kurdistan)', ku: 'باشوور (کوردستانی باشوور)' },
|
||||||
|
bakur: { en: 'Bakur (North Kurdistan)', ku: 'باکوور (کوردستانی باکوور)' },
|
||||||
|
rojava: { en: 'Rojava (West Kurdistan)', ku: 'رۆژاڤا (کوردستانی رۆژاڤا)' },
|
||||||
|
rojhelat: { en: 'Rojhelat (East Kurdistan)', ku: 'رۆژهەڵات (کوردستانی رۆژهەڵات)' },
|
||||||
|
kurdistan_a_sor: { en: 'Kurdistan a Sor (Red Kurdistan)', ku: 'کوردستانا سور' },
|
||||||
|
diaspora: { en: 'Diaspora', ku: 'دیاسپۆرا' },
|
||||||
|
};
|
||||||
|
|
||||||
Reference in New Issue
Block a user