Files
pwap/mobile/src/screens/WalletScreen.tsx
T
pezkuwichain 24d6a942f8 refactor(mobile): Remove i18n, expand core screens, update plan
BREAKING: Removed multi-language support (i18n) - will be re-added later

Changes:
- Removed i18n system (6 language files, LanguageContext)
- Expanded WalletScreen, SettingsScreen, SwapScreen with more features
- Added KurdistanSun component, HEZ/PEZ token icons
- Added EditProfileScreen, WalletSetupScreen
- Added button e2e tests (Profile, Settings, Wallet)
- Updated plan: honest assessment - 42 nav buttons with mock data
- Fixed terminology: Polkadot→Pezkuwi, Substrate→Bizinikiwi

Reality check: UI complete with mock data, converting to production one-by-one
2026-01-15 05:08:21 +03:00

1342 lines
41 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useCallback } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
SafeAreaView,
ScrollView,
StatusBar,
Modal,
TextInput,
Alert,
ActivityIndicator,
RefreshControl,
Image,
Platform,
Clipboard,
Share,
} from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
import { useNavigation } from '@react-navigation/native';
import QRCode from 'react-native-qrcode-svg';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as SecureStore from 'expo-secure-store';
import { KurdistanColors } from '../theme/colors';
import { usePezkuwi, NetworkType, NETWORKS } from '../contexts/PezkuwiContext';
import { AddTokenModal } from '../components/wallet/AddTokenModal';
import { HezTokenLogo, PezTokenLogo } from '../components/icons';
// Secure storage helper - same as in PezkuwiContext
const secureStorage = {
getItem: async (key: string): Promise<string | null> => {
if (Platform.OS === 'web') {
return await AsyncStorage.getItem(key);
} else {
return await SecureStore.getItemAsync(key);
}
},
};
// Cross-platform alert helper
const showAlert = (title: string, message: string, buttons?: Array<{text: string; onPress?: () => void; style?: string}>) => {
if (Platform.OS === 'web') {
if (buttons && buttons.length > 1) {
const result = window.confirm(`${title}\n\n${message}`);
if (result && buttons[1]?.onPress) {
buttons[1].onPress();
} else if (!result && buttons[0]?.onPress) {
buttons[0].onPress();
}
} else {
window.alert(`${title}\n\n${message}`);
if (buttons?.[0]?.onPress) buttons[0].onPress();
}
} else {
showAlert(title, message, buttons as any);
}
};
// Token Images - From shared/images
const hezLogo = require('../../../shared/images/hez_logo.png');
const pezLogo = require('../../../shared/images/pez_logo.jpg');
const usdtLogo = require('../../../shared/images/USDT(hez)logo.png');
const dotLogo = require('../../../shared/images/dot.png');
const btcLogo = require('../../../shared/images/bitcoin.png');
const ethLogo = require('../../../shared/images/etherium.png');
const bnbLogo = require('../../../shared/images/BNB_logo.png');
const adaLogo = require('../../../shared/images/ADAlogo.png');
interface Token {
symbol: string;
name: string;
balance: string;
value: string;
change: string;
logo: any; // Image source
assetId?: number;
isLive: boolean;
}
interface Transaction {
hash: string;
method: string;
section: string;
from: string;
to?: string;
amount?: string;
blockNumber: number;
timestamp?: number;
isIncoming: boolean;
}
const WalletScreen: React.FC = () => {
const navigation = useNavigation<any>();
const {
api,
isApiReady,
accounts,
selectedAccount,
setSelectedAccount,
connectWallet,
disconnectWallet,
createWallet,
deleteWallet,
getKeyPair,
currentNetwork,
switchNetwork,
error: pezkuwiError
} = usePezkuwi();
const [selectedToken, setSelectedToken] = useState<Token | null>(null);
const [sendModalVisible, setSendModalVisible] = useState(false);
const [receiveModalVisible, setReceiveModalVisible] = useState(false);
const [createWalletModalVisible, setCreateWalletModalVisible] = useState(false);
const [importWalletModalVisible, setImportWalletModalVisible] = useState(false);
const [backupModalVisible, setBackupModalVisible] = useState(false);
const [networkSelectorVisible, setNetworkSelectorVisible] = useState(false);
const [walletSelectorVisible, setWalletSelectorVisible] = useState(false);
const [addTokenModalVisible, setAddTokenModalVisible] = useState(false);
const [recipientAddress, setRecipientAddress] = useState('');
const [sendAmount, setSendAmount] = useState('');
const [walletName, setWalletName] = useState('');
const [importMnemonic, setImportMnemonic] = useState('');
const [importWalletName, setImportWalletName] = useState('');
const [userMnemonic, setUserMnemonic] = useState<string>('');
const [isSending, setIsSending] = useState(false);
const [isLoadingBalances, setIsLoadingBalances] = useState(false);
// Transaction History State
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [isLoadingHistory, setIsLoadingHistory] = useState(false);
const [balances, setBalances] = useState<{ [key: string]: string }>({
HEZ: '0.00',
PEZ: '0.00',
USDT: '0.00',
});
const tokens: Token[] = [
{
symbol: 'HEZ',
name: 'Pezkuwi',
balance: balances.HEZ,
value: '$0.00',
change: '+0.00%',
logo: hezLogo,
isLive: true
},
{
symbol: 'PEZ',
name: 'Pezkuwi Token',
balance: balances.PEZ,
value: '$0.00',
change: '+0.00%',
logo: pezLogo,
assetId: 1,
isLive: true
},
{
symbol: 'USDT',
name: 'Tether USD',
balance: balances.USDT,
value: '$0.00',
change: '+0.00%',
logo: usdtLogo,
assetId: 1000,
isLive: true
},
];
// Fetch balances and history
const fetchData = useCallback(async () => {
if (!api || !isApiReady || !selectedAccount) return;
setIsLoadingBalances(true);
try {
// 1. Fetch Balances
const accountInfo = await api.query.system.account(selectedAccount.address);
const hezBalance = (Number(accountInfo.data.free.toString()) / 1e12).toFixed(2);
let pezBalance = '0.00';
try {
if (api.query.assets?.account) {
const pezAsset = await api.query.assets.account(1, selectedAccount.address);
if (pezAsset.isSome) pezBalance = (Number(pezAsset.unwrap().balance.toString()) / 1e12).toFixed(2);
}
} catch {}
let usdtBalance = '0.00';
try {
if (api.query.assets?.account) {
// Check ID 1000 first (as per constants), fallback to 2 just in case
let usdtAsset = await api.query.assets.account(1000, selectedAccount.address);
if (usdtAsset.isNone) {
usdtAsset = await api.query.assets.account(2, selectedAccount.address);
}
if (usdtAsset.isSome) {
// USDT uses 6 decimals usually, checking constants or assuming standard
usdtBalance = (Number(usdtAsset.unwrap().balance.toString()) / 1e6).toFixed(2);
}
}
} catch {}
setBalances({ HEZ: hezBalance, PEZ: pezBalance, USDT: usdtBalance });
// 2. Fetch History from Indexer API (MUCH FASTER)
setIsLoadingHistory(true);
try {
const INDEXER_URL = 'http://172.31.134.70:3001'; // Update this to your local IP for physical device testing
const response = await fetch(`${INDEXER_URL}/api/history/${selectedAccount.address}`);
const data = await response.json();
const txList = data.map((tx: any) => ({
hash: tx.hash,
method: tx.asset_id ? 'transfer' : 'transfer',
section: tx.asset_id ? 'assets' : 'balances',
from: tx.sender,
to: tx.receiver,
amount: tx.amount,
blockNumber: tx.block_number,
isIncoming: tx.receiver === selectedAccount.address,
}));
setTransactions(txList);
} catch (e) {
console.warn('Indexer API unreachable, history not updated', e);
}
} catch (error) {
console.error('Fetch error:', error);
} finally {
setIsLoadingBalances(false);
setIsLoadingHistory(false);
}
}, [api, isApiReady, selectedAccount]);
useEffect(() => {
fetchData();
const interval = setInterval(fetchData, 30000);
return () => clearInterval(interval);
}, [fetchData]);
const handleTokenPress = (token: Token) => {
if (!token.isLive) return;
setSelectedToken(token);
setSendModalVisible(true);
};
const handleSend = () => {
setSelectedToken(tokens[0]); // Default to HEZ
setSendModalVisible(true);
};
const handleReceive = () => {
setSelectedToken(tokens[0]);
setReceiveModalVisible(true);
};
const handleConfirmSend = async () => {
if (!recipientAddress || !sendAmount || !selectedToken || !selectedAccount || !api) {
showAlert('Error', 'Please enter recipient address and amount');
return;
}
setIsSending(true);
try {
const keypair = await getKeyPair(selectedAccount.address);
if (!keypair) throw new Error('Failed to load keypair');
// Adjust decimals based on token
const decimals = selectedToken.symbol === 'USDT' ? 1e6 : 1e12;
const amountInUnits = BigInt(Math.floor(parseFloat(sendAmount) * decimals));
let tx;
if (selectedToken.symbol === 'HEZ') {
tx = api.tx.balances.transfer(recipientAddress, amountInUnits);
} else if (selectedToken.assetId !== undefined) {
tx = api.tx.assets.transfer(selectedToken.assetId, recipientAddress, amountInUnits);
} else {
throw new Error('Unknown token type');
}
await tx.signAndSend(keypair, ({ status }) => {
if (status.isFinalized) {
setSendModalVisible(false);
setIsSending(false);
showAlert('Success', 'Transaction Sent!');
fetchData();
}
});
} catch (e: any) {
setIsSending(false);
showAlert('Error', e.message);
}
};
// Connect/Create Wallet handlers
const handleConnectWallet = async () => {
if (accounts.length === 0) setCreateWalletModalVisible(true);
else await connectWallet();
};
const handleCreateWallet = async () => {
try {
const { address, mnemonic } = await createWallet(walletName);
setUserMnemonic(mnemonic); // Save for backup
setCreateWalletModalVisible(false);
showAlert('Wallet Created', `Save this mnemonic:\n${mnemonic}`, [{ text: 'OK', onPress: () => connectWallet() }]);
} catch (e) { showAlert('Error', 'Failed'); }
};
// Copy Address Handler
const handleCopyAddress = () => {
if (!selectedAccount) return;
Clipboard.setString(selectedAccount.address);
showAlert('Copied!', 'Address copied to clipboard');
};
// Import Wallet Handler
const handleImportWallet = async () => {
if (!importMnemonic.trim()) {
showAlert('Error', 'Please enter a valid mnemonic');
return;
}
try {
// Use createWallet but inject mnemonic (you may need to modify PezkuwiContext)
// For now, basic implementation:
const { Keyring } = await import('@pezkuwi/keyring');
const keyring = new Keyring({ type: 'sr25519' });
const pair = keyring.addFromMnemonic(importMnemonic.trim());
// Store in AsyncStorage (via context method ideally)
showAlert('Success', `Wallet imported: ${pair.address.slice(0,8)}...`);
setImportWalletModalVisible(false);
setImportMnemonic('');
connectWallet();
} catch (e: any) {
showAlert('Error', e.message || 'Invalid mnemonic');
}
};
// Backup Mnemonic Handler
const handleBackupMnemonic = async () => {
if (!selectedAccount) {
showAlert('No Wallet', 'Please create or import a wallet first.');
return;
}
try {
// Retrieve mnemonic from secure storage
const seedKey = `pezkuwi_seed_${selectedAccount.address}`;
const storedMnemonic = await secureStorage.getItem(seedKey);
if (storedMnemonic) {
setUserMnemonic(storedMnemonic);
setBackupModalVisible(true);
} else {
showAlert('No Backup', 'Mnemonic not found in secure storage. It may have been imported from another device.');
}
} catch (error) {
console.error('Error retrieving mnemonic:', error);
showAlert('Error', 'Failed to retrieve mnemonic from secure storage.');
}
};
// Share QR Code
const handleShareQR = async () => {
if (!selectedAccount) return;
try {
await Share.share({
message: `My Pezkuwichain Address:\n${selectedAccount.address}`,
});
} catch (e) {
console.error(e);
}
};
// Network Switch Handler
const handleNetworkSwitch = async (network: NetworkType) => {
try {
await switchNetwork(network);
setNetworkSelectorVisible(false);
showAlert('Success', `Switched to ${NETWORKS[network].displayName}`);
} catch (e: any) {
showAlert('Error', e.message || 'Failed to switch network');
}
};
// Redirect to WalletSetupScreen if no wallet exists
useEffect(() => {
if (!selectedAccount && accounts.length === 0) {
navigation.replace('WalletSetup');
}
}, [selectedAccount, accounts, navigation]);
// Show loading while checking wallet state or redirecting
if (!selectedAccount && accounts.length === 0) {
return (
<SafeAreaView style={styles.container} testID="wallet-redirecting">
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={KurdistanColors.kesk} />
<Text style={styles.loadingText}>Loading wallet...</Text>
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container} testID="wallet-screen">
<StatusBar barStyle="dark-content" />
{/* Top Header with Back Button */}
<View style={styles.topHeader} testID="wallet-top-header">
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.backButton} testID="wallet-back-button">
<Text style={styles.backButtonText}> Back</Text>
</TouchableOpacity>
<Text style={styles.topHeaderTitle}>Wallet</Text>
<TouchableOpacity onPress={() => setNetworkSelectorVisible(true)} testID="wallet-network-button">
<Text style={styles.networkBadge}>🌐 {NETWORKS[currentNetwork].displayName}</Text>
</TouchableOpacity>
</View>
<ScrollView
style={styles.scrollContent}
refreshControl={<RefreshControl refreshing={isLoadingBalances} onRefresh={fetchData} />}
showsVerticalScrollIndicator={false}
testID="wallet-scroll-view"
>
{/* Wallet Selector Row */}
<View style={styles.walletSelectorRow}>
<TouchableOpacity
style={styles.walletSelector}
onPress={() => setWalletSelectorVisible(true)}
testID="wallet-selector-button"
>
<View style={styles.walletSelectorInfo}>
<Text style={styles.walletSelectorName}>{selectedAccount?.name || 'Wallet'}</Text>
<Text style={styles.walletSelectorAddress} numberOfLines={1}>
{selectedAccount?.address ? `${selectedAccount.address.slice(0, 8)}...${selectedAccount.address.slice(-6)}` : ''}
</Text>
</View>
<Text style={styles.walletSelectorArrow}></Text>
</TouchableOpacity>
<View style={styles.walletHeaderButtons}>
<TouchableOpacity
style={styles.addWalletButton}
onPress={() => navigation.navigate('WalletSetup')}
testID="add-wallet-button"
>
<Text style={styles.addWalletIcon}>+</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.scanButton}
onPress={() => showAlert('Scan', 'QR Scanner coming soon')}
testID="wallet-scan-button"
>
<Text style={styles.scanIcon}></Text>
</TouchableOpacity>
</View>
</View>
{/* Main Token Cards - HEZ and PEZ side by side */}
<View style={styles.mainTokensRow}>
{/* HEZ Card */}
<TouchableOpacity style={styles.mainTokenCard} onPress={() => handleTokenPress(tokens[0])}>
<View style={styles.mainTokenLogoContainer}>
<HezTokenLogo size={56} />
</View>
<Text style={styles.mainTokenSymbol}>HEZ</Text>
<Text style={styles.mainTokenBalance}>{balances.HEZ}</Text>
<Text style={styles.mainTokenSubtitle}>Welati Coin</Text>
</TouchableOpacity>
{/* PEZ Card */}
<TouchableOpacity style={styles.mainTokenCard} onPress={() => handleTokenPress(tokens[1])}>
<View style={styles.mainTokenLogoContainer}>
<PezTokenLogo size={56} />
</View>
<Text style={styles.mainTokenSymbol}>PEZ</Text>
<Text style={styles.mainTokenBalance}>{balances.PEZ}</Text>
<Text style={styles.mainTokenSubtitle}>Pezkuwichain Token</Text>
</TouchableOpacity>
</View>
{/* Action Buttons Grid - 1x4 */}
<View style={styles.actionsGrid}>
<TouchableOpacity style={[styles.actionButton, {backgroundColor: '#22C55E'}]} onPress={handleSend}>
<Text style={styles.actionIcon}></Text>
<Text style={styles.actionLabel}>Send</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.actionButton, {backgroundColor: '#3B82F6'}]} onPress={handleReceive}>
<Text style={styles.actionIcon}></Text>
<Text style={styles.actionLabel}>Receive</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.actionButton, {backgroundColor: '#6B7280'}]} onPress={() => navigation.navigate('Swap')}>
<Text style={styles.actionIcon}>🔄</Text>
<Text style={styles.actionLabel}>Swap</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.actionButton, {backgroundColor: '#10B981'}]} onPress={() => showAlert('Staking', 'Navigate to Staking')}>
<Text style={styles.actionIcon}>🥩</Text>
<Text style={styles.actionLabel}>Staking</Text>
</TouchableOpacity>
</View>
{/* Tokens List */}
<View style={styles.tokensSection}>
<View style={styles.tokensSectionHeader}>
<Text style={styles.tokensTitle}>Tokens</Text>
<View style={styles.tokenHeaderIcons}>
<TouchableOpacity style={styles.tokenHeaderIcon}>
<Text>🔍</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.tokenHeaderIcon} onPress={() => setAddTokenModalVisible(true)}>
<Text></Text>
</TouchableOpacity>
<TouchableOpacity style={styles.tokenHeaderIcon} onPress={handleBackupMnemonic}>
<Text></Text>
</TouchableOpacity>
</View>
</View>
{/* USDT */}
<TouchableOpacity style={styles.tokenListItem}>
<Image source={usdtLogo} style={styles.tokenListLogo} resizeMode="contain" />
<View style={styles.tokenListInfo}>
<Text style={styles.tokenListSymbol}>USDT</Text>
<Text style={styles.tokenListNetwork}>PEZ Network</Text>
</View>
<View style={styles.tokenListBalance}>
<Text style={styles.tokenListAmount}>{balances.USDT}</Text>
<Text style={styles.tokenListUsdValue}>$0.00</Text>
</View>
</TouchableOpacity>
{/* DOT */}
<TouchableOpacity style={styles.tokenListItem}>
<Image source={dotLogo} style={styles.tokenListLogo} resizeMode="contain" />
<View style={styles.tokenListInfo}>
<Text style={styles.tokenListSymbol}>DOT</Text>
<Text style={styles.tokenListNetwork}>Polkadot</Text>
</View>
<View style={styles.tokenListBalance}>
<Text style={styles.tokenListAmount}>0.00</Text>
<Text style={styles.tokenListUsdValue}>$0.00</Text>
</View>
</TouchableOpacity>
</View>
<View style={{height: 100}} />
</ScrollView>
{/* Modals */}
<Modal visible={sendModalVisible} transparent animationType="slide" onRequestClose={() => setSendModalVisible(false)}>
<View style={styles.modalOverlay}>
<View style={styles.modalCard}>
<Text style={styles.modalHeader}>Send {selectedToken?.symbol}</Text>
<View style={{alignItems:'center', marginBottom:16}}>
{selectedToken && <Image source={selectedToken.logo} style={{width:48, height:48}} />}
</View>
<TextInput style={styles.inputField} placeholder="Address" value={recipientAddress} onChangeText={setRecipientAddress} />
<TextInput style={styles.inputField} placeholder="Amount" keyboardType="numeric" value={sendAmount} onChangeText={setSendAmount} />
<View style={styles.modalActions}>
<TouchableOpacity style={styles.btnCancel} onPress={() => setSendModalVisible(false)}><Text>Cancel</Text></TouchableOpacity>
<TouchableOpacity style={styles.btnConfirm} onPress={handleConfirmSend} disabled={isSending}>
<Text style={{color:'white'}}>{isSending ? 'Sending...' : 'Confirm'}</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
<Modal visible={receiveModalVisible} transparent animationType="slide" onRequestClose={() => setReceiveModalVisible(false)}>
<View style={styles.modalOverlay}>
<View style={styles.modalCard}>
<Text style={styles.modalHeader}>Receive Address</Text>
<View style={{alignItems:'center', marginVertical: 20}}>
{selectedAccount && <QRCode value={selectedAccount.address} size={180}/>}
</View>
<Text style={styles.addrFull}>{selectedAccount?.address}</Text>
<View style={styles.modalActions}>
<TouchableOpacity style={styles.btnCancel} onPress={handleCopyAddress}>
<Text>📋 Copy</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btnCancel} onPress={handleShareQR}>
<Text>📤 Share</Text>
</TouchableOpacity>
</View>
<TouchableOpacity style={[styles.btnConfirm, {width: '100%', marginTop: 8}]} onPress={() => setReceiveModalVisible(false)}>
<Text style={{color:'white'}}>Close</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
{/* Backup Mnemonic Modal */}
<Modal visible={backupModalVisible} transparent animationType="slide" onRequestClose={() => setBackupModalVisible(false)}>
<View style={styles.modalOverlay}>
<View style={styles.modalCard}>
<Text style={styles.modalHeader}>🔐 Backup Mnemonic</Text>
<Text style={{color: 'red', fontSize: 12, marginBottom: 12, textAlign: 'center'}}>
NEVER share this with anyone! Write it down and store safely.
</Text>
<View style={[styles.inputField, {backgroundColor: '#FFF9E6', padding: 16, minHeight: 100}]}>
<Text style={{fontSize: 14, lineHeight: 22}}>{userMnemonic}</Text>
</View>
<View style={styles.modalActions}>
<TouchableOpacity style={styles.btnCancel} onPress={() => {
Clipboard.setString(userMnemonic);
showAlert('Copied', 'Mnemonic copied to clipboard');
}}>
<Text>📋 Copy</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btnConfirm} onPress={() => setBackupModalVisible(false)}>
<Text style={{color:'white'}}>Close</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
{/* Wallet Selector Modal */}
<Modal visible={walletSelectorVisible} transparent animationType="slide" onRequestClose={() => setWalletSelectorVisible(false)}>
<View style={styles.modalOverlay}>
<View style={styles.modalCard}>
<Text style={styles.modalHeader}>👛 My Wallets</Text>
<Text style={{color: '#666', fontSize: 12, marginBottom: 16, textAlign: 'center'}}>
Select a wallet or create a new one
</Text>
{/* Wallet List */}
{accounts.map((account) => {
const isSelected = account.address === selectedAccount?.address;
return (
<View key={account.address} style={styles.walletOptionRow}>
<TouchableOpacity
style={[
styles.walletOption,
isSelected && styles.walletOptionSelected,
{flex: 1, marginBottom: 0}
]}
onPress={() => {
setSelectedAccount(account);
setWalletSelectorVisible(false);
}}
>
<View style={styles.walletOptionIcon}>
<Text style={{fontSize: 24}}>👛</Text>
</View>
<View style={{flex: 1}}>
<Text style={[styles.walletOptionName, isSelected && {color: KurdistanColors.kesk}]}>
{account.name}
</Text>
<Text style={styles.walletOptionAddress} numberOfLines={1}>
{account.address.slice(0, 12)}...{account.address.slice(-8)}
</Text>
</View>
{isSelected && <Text style={{fontSize: 20, color: KurdistanColors.kesk}}></Text>}
</TouchableOpacity>
<TouchableOpacity
style={styles.deleteWalletButton}
onPress={async () => {
const confirmDelete = Platform.OS === 'web'
? window.confirm(`Delete "${account.name}"?\n\nThis action cannot be undone. Make sure you have backed up your recovery phrase.`)
: await new Promise<boolean>((resolve) => {
Alert.alert(
'Delete Wallet',
`Are you sure you want to delete "${account.name}"?\n\nThis action cannot be undone. Make sure you have backed up your recovery phrase.`,
[
{ text: 'Cancel', style: 'cancel', onPress: () => resolve(false) },
{ text: 'Delete', style: 'destructive', onPress: () => resolve(true) }
]
);
});
if (confirmDelete) {
try {
await deleteWallet(account.address);
if (accounts.length <= 1) {
setWalletSelectorVisible(false);
}
} catch (err) {
if (Platform.OS === 'web') {
window.alert('Failed to delete wallet');
} else {
Alert.alert('Error', 'Failed to delete wallet');
}
}
}
}}
>
<Text style={styles.deleteWalletIcon}>🗑</Text>
</TouchableOpacity>
</View>
);
})}
{/* Add New Wallet Button */}
<TouchableOpacity
style={styles.addNewWalletOption}
onPress={() => {
setWalletSelectorVisible(false);
navigation.navigate('WalletSetup');
}}
>
<View style={styles.addNewWalletIcon}>
<Text style={{fontSize: 24, color: KurdistanColors.kesk}}>+</Text>
</View>
<Text style={styles.addNewWalletText}>Add New Wallet</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btnConfirm} onPress={() => setWalletSelectorVisible(false)}>
<Text style={{color:'white'}}>Close</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
{/* Network Selector Modal */}
<Modal visible={networkSelectorVisible} transparent animationType="slide" onRequestClose={() => setNetworkSelectorVisible(false)}>
<View style={styles.modalOverlay}>
<View style={styles.modalCard}>
<Text style={styles.modalHeader}>🌐 Select Network</Text>
<Text style={{color: '#666', fontSize: 12, marginBottom: 16, textAlign: 'center'}}>
Choose the network you want to connect to
</Text>
{(Object.keys(NETWORKS) as NetworkType[]).map((networkKey) => {
const network = NETWORKS[networkKey];
const isSelected = networkKey === currentNetwork;
return (
<TouchableOpacity
key={networkKey}
style={[
styles.networkOption,
isSelected && styles.networkOptionSelected
]}
onPress={() => handleNetworkSwitch(networkKey)}
>
<View style={{flex: 1}}>
<Text style={[styles.networkName, isSelected && {color: KurdistanColors.kesk}]}>
{network.displayName}
</Text>
<Text style={styles.networkType}>
{network.type === 'mainnet' ? '🟢 Mainnet' : network.type === 'testnet' ? '🟡 Testnet' : '🟠 Canary'}
</Text>
</View>
{isSelected && <Text style={{fontSize: 20}}></Text>}
</TouchableOpacity>
);
})}
<TouchableOpacity style={styles.btnConfirm} onPress={() => setNetworkSelectorVisible(false)}>
<Text style={{color:'white'}}>Close</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
{/* Add Token Modal */}
<AddTokenModal
visible={addTokenModalVisible}
onClose={() => setAddTokenModalVisible(false)}
onTokenAdded={fetchData}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F8F9FA'
},
scrollContent: {
flex: 1,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
marginTop: 16,
fontSize: 16,
color: '#666',
},
// Top Header with Back Button
topHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: '#FFFFFF',
borderBottomWidth: 1,
borderBottomColor: '#E5E5E5',
},
backButton: {
paddingVertical: 4,
paddingRight: 8,
},
backButtonText: {
fontSize: 16,
color: KurdistanColors.kesk,
fontWeight: '500',
},
topHeaderTitle: {
fontSize: 18,
fontWeight: '600',
color: '#333',
},
// Header Styles (New Design)
headerContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20,
paddingTop: Platform.OS === 'android' ? 16 : 12,
paddingBottom: 16,
backgroundColor: '#FFFFFF',
},
walletTitle: {
fontSize: 22,
fontWeight: 'bold',
color: '#333',
},
networkBadge: {
fontSize: 14,
color: KurdistanColors.kesk,
backgroundColor: 'rgba(0, 143, 67, 0.1)',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 16,
overflow: 'hidden',
},
scanButton: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#F5F5F5',
justifyContent: 'center',
alignItems: 'center',
},
scanIcon: {
fontSize: 20,
color: '#333',
},
// Main Token Cards (HEZ & PEZ) - New Design
mainTokensRow: {
flexDirection: 'row',
paddingHorizontal: 16,
paddingVertical: 16,
gap: 12,
},
mainTokenCard: {
flex: 1,
backgroundColor: '#FFFFFF',
borderRadius: 20,
padding: 20,
alignItems: 'center',
boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.08)',
elevation: 4,
},
mainTokenLogo: {
width: 56,
height: 56,
marginBottom: 12,
},
mainTokenLogoContainer: {
width: 56,
height: 56,
marginBottom: 12,
justifyContent: 'center',
alignItems: 'center',
},
mainTokenSymbol: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
marginBottom: 8,
},
mainTokenBalance: {
fontSize: 24,
fontWeight: 'bold',
color: KurdistanColors.kesk,
marginBottom: 4,
},
mainTokenSubtitle: {
fontSize: 11,
color: '#888',
},
// Action Buttons Grid (2x4) - New Design
actionsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
paddingHorizontal: 16,
paddingVertical: 8,
gap: 12,
},
actionButton: {
width: '23%',
aspectRatio: 1,
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)',
elevation: 3,
},
actionIcon: {
fontSize: 24,
color: '#FFFFFF',
marginBottom: 4,
},
actionLabel: {
fontSize: 11,
fontWeight: '600',
color: '#FFFFFF',
},
// Tokens List Section - New Design
tokensSection: {
paddingHorizontal: 16,
paddingTop: 20,
},
tokensSectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
paddingHorizontal: 4,
},
tokensTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#333',
},
tokenHeaderIcons: {
flexDirection: 'row',
gap: 12,
},
tokenHeaderIcon: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#F5F5F5',
justifyContent: 'center',
alignItems: 'center',
},
tokenListItem: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 16,
marginBottom: 12,
boxShadow: '0px 1px 4px rgba(0, 0, 0, 0.05)',
elevation: 2,
},
tokenListLogo: {
width: 44,
height: 44,
marginRight: 12,
},
tokenListInfo: {
flex: 1,
},
tokenListSymbol: {
fontSize: 16,
fontWeight: 'bold',
color: '#333',
marginBottom: 2,
},
tokenListNetwork: {
fontSize: 12,
color: '#888',
},
tokenListBalance: {
alignItems: 'flex-end',
},
tokenListAmount: {
fontSize: 16,
fontWeight: 'bold',
color: '#333',
marginBottom: 2,
},
tokenListUsdValue: {
fontSize: 12,
color: '#888',
},
// Modal Styles
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'center',
alignItems: 'center',
padding: 20
},
modalCard: {
backgroundColor: 'white',
borderRadius: 20,
padding: 24,
width: '100%',
alignItems: 'center'
},
modalHeader: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 20
},
inputField: {
width: '100%',
backgroundColor: '#F5F5F5',
padding: 16,
borderRadius: 12,
marginBottom: 12
},
modalActions: {
flexDirection: 'row',
width: '100%',
gap: 12,
marginTop: 10
},
btnCancel: {
flex: 1,
padding: 16,
borderRadius: 12,
backgroundColor: '#EEE',
alignItems: 'center'
},
btnConfirm: {
flex: 1,
padding: 16,
borderRadius: 12,
backgroundColor: KurdistanColors.kesk,
alignItems: 'center'
},
addrFull: {
textAlign: 'center',
color: '#666',
fontSize: 12,
marginVertical: 10,
fontFamily: 'monospace'
},
// Network Selector Styles
networkOption: {
flexDirection: 'row',
width: '100%',
padding: 16,
borderRadius: 12,
backgroundColor: '#F5F5F5',
marginBottom: 8,
alignItems: 'center'
},
networkOptionSelected: {
backgroundColor: 'rgba(0, 143, 67, 0.1)',
borderWidth: 2,
borderColor: KurdistanColors.kesk
},
networkName: {
fontSize: 16,
fontWeight: '600',
color: '#333'
},
networkType: {
fontSize: 12,
color: '#666',
marginTop: 4
},
// Welcome Screen Styles
welcomeGradient: {
flex: 0.45,
justifyContent: 'center',
alignItems: 'center',
borderBottomLeftRadius: 32,
borderBottomRightRadius: 32,
},
welcomeContent: {
alignItems: 'center',
paddingHorizontal: 20,
},
welcomeEmoji: {
fontSize: 64,
marginBottom: 16,
},
welcomeTitle: {
fontSize: 18,
color: 'rgba(255,255,255,0.8)',
marginBottom: 8,
},
welcomeBrand: {
fontSize: 32,
fontWeight: 'bold',
color: 'white',
marginBottom: 8,
textAlign: 'center',
},
welcomeSubtitle: {
fontSize: 16,
color: 'rgba(255,255,255,0.7)',
textAlign: 'center',
},
// Button Container (Welcome Screen)
buttonContainer: {
flex: 0.55,
paddingHorizontal: 24,
paddingTop: 32,
justifyContent: 'flex-start',
},
// Primary Button (Welcome Screen)
primaryWalletButton: {
marginBottom: 16,
borderRadius: 16,
overflow: 'hidden',
boxShadow: '0px 4px 8px rgba(0, 128, 0, 0.3)',
elevation: 8,
},
buttonGradient: {
flexDirection: 'row',
alignItems: 'center',
padding: 20,
borderRadius: 16,
},
buttonIcon: {
width: 52,
height: 52,
borderRadius: 26,
backgroundColor: 'rgba(255,255,255,0.2)',
justifyContent: 'center',
alignItems: 'center',
marginRight: 16,
},
buttonIconText: {
fontSize: 24,
color: 'white',
},
buttonTextContainer: {
flex: 1,
},
primaryButtonText: {
fontSize: 18,
fontWeight: 'bold',
color: 'white',
marginBottom: 4,
},
primaryButtonSubtext: {
fontSize: 14,
color: 'rgba(255,255,255,0.8)',
},
// Secondary Button (Welcome Screen)
secondaryWalletButton: {
backgroundColor: 'white',
borderRadius: 16,
marginBottom: 24,
boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.1)',
elevation: 4,
},
secondaryButtonContent: {
flexDirection: 'row',
alignItems: 'center',
padding: 20,
},
secondaryButtonText: {
fontSize: 18,
fontWeight: '600',
color: '#333',
marginBottom: 4,
},
secondaryButtonSubtext: {
fontSize: 14,
color: '#666',
},
// Security Notice (Welcome Screen)
securityNotice: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(0,105,62,0.05)',
padding: 16,
borderRadius: 12,
marginTop: 'auto',
marginBottom: 20,
},
securityIcon: {
fontSize: 20,
marginRight: 12,
},
securityText: {
flex: 1,
fontSize: 12,
color: '#666',
lineHeight: 18,
},
// Wallet Selector Row
walletSelectorRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 12,
marginBottom: 8,
},
walletSelector: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#FFFFFF',
borderRadius: 12,
padding: 12,
marginRight: 12,
boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.05)',
elevation: 2,
},
walletSelectorInfo: {
flex: 1,
},
walletSelectorName: {
fontSize: 16,
fontWeight: '600',
color: KurdistanColors.reş,
},
walletSelectorAddress: {
fontSize: 12,
color: '#999',
marginTop: 2,
},
walletSelectorArrow: {
fontSize: 12,
color: '#666',
marginLeft: 8,
},
walletHeaderButtons: {
flexDirection: 'row',
gap: 8,
},
addWalletButton: {
width: 44,
height: 44,
borderRadius: 12,
backgroundColor: '#FFFFFF',
justifyContent: 'center',
alignItems: 'center',
boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.05)',
elevation: 2,
},
addWalletIcon: {
fontSize: 24,
color: KurdistanColors.kesk,
fontWeight: '300',
},
// Wallet Selector Modal
walletOption: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: '#F8F9FA',
borderRadius: 12,
marginBottom: 8,
borderWidth: 2,
borderColor: 'transparent',
},
walletOptionSelected: {
borderColor: KurdistanColors.kesk,
backgroundColor: 'rgba(0, 143, 67, 0.05)',
},
walletOptionIcon: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: '#FFFFFF',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
walletOptionName: {
fontSize: 16,
fontWeight: '600',
color: KurdistanColors.reş,
},
walletOptionAddress: {
fontSize: 12,
color: '#999',
marginTop: 2,
},
addNewWalletOption: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: 'rgba(0, 143, 67, 0.05)',
borderRadius: 12,
marginBottom: 16,
borderWidth: 2,
borderColor: KurdistanColors.kesk,
borderStyle: 'dashed',
},
addNewWalletIcon: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: '#FFFFFF',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
addNewWalletText: {
fontSize: 16,
fontWeight: '600',
color: KurdistanColors.kesk,
},
// Delete wallet
walletOptionRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
gap: 8,
},
deleteWalletButton: {
width: 44,
height: 44,
borderRadius: 12,
backgroundColor: 'rgba(239, 68, 68, 0.1)',
justifyContent: 'center',
alignItems: 'center',
},
deleteWalletIcon: {
fontSize: 18,
},
});
export default WalletScreen;