diff --git a/frontend/App.tsx b/frontend/App.tsx new file mode 100644 index 00000000..e4701509 --- /dev/null +++ b/frontend/App.tsx @@ -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 ( + <> + + + + ); +} diff --git a/frontend/app.json b/frontend/app.json index 0fa96f1e..8ef17a67 100644 --- a/frontend/app.json +++ b/frontend/app.json @@ -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" } } } diff --git a/frontend/app/index.tsx b/frontend/app/index.tsx deleted file mode 100644 index 60b6cda3..00000000 --- a/frontend/app/index.tsx +++ /dev/null @@ -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 ( - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: "#0c0c0c", - alignItems: "center", - justifyContent: "center", - }, - image: { - width: "100%", - height: "100%", - resizeMode: "contain", - }, -}); diff --git a/frontend/assets/adaptive-icon.png b/frontend/assets/adaptive-icon.png new file mode 100644 index 00000000..03d6f6b6 Binary files /dev/null and b/frontend/assets/adaptive-icon.png differ diff --git a/frontend/assets/favicon.png b/frontend/assets/favicon.png new file mode 100644 index 00000000..e75f697b Binary files /dev/null and b/frontend/assets/favicon.png differ diff --git a/frontend/assets/icon.png b/frontend/assets/icon.png new file mode 100644 index 00000000..a0b1526f Binary files /dev/null and b/frontend/assets/icon.png differ diff --git a/frontend/assets/splash-icon.png b/frontend/assets/splash-icon.png new file mode 100644 index 00000000..03d6f6b6 Binary files /dev/null and b/frontend/assets/splash-icon.png differ diff --git a/frontend/assets/tokens/hez.png b/frontend/assets/tokens/hez.png new file mode 100644 index 00000000..c36e353a Binary files /dev/null and b/frontend/assets/tokens/hez.png differ diff --git a/frontend/assets/tokens/pez.png b/frontend/assets/tokens/pez.png new file mode 100644 index 00000000..15e2b9b6 Binary files /dev/null and b/frontend/assets/tokens/pez.png differ diff --git a/frontend/index.ts b/frontend/index.ts new file mode 100644 index 00000000..1d6e981e --- /dev/null +++ b/frontend/index.ts @@ -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); diff --git a/frontend/src/components/ReceiveModal.tsx b/frontend/src/components/ReceiveModal.tsx new file mode 100644 index 00000000..5ae96243 --- /dev/null +++ b/frontend/src/components/ReceiveModal.tsx @@ -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 ( + + + + {/* Close Button */} + + + + + {/* Token Icon */} + + + {/* Title */} + Receive {token.symbol} + + {/* QR Code Card */} + + + + + {/* Wallet Address */} + + {walletAddress} + + + Tap to copy + + {/* Network Badge */} + + + {network} Network + + + {/* Share Button */} + + + Share Address + + + {/* Warning Box */} + + + + Only send {token.symbol} to this address on {network} network + + + + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/components/SendModal.tsx b/frontend/src/components/SendModal.tsx new file mode 100644 index 00000000..62cdfd9d --- /dev/null +++ b/frontend/src/components/SendModal.tsx @@ -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 ( + + + + {/* Close Button */} + + + + + {/* Token Icon */} + + + {/* Title */} + Send {token.symbol} + + {/* Available Balance */} + + Available Balance: {token.balance} {token.symbol} + + + {/* Recipient Address */} + Recipient Address + + + + + + + + + + + {/* Amount */} + Amount + + + + MAX + + + USD Value: ${usdValue.toFixed(2)} + + {/* Network */} + + Network: + + {network} + + + + + {/* Transaction Fee */} + + Fee: 0.001 HEZ (~$0.01) + + + {/* Send Button */} + + Send {token.symbol} + + + + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/constants/blockchain.ts b/frontend/src/constants/blockchain.ts new file mode 100644 index 00000000..56b37cb9 --- /dev/null +++ b/frontend/src/constants/blockchain.ts @@ -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', +}; + diff --git a/frontend/src/constants/colors.ts b/frontend/src/constants/colors.ts new file mode 100644 index 00000000..5a87bc95 --- /dev/null +++ b/frontend/src/constants/colors.ts @@ -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; + diff --git a/frontend/src/constants/theme.ts b/frontend/src/constants/theme.ts new file mode 100644 index 00000000..3101e58b --- /dev/null +++ b/frontend/src/constants/theme.ts @@ -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, +}; + diff --git a/frontend/src/navigation/BottomTabNavigator.tsx b/frontend/src/navigation/BottomTabNavigator.tsx new file mode 100644 index 00000000..67b65e87 --- /dev/null +++ b/frontend/src/navigation/BottomTabNavigator.tsx @@ -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 ( + ({ + 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 ; + }, + 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, + })} + > + + + + + + + ); +} + diff --git a/frontend/src/navigation/RootNavigator.tsx b/frontend/src/navigation/RootNavigator.tsx new file mode 100644 index 00000000..714f5204 --- /dev/null +++ b/frontend/src/navigation/RootNavigator.tsx @@ -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 ( + + + {/* Auth Flow */} + + + + {/* Main App */} + + + {/* Identity & KYC */} + + + + {/* Additional Screens */} + + + + + + + ); +} + +// Add Ministries screen to imports and stack diff --git a/frontend/src/screens/Auth/LanguageSelectionScreen.tsx b/frontend/src/screens/Auth/LanguageSelectionScreen.tsx new file mode 100644 index 00000000..8f5d2774 --- /dev/null +++ b/frontend/src/screens/Auth/LanguageSelectionScreen.tsx @@ -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('en'); + + const handleContinue = () => { + // Save language preference + // TODO: Implement i18n language switching + navigation.navigate('SignUp'); + }; + + return ( + + + + + {/* Header */} + + Welcome to + PezkuwiChain + Your Digital Citizenship Platform + + Building the future of Kurdish digital infrastructure + + + + {/* Language Selection */} + + Select Your Language + + + {LANGUAGES.map((language) => ( + setSelectedLanguage(language.code)} + activeOpacity={0.7} + > + + {language.nativeName} + + + ))} + + + + {/* Continue Button */} + + Get Started → + + + + + ); +} + +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', + }, +}); + diff --git a/frontend/src/screens/Auth/SignUpScreen.tsx b/frontend/src/screens/Auth/SignUpScreen.tsx new file mode 100644 index 00000000..fff9ba06 --- /dev/null +++ b/frontend/src/screens/Auth/SignUpScreen.tsx @@ -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 ( + + + + {/* Header */} + + PezkuwiChain + Access your governance account + + + {/* Toggle Buttons */} + + setIsSignUp(false)} + > + + Sign In + + + setIsSignUp(true)} + > + + Sign Up + + + + + {/* Form */} + + {isSignUp && ( + + Full Name + + + setFormData({ ...formData, fullName: text })} + /> + + + )} + + + Email + + + setFormData({ ...formData, email: text })} + /> + + + + + Password + + + setFormData({ ...formData, password: text })} + /> + setShowPassword(!showPassword)}> + + + + + + {isSignUp && ( + <> + + Confirm Password + + + + setFormData({ ...formData, confirmPassword: text }) + } + /> + setShowConfirmPassword(!showConfirmPassword)} + > + + + + + + + Referral Code (Optional) + + + + setFormData({ ...formData, referralCode: text }) + } + /> + + + + )} + + {!isSignUp && ( + + Forgot Password? + + )} + + {/* Submit Button */} + + + {isSignUp ? 'Create Account' : 'Sign In'} + + + + {/* Footer */} + + + {isSignUp ? 'Already have an account?' : "Don't have an account?"} + + + + {isSignUp ? 'Sign In' : 'Sign Up'} + + + + + + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/screens/Business/BusinessScreen.tsx b/frontend/src/screens/Business/BusinessScreen.tsx new file mode 100644 index 00000000..ce34fb4e --- /dev/null +++ b/frontend/src/screens/Business/BusinessScreen.tsx @@ -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({ + totalSales: 125450, + todaySales: 3250, + pendingPayments: 850, + totalCustomers: 342, + }); + + const [transactions] = useState([ + { + 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 ( + + + {/* Header */} + + + + Business Hub + Merchant Payment Center + + Alert.alert('Settings', 'Business settings coming soon')} + > + + + + + + {/* Statistics Grid */} + + + + + {stats.totalSales.toLocaleString()} + Total Sales (PEZ) + + + + {stats.todaySales.toLocaleString()} + Today's Sales + + + + {stats.pendingPayments.toLocaleString()} + Pending + + + + {stats.totalCustomers} + Customers + + + + + {/* Quick Actions */} + + Quick Actions + + + + + + Generate QR + + + + + + Create Invoice + + + + + + Analytics + + + + + + Payment Link + + + + + {/* Payment Request Form */} + + Create Payment Request + + + Amount + + + + + Token + + setSelectedToken('PEZ')} + > + + PEZ + + + setSelectedToken('HEZ')} + > + + HEZ + + + + + + + Note (Optional) + + + + + + + Generate Payment QR + + + + + + {/* Recent Transactions */} + + + Recent Transactions + + View All + + + + {transactions.map((transaction) => ( + + + + + + + + {transaction.customer} + + {transaction.date} + + {transaction.invoiceId} + + + + + {transaction.amount.toLocaleString()} + + {transaction.token} + + + + + {transaction.status.charAt(0).toUpperCase() + + transaction.status.slice(1)} + + + + ))} + + + {/* Merchant Info */} + + Merchant Information + + + + Your Merchant Address + + + + {merchantAddress} + + + + + Share this address with customers to receive payments + + + + + + + + {/* Payment QR Modal */} + setShowPaymentModal(false)} + > + + + + Payment QR Code + setShowPaymentModal(false)}> + + + + + + + + + + + Amount: + + {paymentAmount} {selectedToken} + + + {paymentNote ? ( + + Note: + {paymentNote} + + ) : null} + + + + Ask customer to scan this QR code with their PezkuwiChain app + + + setShowPaymentModal(false)} + > + Done + + + + + + ); +} + +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', + }, +}); + diff --git a/frontend/src/screens/Education/EducationScreen.tsx b/frontend/src/screens/Education/EducationScreen.tsx new file mode 100644 index 00000000..6ace9549 --- /dev/null +++ b/frontend/src/screens/Education/EducationScreen.tsx @@ -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({ + totalCertificates: 8, + verifiedCertificates: 6, + skillsAcquired: 15, + learningHours: 240, + }); + + const [certificates] = useState([ + { + 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 ( + + + {/* Header */} + + + + Perwerde + Education & Certificates + + + Alert.alert( + 'Add Certificate', + 'Request your institution to issue a certificate on PezkuwiChain', + [{ text: 'OK' }] + ) + } + > + + + + + + {/* Statistics Grid */} + + + + + {stats.totalCertificates} + Certificates + + + + {stats.verifiedCertificates} + Verified + + + + {stats.skillsAcquired} + Skills + + + + {stats.learningHours} + Hours + + + + + {/* Certificates Section */} + + + My Certificates + + View All + + + + {certificates.map((certificate) => ( + verifyCertificate(certificate)} + activeOpacity={0.7} + > + + + + + + + {certificate.title} + + + {certificate.institution} + + + Issued: {certificate.issueDate} + + + + + + + + + {certificate.certificateId} + + + + {certificate.verified && ( + + + Verified + + )} + shareCertificate(certificate)} + style={styles.shareButton} + > + + + + + + ))} + + + {/* Learning Paths */} + + Recommended Learning Paths + + + + + + + + + + Blockchain Developer Path + + + Master Substrate & Polkadot development + + + + + 8 Courses + + + + 120 Hours + + + + + + + + + + + + + + + + Kurdish Culture & Heritage + + Learn Kurdish language and history + + + + + 5 Courses + + + + 60 Hours + + + + + + + + + + {/* Info Section */} + + + + + All certificates are stored on PezkuwiChain blockchain and IPFS, + ensuring permanent verification and authenticity. + + + + + + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/screens/Exchange/ExchangeScreen.tsx b/frontend/src/screens/Exchange/ExchangeScreen.tsx new file mode 100644 index 00000000..00363ffe --- /dev/null +++ b/frontend/src/screens/Exchange/ExchangeScreen.tsx @@ -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({ + hezToPez: 27.5, + pezToHez: 0.0364, + lastUpdated: new Date().toLocaleTimeString(), + }); + + const [swapHistory] = useState([ + { + 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 ( + + + {/* Header */} + + + + Exchange + Swap HEZ ↔ PEZ Tokens + + Alert.alert('History', 'Full swap history coming soon')} + > + + + + + + {/* Exchange Rate Card */} + + + + Current Exchange Rate + + + Live + + + + + 1 HEZ = + {exchangeRate.hezToPez} PEZ + + + + 1 PEZ = + {exchangeRate.pezToHez} HEZ + + + + Last updated: {exchangeRate.lastUpdated} + + + + + {/* Swap Interface */} + + Swap Tokens + + {/* From Token */} + + + From + + Balance: {fromToken === 'HEZ' ? balances.hez.toLocaleString() : balances.pez.toLocaleString()} {fromToken} + + + + + + + {fromToken} + + + MAX + + + + + + {/* Swap Button */} + + + + + + + {/* To Token */} + + + To + + Balance: {toToken === 'HEZ' ? balances.hez.toLocaleString() : balances.pez.toLocaleString()} {toToken} + + + + + + + {toToken} + + + + + + {/* Swap Details */} + {fromAmount && toAmount && ( + + + Rate + + 1 {fromToken} = {getCurrentRate()} {toToken} + + + + Slippage Tolerance + + setSlippage('0.5')} + > + + 0.5% + + + setSlippage('1.0')} + > + + 1.0% + + + setSlippage('2.0')} + > + + 2.0% + + + + + + Network Fee + ~0.01 HEZ + + + )} + + {/* Execute Swap Button */} + + + + Swap Tokens + + + + + {/* Recent Swaps */} + + + Recent Swaps + + View All + + + + {swapHistory.map((swap) => ( + + + + + + + + {swap.fromToken} → {swap.toToken} + + {swap.date} + Tx: {swap.txHash} + + + + -{swap.fromAmount} {swap.fromToken} + + + +{swap.toAmount} {swap.toToken} + + + + + + Rate: 1 {swap.fromToken} = {swap.rate} {swap.toToken} + + + + + + + ))} + + + {/* Info Section */} + + + + + About Token Swap + + 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. + + + + + + + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/screens/Governance/GovernanceScreen.tsx b/frontend/src/screens/Governance/GovernanceScreen.tsx new file mode 100644 index 00000000..fa4202fb --- /dev/null +++ b/frontend/src/screens/Governance/GovernanceScreen.tsx @@ -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 ( + + + Loading... + + + ); + } + + if (!isHemwelati) { + return ( + + + + Access Restricted + + Governance features are only available to verified Hemwelatî (Digital Citizens). + + + Complete your Identity KYC verification to access: + + + • Vote on proposals + • Participate in governance + • Access government services + • View parliamentary sessions + + navigation.navigate('Identity')} + > + + Complete Identity KYC + + + + ); + } + + return ( + + {/* Header */} + + + Welati Governance + Komara Kurdistanê + + + + Verified Citizen + + + + + {/* Services Grid */} + + {services.map((service) => ( + handleServicePress(service)} + activeOpacity={0.7} + > + + + + {service.name} + {service.nameKu} + + ))} + + + {/* Info Footer */} + + + + As a verified Hemwelatî, you have full access to all governance features. + + + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/screens/Governance/GovernanceScreen_old.tsx b/frontend/src/screens/Governance/GovernanceScreen_old.tsx new file mode 100644 index 00000000..e03aaa65 --- /dev/null +++ b/frontend/src/screens/Governance/GovernanceScreen_old.tsx @@ -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 ( + + + + ); + } + + // If user doesn't have access (KYC not approved) + if (!hasAccess) { + return ( + + + Governance (Welati) + + + + + + Access Restricted + + You need to complete Identity KYC verification to access Governance features. + + + + + Requirements: + + + Complete Identity KYC form + + + + Get KYC approval on blockchain + + + + Receive Kurdistan Digital Citizen card + + + + navigation.navigate('Identity')} + > + Complete Identity KYC + + + + + ); + } + + // If user has access, show governance features + return ( + + + Governance (Welati) + + + Verified Citizen + + + + + Active Proposals + Coming soon... + + + ); +} + +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 }, +}); diff --git a/frontend/src/screens/Governance/MinistriesScreen.tsx b/frontend/src/screens/Governance/MinistriesScreen.tsx new file mode 100644 index 00000000..a047cee4 --- /dev/null +++ b/frontend/src/screens/Governance/MinistriesScreen.tsx @@ -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 ( + + {/* Header */} + + navigation.goBack()} style={styles.backButton}> + + + + Ministries + Wezaretî + + + + + {/* Ministries Grid */} + + {ministries.map((ministry) => ( + handleMinistryPress(ministry)} + activeOpacity={0.7} + > + + + + {ministry.name} + {ministry.nameKu} + {ministry.minister && ( + {ministry.minister} + )} + + ))} + + + {/* Info Footer */} + + + + Cabinet of {ministries.length} ministries serving the people of Kurdistan. + + + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/screens/Home/HomeScreen.tsx b/frontend/src/screens/Home/HomeScreen.tsx new file mode 100644 index 00000000..7b5f20b8 --- /dev/null +++ b/frontend/src/screens/Home/HomeScreen.tsx @@ -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(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 ( + + + {/* Header */} + + + {/* Profile Avatar */} + navigation.navigate('Profile')} + activeOpacity={0.7} + > + + + + + {/* Trust Score Badge */} + navigation.navigate('TrustScore')} + activeOpacity={0.7} + > + + {trustScore} + + + + {/* Action Icons */} + + navigation.navigate('QRScanner')} + > + + + + navigation.navigate('Notifications')} + > + + + + navigation.navigate('Profile')} + > + + + + + + + {/* Balance Card */} + + navigation.navigate('Wallet')} + activeOpacity={0.8} + > + + navigation.navigate('Wallet', { tab: 'HEZ' })} + activeOpacity={0.7} + > + HEZ Balance + {balance?.hez || '0'} + + + + navigation.navigate('Wallet', { tab: 'PEZ' })} + activeOpacity={0.7} + > + PEZ Balance + {balance?.pez || '0'} + + + + + + + {/* Quick Actions */} + + Quick Actions + + + {quickActions.map((action) => ( + + + + + {action.label} + + ))} + + + + + ); +} + +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', + }, +}); + diff --git a/frontend/src/screens/Home/NotificationsScreen.tsx b/frontend/src/screens/Home/NotificationsScreen.tsx new file mode 100644 index 00000000..94ade090 --- /dev/null +++ b/frontend/src/screens/Home/NotificationsScreen.tsx @@ -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([]); + + 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 ( + + + navigation.goBack()} style={styles.backButton}> + + + Notifications + {unreadCount > 0 && ( + + {unreadCount} + + )} + + + + {loading ? ( + + ) : notifications.length === 0 ? ( + + + No notifications yet + + ) : ( + notifications.map((notification) => ( + markAsRead(notification.id)} + activeOpacity={0.7} + > + + + + + + + {notification.title} + {!notification.read && } + + {notification.message} + {formatTime(notification.timestamp)} + + + )) + )} + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/screens/Identity/CitizenCardScreen.tsx b/frontend/src/screens/Identity/CitizenCardScreen.tsx new file mode 100644 index 00000000..a559a7fb --- /dev/null +++ b/frontend/src/screens/Identity/CitizenCardScreen.tsx @@ -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(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 ( + + + + ); + } + + if (!citizen) { + return ( + + No citizen data found + navigation.navigate('IdentityKYCForm')} + > + Complete KYC + + + ); + } + + const formatDate = (timestamp: number) => { + const date = new Date(timestamp); + return date.toLocaleDateString('en-GB'); + }; + + return ( + + + navigation.goBack()} style={styles.backButton}> + + + Digital Hemwelatî + + + + + + + {/* Official Citizen Card */} + + {/* Burgundy Header */} + + KOMARA KURDISTANÊ + HEMWELATÎ + + + {/* Main Content - White Background */} + + {/* Photo and Info Section */} + + {/* Photo */} + + {citizen.photo ? ( + + ) : ( + + + + )} + + + {/* Info List */} + + + NAV/NAME + {citizen.fullName} + + + {citizen.fatherName && ( + + BAV/FATHER + {citizen.fatherName} + + )} + + {citizen.grandfatherName && ( + + DAPÎR/GRANDFATHER + {citizen.grandfatherName} + + )} + + {citizen.greatGrandfatherName && ( + + DAPÎRA BIRA/G.GRANDFATHER + {citizen.greatGrandfatherName} + + )} + + {citizen.motherName && ( + + DAY/MOTHER + {citizen.motherName} + + )} + + + ZEWICÎN/MARITAL + + {citizen.maritalStatus === 'married' ? 'Zewicî' : 'Nezewicî'} + + + + {citizen.maritalStatus === 'married' && citizen.spouseName && ( + + HEVJÎN/SPOUSE + {citizen.spouseName} + + )} + + {citizen.children && citizen.children.length > 0 && ( + + ZAROK/CHILDREN + {citizen.children.length} + + )} + + + + {/* PEZ Sun Logo */} + + + + + {/* Region and Citizen ID */} + + + HEREM/REGION + {REGION_LABELS[citizen.region].en} + + + + JIMARA HEMWELATÎ/CITIZEN ID + {citizen.citizenId} + + + + + {/* Green Footer */} + + + + NASNAMA DIJÎTAL/DIGITAL ID + {citizen.citizenId.replace(/-/g, '')} + + + + JIMARA VEKIRAN/ACCOUNT# + {citizen.qrCode.substring(0, 12)} + + + + DAXWAZ/ISSUED + {formatDate(citizen.approvedAt)} + + + + + + + + + + {/* Status Badge */} + + + Verified Kurdistan Citizen + + + {/* Info Text */} + + This official digital citizenship card grants you full access to Governance (Welati) and all citizen-only features on PezkuwiChain. + + + {/* Action Buttons */} + + + + Download + + + + + Show QR + + + + + Share + + + + + ); +} + +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', + }, +}); + diff --git a/frontend/src/screens/Identity/IdentityKYCFormScreen.tsx b/frontend/src/screens/Identity/IdentityKYCFormScreen.tsx new file mode 100644 index 00000000..09057091 --- /dev/null +++ b/frontend/src/screens/Identity/IdentityKYCFormScreen.tsx @@ -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({ + 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) => ( + + {index + 1}. Child's Name + + + updateChild(index, text)} + /> + + + )); + }; + + return ( + + + navigation.goBack()} style={styles.backButton}> + + + Identity KYC + + + + + + + + Complete this form to become a verified Kurdistan Digital Citizen and access Governance features. + + + + {/* Personal Information */} + Personal Information + + + Full Name * + + + updateField('fullName', text)} + /> + + + + + Father's Name * + + + updateField('fatherName', text)} + /> + + + + + Grandfather's Name + + + updateField('grandfatherName', text)} + /> + + + + + Great-Grandfather's Name + + + updateField('greatGrandfatherName', text)} + /> + + + + + Mother's Name * + + + updateField('motherName', text)} + /> + + + + {/* Marital Status */} + Marital Status + + + updateField('maritalStatus', 'single')} + > + + Single + + + updateField('maritalStatus', 'married')} + > + + Married + + + + {formData.maritalStatus === 'married' && ( + <> + + Spouse's Name * + + + updateField('spouseName', text)} + /> + + + + + Number of Children + + + { + const num = parseInt(text) || 0; + updateField('numberOfChildren', num); + if (num > 0) { + updateField('children', Array(num).fill({ name: '', order: 0 })); + } + }} + /> + + + + {renderChildInputs()} + + )} + + {/* Region */} + Region + + + Select Your Region * + + updateField('region', value)} + style={styles.picker} + > + {Object.entries(REGION_LABELS).map(([key, value]) => ( + + ))} + + + + + {/* Submit Button */} + + {loading ? ( + + ) : ( + <> + Submit KYC Application + + + )} + + + + + + ); +} + +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', + }, +}); + diff --git a/frontend/src/screens/Identity/IdentityScreen.tsx b/frontend/src/screens/Identity/IdentityScreen.tsx new file mode 100644 index 00000000..d1749f7c --- /dev/null +++ b/frontend/src/screens/Identity/IdentityScreen.tsx @@ -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({ + 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 ( + + + + ); + } + + // If KYC approved, show citizen card access + if (kycStatus.approved && kycStatus.citizen) { + return ( + + + Digital Identity + + + + + + KYC Approved + You are a verified Kurdistan Digital Citizen + + + navigation.navigate('CitizenCard')} + > + + View Citizen Card + + + + + + Your citizen card grants you access to Governance (Welati) and other citizen-only features. + + + + + ); + } + + // If KYC submitted but not approved + if (kycStatus.submitted && !kycStatus.approved) { + return ( + + + Digital Identity + + + + + + KYC Under Review + + Your application is being reviewed. You will be notified once approved. + + + + + + Check Status + + + + ); + } + + // If KYC not started + return ( + + + Digital Identity + + + + + + Become a Digital Citizen + + Complete KYC verification to access Governance (Welati) and become a verified Kurdistan Digital Citizen. + + + + + Benefits: + + + Vote on governance proposals + + + + Access Parliamentary NFT elections + + + + Digital citizenship certificate + + + + Verified trust score + + + + navigation.navigate('IdentityKYCForm')} + > + Start KYC Verification + + + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/screens/Profile/ProfileScreen.tsx b/frontend/src/screens/Profile/ProfileScreen.tsx new file mode 100644 index 00000000..79fdedf1 --- /dev/null +++ b/frontend/src/screens/Profile/ProfileScreen.tsx @@ -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) => ( + + + + + + + {item.title} + {item.subtitle && {item.subtitle}} + + + {item.showArrow && ( + + )} + + {item.showSwitch && ( + { + if (item.id === 'notifications') { + setNotificationsEnabled(value); + } else if (item.id === 'biometric') { + setBiometricEnabled(value); + } + }} + trackColor={{ false: '#D0D0D0', true: Colors.teal }} + thumbColor="#FFFFFF" + /> + )} + + ); + + return ( + + + {/* Profile Header */} + + + + + + + + Satoshi Qazi Muhammed + satoshi@pezkuwichain.io + + + Alert.alert('Edit Profile', 'Coming soon')} + > + + + + + {/* Stats */} + + + 750 + Trust Score + + + + + + 24 + Referrals + + + + + + 5 + Certificates + + + + + {/* Account Section */} + + Account + {accountMenuItems.map(renderMenuItem)} + + + {/* Security Section */} + + Security + {securityMenuItems.map(renderMenuItem)} + + + {/* Preferences Section */} + + Preferences + {preferencesMenuItems.map(renderMenuItem)} + + + {/* Support Section */} + + Support + {supportMenuItems.map(renderMenuItem)} + + + {/* Logout Button */} + + + Logout + + + + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/screens/Profile/ProfileScreen_old.tsx b/frontend/src/screens/Profile/ProfileScreen_old.tsx new file mode 100644 index 00000000..13e6a50a --- /dev/null +++ b/frontend/src/screens/Profile/ProfileScreen_old.tsx @@ -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 ( + + Profile + Coming soon... + + ); +} + +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 }, +}); diff --git a/frontend/src/screens/Profile/TrustScoreScreen.tsx b/frontend/src/screens/Profile/TrustScoreScreen.tsx new file mode 100644 index 00000000..a99439fa --- /dev/null +++ b/frontend/src/screens/Profile/TrustScoreScreen.tsx @@ -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([]); + + 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 ( + + + navigation.goBack()} style={styles.backButton}> + + + Trust Score + + + + + {/* Trust Score Card */} + + + {trustScore} + Your Trust Score + + + 🏆 Gold Tier + + + + {/* Benefits */} + + Benefits + + + + + Higher PEZ Rewards + + Earn 1.5x more PEZ rewards from staking + + + + + + + + Validator Priority + + Higher chance of being selected as validator + + + + + + + + Governance Weight + + Your votes carry more weight in proposals + + + + + + {/* Recent Activities */} + + Recent Activities + + {loading ? ( + + ) : ( + activities.map((activity) => ( + + + + + + + {activity.description} + {formatDate(activity.timestamp)} + + + + +{activity.points} + points + + + )) + )} + + + {/* How to Increase */} + + How to Increase Trust Score + + + ✓ Vote on governance proposals + + + ✓ Stake HEZ tokens + + + ✓ Create valuable proposals + + + ✓ Refer new citizens + + + ✓ Maintain long-term participation + + + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/screens/Referral/ReferralScreen.tsx b/frontend/src/screens/Referral/ReferralScreen.tsx new file mode 100644 index 00000000..40464d88 --- /dev/null +++ b/frontend/src/screens/Referral/ReferralScreen.tsx @@ -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({ + totalReferrals: 12, + activeReferrals: 8, + totalRewards: 2450, + pendingRewards: 350, + }); + + const [referrals] = useState([ + { + 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 ( + + + {/* Header */} + + Referral Program + + Invite friends and earn rewards together + + + + {/* Referral Code Card */} + + Your Referral Code + + + + + {referralCode} + + + + + Copy Code + + + + Share + + + + + {/* Statistics Grid */} + + + + {stats.totalReferrals} + Total Referrals + + + + {stats.activeReferrals} + Active + + + + {stats.totalRewards} + Total Rewards (PEZ) + + + + {stats.pendingRewards} + Pending (PEZ) + + + + {/* Reward Tiers */} + + Reward Tiers + + + + Tier 1 + + Direct Referrals + 500 PEZ + + + + Tier 2 + + 2nd Level Referrals + 250 PEZ + + + + Tier 3 + + 3rd Level Referrals + 100 PEZ + + + + + {/* Referral List */} + + Your Referrals + {referrals.map((referral) => ( + + + + + {referral.name.charAt(0)} + + + + {referral.name} + + Joined: {referral.joinDate} + + + + + {referral.status.charAt(0).toUpperCase() + + referral.status.slice(1)} + + + + + + + Tier {referral.tier} + + + Earned: {referral.rewardEarned} PEZ + + + + ))} + + + {/* How It Works */} + + How It Works + + + + 1 + + + Share your unique referral code with friends + + + + + 2 + + + They sign up and complete KYC verification + + + + + 3 + + + Both of you earn PEZ rewards based on tier level + + + + + 4 + + + Rewards are distributed automatically to your wallet + + + + + + + + + ); +} + +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, + }, +}); + diff --git a/frontend/src/screens/Wallet/WalletScreen.tsx b/frontend/src/screens/Wallet/WalletScreen.tsx new file mode 100644 index 00000000..0840b962 --- /dev/null +++ b/frontend/src/screens/Wallet/WalletScreen.tsx @@ -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(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 ( + + {/* Colorful Top Band */} + + + + + + + + + + {/* NFT Card - Top Right */} + + + NFT + 123456784444444444444444912 + + + + + + Satoshi Qazi M. + + + + TOTAL REWARD + {totalReward} + + + + {/* Coins Table */} + + {/* Table Header */} + + coin + amount + Network + Witdrawal adress + withdraw deposit + + + {/* Table Rows */} + {coins.map((coin) => ( + + {/* Coin */} + + + {coin.symbol} + + + {/* Amount */} + + {coin.amount} + + + {/* Network */} + + {coin.network} + + + {/* Withdrawal Address */} + + + setWithdrawalAddresses({ ...withdrawalAddresses, [coin.symbol]: text }) + } + /> + + + {/* Withdraw/Deposit Buttons */} + + handleWithdraw(coin)} + > + + + + + + handleDeposit(coin)} + > + + + + + + + ))} + + + + {/* Bottom Stats Cards */} + + {/* Stake/Unstake Card */} + + + STAKE + {stakeAmount} + + + + + + + + + UNSTAKE + {stakeAmount} + + + + + + + {/* Trust Score */} + navigation.navigate('TrustScore')} + > + + TRUST SCORE + {trustScore} + + + {/* Total Referral */} + navigation.navigate('Referral')} + > + + TOTAL REFERAL + {totalReferral} + + + {/* Circle Score */} + + + CIRCLE SCORE + {circleScore} + + + {/* Edu Score */} + navigation.navigate('Education')} + > + + EDU. SCORE + {eduScore} + + + + + {/* Send Modal */} + {selectedToken && ( + setSendModalVisible(false)} + token={{ + symbol: selectedToken.symbol, + name: selectedToken.name, + balance: selectedToken.amount, + icon: selectedToken.icon, + color: selectedToken.color, + }} + /> + )} + + {/* Receive Modal */} + {selectedToken && ( + setReceiveModalVisible(false)} + token={{ + symbol: selectedToken.symbol, + name: selectedToken.name, + icon: selectedToken.icon, + color: selectedToken.color, + }} + /> + )} + + ); +} + +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, + }, +}); + diff --git a/frontend/src/screens/Wallet/WalletScreen_old.tsx b/frontend/src/screens/Wallet/WalletScreen_old.tsx new file mode 100644 index 00000000..56b8f403 --- /dev/null +++ b/frontend/src/screens/Wallet/WalletScreen_old.tsx @@ -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 ( + + + Wallet + + {/* HEZ Card */} + + + HEZ + The People's Currency + + 45,750.5 + $45,234 USD + Staked: 30,000 HEZ + + + {/* PEZ Card */} + + + PEZ + Governance Token + + 1,234,567 + $123,456 USD + Governance Power: 2.5% + + + {/* Action Buttons */} + + + + Send + + + + Receive + + + + {/* Transactions */} + + Recent Transactions + + + + + + Sent HEZ + Yesterday + + -500 HEZ + + + + + ); +} + +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 }, +}); diff --git a/frontend/src/services/blockchain.ts b/frontend/src/services/blockchain.ts new file mode 100644 index 00000000..dd4368a2 --- /dev/null +++ b/frontend/src/services/blockchain.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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; + diff --git a/frontend/src/services/kycService.ts b/frontend/src/services/kycService.ts new file mode 100644 index 00000000..ea83d321 --- /dev/null +++ b/frontend/src/services/kycService.ts @@ -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 { + // 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + const status = await this.getKYCStatus(); + return status.approved; + } + + /** + * Clear all KYC data (for testing) + */ + async clearKYCData(): Promise { + 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; + diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts new file mode 100644 index 00000000..810fa5ce --- /dev/null +++ b/frontend/src/types/index.ts @@ -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 { + success: boolean; + data?: T; + error?: string; + message?: string; +} + diff --git a/frontend/src/types/kyc.ts b/frontend/src/types/kyc.ts new file mode 100644 index 00000000..a5f9a8d7 --- /dev/null +++ b/frontend/src/types/kyc.ts @@ -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 = { + 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: 'دیاسپۆرا' }, +}; +