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": {
|
||||
"name": "frontend",
|
||||
"slug": "frontend",
|
||||
"name": "pezkuwi-mobile-app",
|
||||
"slug": "pezkuwi-mobile-app",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"scheme": "frontend",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"newArchEnabled": true,
|
||||
"splash": {
|
||||
"image": "./assets/splash-icon.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#000"
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"edgeToEdgeEnabled": true
|
||||
"edgeToEdgeEnabled": true,
|
||||
"predictiveBackGestureEnabled": false
|
||||
},
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
"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
|
||||
"favicon": "./assets/favicon.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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