mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-06-18 16:11:01 +00:00
1a47363938
Major fixes: - Replace `any` types with proper TypeScript types across all files - Convert require() imports to ES module imports - Add __DEV__ guards to console statements - Escape special characters in JSX (' and ") - Fix unused variables (prefix with _ or remove) - Fix React hooks violations (useCallback, useMemo patterns) - Convert wasm-crypto-shim.js to TypeScript - Add eslint-disable comments for valid setState patterns Files affected: 50+ screens, components, contexts, and services Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
942 lines
29 KiB
TypeScript
942 lines
29 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
||
import {
|
||
View,
|
||
Text,
|
||
TouchableOpacity,
|
||
StyleSheet,
|
||
SafeAreaView,
|
||
ScrollView,
|
||
StatusBar,
|
||
Alert,
|
||
Switch,
|
||
Linking,
|
||
ActivityIndicator,
|
||
Modal,
|
||
TextInput,
|
||
Platform
|
||
} from 'react-native';
|
||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||
import * as SecureStore from 'expo-secure-store';
|
||
import { Clipboard } from 'react-native';
|
||
import { useNavigation, NavigationProp } from '@react-navigation/native';
|
||
import type { AlertButton } from 'react-native';
|
||
import { RootStackParamList } from '../navigation/AppNavigator';
|
||
import { KurdistanColors } from '../theme/colors';
|
||
|
||
// Profile type for Supabase
|
||
interface UserProfile {
|
||
id?: string;
|
||
full_name: string;
|
||
username: string;
|
||
bio?: string;
|
||
notifications_push: boolean;
|
||
notifications_email: boolean;
|
||
updated_at?: string;
|
||
}
|
||
import { useTheme } from '../contexts/ThemeContext';
|
||
import { useBiometricAuth } from '../contexts/BiometricAuthContext';
|
||
import { useAuth } from '../contexts/AuthContext';
|
||
import { usePezkuwi, NETWORKS } from '../contexts/PezkuwiContext';
|
||
import { supabase } from '../lib/supabase';
|
||
|
||
// Cross-platform alert helper
|
||
const showAlert = (title: string, message: string, buttons?: AlertButton[]) => {
|
||
if (Platform.OS === 'web') {
|
||
if (buttons && buttons.length > 1) {
|
||
// For confirm dialogs
|
||
const result = window.confirm(`${title}\n\n${message}`);
|
||
if (result && buttons[1]?.onPress) {
|
||
buttons[1].onPress();
|
||
}
|
||
} else {
|
||
window.alert(`${title}\n\n${message}`);
|
||
}
|
||
} else {
|
||
Alert.alert(title, message, buttons);
|
||
}
|
||
};
|
||
|
||
// Font size options
|
||
type FontSize = 'small' | 'medium' | 'large';
|
||
const FONT_SIZE_OPTIONS: { value: FontSize; label: string; description: string }[] = [
|
||
{ value: 'small', label: 'Small', description: '87.5% - Compact text' },
|
||
{ value: 'medium', label: 'Medium', description: '100% - Default size' },
|
||
{ value: 'large', label: 'Large', description: '112.5% - Easier to read' },
|
||
];
|
||
|
||
// Auto-lock timer options (in minutes)
|
||
const AUTO_LOCK_OPTIONS: { value: number; label: string }[] = [
|
||
{ value: 1, label: '1 minute' },
|
||
{ value: 5, label: '5 minutes' },
|
||
{ value: 15, label: '15 minutes' },
|
||
{ value: 30, label: '30 minutes' },
|
||
{ value: 60, label: '1 hour' },
|
||
];
|
||
|
||
// --- COMPONENTS (Internal for simplicity) ---
|
||
|
||
const SectionHeader = ({ title }: { title: string }) => {
|
||
const { colors } = useTheme();
|
||
return (
|
||
<View style={styles.sectionHeader}>
|
||
<Text style={[styles.sectionTitle, { color: colors.textSecondary }]}>{title}</Text>
|
||
</View>
|
||
);
|
||
};
|
||
|
||
const SettingItem = ({
|
||
icon,
|
||
title,
|
||
subtitle,
|
||
onPress,
|
||
showArrow = true,
|
||
textColor,
|
||
testID
|
||
}: {
|
||
icon: string;
|
||
title: string;
|
||
subtitle?: string;
|
||
onPress: () => void;
|
||
showArrow?: boolean;
|
||
textColor?: string;
|
||
testID?: string;
|
||
}) => {
|
||
const { colors } = useTheme();
|
||
return (
|
||
<TouchableOpacity
|
||
style={[styles.settingItem, { borderBottomColor: colors.border }]}
|
||
onPress={onPress}
|
||
testID={testID}
|
||
>
|
||
<View style={[styles.settingIcon, { backgroundColor: colors.background }]}>
|
||
<Text style={styles.settingIconText}>{icon}</Text>
|
||
</View>
|
||
<View style={styles.settingContent}>
|
||
<Text style={[styles.settingTitle, { color: textColor || colors.text }]}>{title}</Text>
|
||
{subtitle && <Text style={[styles.settingSubtitle, { color: colors.textSecondary }]}>{subtitle}</Text>}
|
||
</View>
|
||
{showArrow && <Text style={[styles.arrow, { color: colors.textSecondary }]}>→</Text>}
|
||
</TouchableOpacity>
|
||
);
|
||
};
|
||
|
||
const SettingToggle = ({
|
||
icon,
|
||
title,
|
||
subtitle,
|
||
value,
|
||
onToggle,
|
||
loading = false,
|
||
testID
|
||
}: {
|
||
icon: string;
|
||
title: string;
|
||
subtitle?: string;
|
||
value: boolean;
|
||
onToggle: (value: boolean) => void;
|
||
loading?: boolean;
|
||
testID?: string;
|
||
}) => {
|
||
const { colors } = useTheme();
|
||
return (
|
||
<View style={[styles.settingItem, { borderBottomColor: colors.border }]} testID={testID}>
|
||
<View style={[styles.settingIcon, { backgroundColor: colors.background }]}>
|
||
<Text style={styles.settingIconText}>{icon}</Text>
|
||
</View>
|
||
<View style={styles.settingContent}>
|
||
<Text style={[styles.settingTitle, { color: colors.text }]}>{title}</Text>
|
||
{subtitle && <Text style={[styles.settingSubtitle, { color: colors.textSecondary }]}>{subtitle}</Text>}
|
||
</View>
|
||
{loading ? (
|
||
<ActivityIndicator size="small" color={KurdistanColors.kesk} />
|
||
) : (
|
||
<Switch
|
||
value={value}
|
||
onValueChange={onToggle}
|
||
trackColor={{ false: '#E0E0E0', true: KurdistanColors.kesk }}
|
||
thumbColor={value ? KurdistanColors.spi : '#f4f3f4'}
|
||
testID={testID ? `${testID}-switch` : undefined}
|
||
/>
|
||
)}
|
||
</View>
|
||
);
|
||
};
|
||
|
||
// --- MAIN SCREEN ---
|
||
|
||
const SettingsScreen: React.FC = () => {
|
||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||
const { isDarkMode, toggleDarkMode, colors, fontSize, setFontSize } = useTheme();
|
||
const { isBiometricEnabled, enableBiometric, disableBiometric, biometricType, autoLockTimer, setAutoLockTimer } = useBiometricAuth();
|
||
const { signOut, user } = useAuth();
|
||
const { currentNetwork, switchNetwork, selectedAccount } = usePezkuwi();
|
||
|
||
// Profile State (Supabase)
|
||
const [profile, setProfile] = useState<UserProfile>({
|
||
full_name: '',
|
||
username: '',
|
||
notifications_push: false,
|
||
notifications_email: true,
|
||
});
|
||
const [_loadingProfile, setLoadingProfile] = useState(false);
|
||
const [savingSettings, setSavingSettings] = useState(false);
|
||
|
||
// Modals
|
||
const [showNetworkModal, setShowNetworkModal] = useState(false);
|
||
const [showProfileEdit, setShowProfileEdit] = useState(false);
|
||
const [showFontSizeModal, setShowFontSizeModal] = useState(false);
|
||
const [showAutoLockModal, setShowAutoLockModal] = useState(false);
|
||
const [showBackupModal, setShowBackupModal] = useState(false);
|
||
const [backupMnemonic, setBackupMnemonic] = useState('');
|
||
const [editName, setEditName] = useState('');
|
||
const [editBio, setEditBio] = useState('');
|
||
|
||
// 1. Fetch Profile from Supabase
|
||
const fetchProfile = useCallback(async () => {
|
||
if (!user) return;
|
||
setLoadingProfile(true);
|
||
try {
|
||
const { data, error } = await supabase
|
||
.from('profiles')
|
||
.select('*')
|
||
.eq('id', user.id)
|
||
.maybeSingle();
|
||
|
||
if (error) throw error;
|
||
if (data) {
|
||
setProfile(data);
|
||
setEditName(data.full_name || '');
|
||
setEditBio(data.bio || '');
|
||
}
|
||
} catch (_err) {
|
||
console.log('Error fetching profile:', _err);
|
||
} finally {
|
||
setLoadingProfile(false);
|
||
}
|
||
}, [user]);
|
||
|
||
useEffect(() => {
|
||
fetchProfile();
|
||
}, [fetchProfile]);
|
||
|
||
// 2. Update Settings in Supabase
|
||
const updateSetting = async (key: string, value: boolean) => {
|
||
if (!user) return;
|
||
setSavingSettings(true);
|
||
|
||
// Optimistic update
|
||
setProfile((prev) => ({ ...prev, [key]: value }));
|
||
|
||
try {
|
||
const { error } = await supabase
|
||
.from('profiles')
|
||
.update({ [key]: value, updated_at: new Date().toISOString() })
|
||
.eq('id', user.id);
|
||
|
||
if (error) throw error;
|
||
} catch (err) {
|
||
console.error('Failed to update setting:', err);
|
||
// Revert on error
|
||
setProfile((prev) => ({ ...prev, [key]: !value }));
|
||
showAlert('Error', 'Failed to save setting. Please check your connection.');
|
||
} finally {
|
||
setSavingSettings(false);
|
||
}
|
||
};
|
||
|
||
// 3. Save Profile Info
|
||
const saveProfileInfo = async () => {
|
||
if (!user) return;
|
||
try {
|
||
const { error } = await supabase
|
||
.from('profiles')
|
||
.update({
|
||
full_name: editName,
|
||
bio: editBio,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', user.id);
|
||
|
||
if (error) throw error;
|
||
|
||
setProfile((prev) => ({ ...prev, full_name: editName, bio: editBio }));
|
||
setShowProfileEdit(false);
|
||
showAlert('Success', 'Profile updated successfully');
|
||
} catch {
|
||
showAlert('Error', 'Failed to update profile');
|
||
}
|
||
};
|
||
|
||
// 4. Biometric Handler
|
||
const handleBiometryToggle = async (value: boolean) => {
|
||
// Biometric not available on web
|
||
if (Platform.OS === 'web') {
|
||
showAlert(
|
||
'Not Available',
|
||
'Biometric authentication is only available on mobile devices.'
|
||
);
|
||
return;
|
||
}
|
||
|
||
if (value) {
|
||
const success = await enableBiometric();
|
||
if (success) {
|
||
showAlert('Success', 'Biometric authentication enabled');
|
||
}
|
||
} else {
|
||
await disableBiometric();
|
||
}
|
||
};
|
||
|
||
// 5. Network Switcher
|
||
const handleNetworkChange = async (network: 'pezkuwi' | 'dicle' | 'zagros' | 'bizinikiwi' | 'zombienet') => {
|
||
await switchNetwork(network);
|
||
setShowNetworkModal(false);
|
||
|
||
showAlert(
|
||
'Network Changed',
|
||
`Switched to ${NETWORKS[network].displayName}. The app will reconnect automatically.`,
|
||
[{ text: 'OK' }]
|
||
);
|
||
};
|
||
|
||
// 6. Font Size Handler
|
||
const handleFontSizeChange = async (size: FontSize) => {
|
||
await setFontSize(size);
|
||
setShowFontSizeModal(false);
|
||
};
|
||
|
||
// 7. Auto-Lock Timer Handler
|
||
const handleAutoLockChange = async (minutes: number) => {
|
||
await setAutoLockTimer(minutes);
|
||
setShowAutoLockModal(false);
|
||
};
|
||
|
||
// Get display text for current font size
|
||
const getFontSizeLabel = () => {
|
||
const option = FONT_SIZE_OPTIONS.find(opt => opt.value === fontSize);
|
||
return option ? option.label : 'Medium';
|
||
};
|
||
|
||
// Get display text for current auto-lock timer
|
||
const getAutoLockLabel = () => {
|
||
const option = AUTO_LOCK_OPTIONS.find(opt => opt.value === autoLockTimer);
|
||
return option ? option.label : '5 minutes';
|
||
};
|
||
|
||
// 8. Wallet Backup Handler
|
||
const handleWalletBackup = 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}`;
|
||
let storedMnemonic: string | null = null;
|
||
|
||
if (Platform.OS === 'web') {
|
||
storedMnemonic = await AsyncStorage.getItem(seedKey);
|
||
} else {
|
||
storedMnemonic = await SecureStore.getItemAsync(seedKey);
|
||
}
|
||
|
||
if (storedMnemonic) {
|
||
setBackupMnemonic(storedMnemonic);
|
||
setShowBackupModal(true);
|
||
} else {
|
||
showAlert('No Backup', 'Recovery phrase not found. It may have been imported from another device.');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error retrieving mnemonic:', error);
|
||
showAlert('Error', 'Failed to retrieve recovery phrase.');
|
||
}
|
||
};
|
||
|
||
return (
|
||
<SafeAreaView style={[styles.container, { backgroundColor: colors.background }]} testID="settings-screen">
|
||
<StatusBar barStyle={isDarkMode ? "light-content" : "dark-content"} />
|
||
|
||
{/* Header */}
|
||
<View style={[styles.header, { backgroundColor: colors.surface, borderBottomColor: colors.border }]}>
|
||
<View style={{ width: 40 }} />
|
||
<Text style={[styles.headerTitle, { color: colors.text }]}>Settings</Text>
|
||
<View style={{ width: 40 }} />
|
||
</View>
|
||
|
||
<ScrollView showsVerticalScrollIndicator={false}>
|
||
|
||
{/* ACCOUNT SECTION */}
|
||
<SectionHeader title="ACCOUNT" />
|
||
<View style={[styles.section, { backgroundColor: colors.surface }]}>
|
||
<SettingItem
|
||
icon="👤"
|
||
title="Edit Profile"
|
||
subtitle={profile.full_name || user?.email || 'Set your name'}
|
||
onPress={() => setShowProfileEdit(true)}
|
||
testID="edit-profile-button"
|
||
/>
|
||
<SettingItem
|
||
icon="💳"
|
||
title="Wallet Management"
|
||
subtitle="Manage your connected keys"
|
||
onPress={() => navigation.navigate('Wallet')}
|
||
testID="wallet-management-button"
|
||
/>
|
||
</View>
|
||
|
||
{/* APP SETTINGS */}
|
||
<SectionHeader title="APP SETTINGS" />
|
||
<View style={[styles.section, { backgroundColor: colors.surface }]}>
|
||
<SettingToggle
|
||
icon="🌙"
|
||
title="Dark Mode"
|
||
subtitle={isDarkMode ? "Dark theme enabled" : "Light theme enabled"}
|
||
value={isDarkMode}
|
||
onToggle={toggleDarkMode}
|
||
testID="dark-mode"
|
||
/>
|
||
|
||
<SettingItem
|
||
icon="🔤"
|
||
title="Font Size"
|
||
subtitle={getFontSizeLabel()}
|
||
onPress={() => setShowFontSizeModal(true)}
|
||
testID="font-size-button"
|
||
/>
|
||
|
||
<SettingToggle
|
||
icon="🔔"
|
||
title="Push Notifications"
|
||
subtitle="Receive alerts about transactions"
|
||
value={profile.notifications_push}
|
||
onToggle={(val) => updateSetting('notifications_push', val)}
|
||
loading={savingSettings}
|
||
testID="push-notifications"
|
||
/>
|
||
|
||
<SettingToggle
|
||
icon="📧"
|
||
title="Email Updates"
|
||
subtitle="Receive newsletters & reports"
|
||
value={profile.notifications_email}
|
||
onToggle={(val) => updateSetting('notifications_email', val)}
|
||
loading={savingSettings}
|
||
testID="email-updates"
|
||
/>
|
||
</View>
|
||
|
||
{/* NETWORK & SECURITY */}
|
||
<SectionHeader title="NETWORK & SECURITY" />
|
||
<View style={[styles.section, { backgroundColor: colors.surface }]}>
|
||
<SettingItem
|
||
icon="📡"
|
||
title="Network Node"
|
||
subtitle={NETWORKS[currentNetwork]?.displayName || 'Unknown'}
|
||
onPress={() => setShowNetworkModal(true)}
|
||
testID="network-node-button"
|
||
/>
|
||
|
||
<SettingToggle
|
||
icon="🔐"
|
||
title="Biometric Security"
|
||
subtitle={isBiometricEnabled ? `Enabled (${biometricType})` : "FaceID / Fingerprint"}
|
||
value={isBiometricEnabled}
|
||
onToggle={handleBiometryToggle}
|
||
testID="biometric-security"
|
||
/>
|
||
|
||
<SettingItem
|
||
icon="⏱️"
|
||
title="Auto-Lock Timer"
|
||
subtitle={getAutoLockLabel()}
|
||
onPress={() => setShowAutoLockModal(true)}
|
||
testID="auto-lock-button"
|
||
/>
|
||
|
||
<SettingItem
|
||
icon="🔑"
|
||
title="Backup Recovery Phrase"
|
||
subtitle={selectedAccount ? "View your wallet's recovery phrase" : "No wallet connected"}
|
||
onPress={handleWalletBackup}
|
||
testID="backup-recovery-button"
|
||
/>
|
||
</View>
|
||
|
||
{/* SUPPORT */}
|
||
<SectionHeader title="SUPPORT" />
|
||
<View style={[styles.section, { backgroundColor: colors.surface }]}>
|
||
<SettingItem
|
||
icon="📄"
|
||
title="Terms of Service"
|
||
onPress={() => showAlert('Terms', 'Terms of service content...')}
|
||
testID="terms-of-service-button"
|
||
/>
|
||
<SettingItem
|
||
icon="🔒"
|
||
title="Privacy Policy"
|
||
onPress={() => showAlert('Privacy', 'Privacy policy content...')}
|
||
testID="privacy-policy-button"
|
||
/>
|
||
<SettingItem
|
||
icon="❓"
|
||
title="Help Center"
|
||
onPress={() => Linking.openURL('mailto:support@pezkuwichain.io')}
|
||
testID="help-center-button"
|
||
/>
|
||
</View>
|
||
|
||
{/* DEVELOPER OPTIONS (only in DEV) */}
|
||
{__DEV__ && (
|
||
<View style={[styles.section, { backgroundColor: colors.surface, marginTop: 20 }]}>
|
||
<Text style={[styles.sectionHeader, { color: colors.textSecondary }]}>DEVELOPER</Text>
|
||
<SettingItem
|
||
icon="🗑️"
|
||
title="Reset Wallet"
|
||
subtitle="Clear all wallet data"
|
||
textColor="#FF9500"
|
||
showArrow={false}
|
||
onPress={() => {
|
||
showAlert(
|
||
'Reset Wallet',
|
||
'This will delete all wallet data including saved accounts and keys. Are you sure?',
|
||
[
|
||
{ text: 'Cancel', style: 'cancel' },
|
||
{
|
||
text: 'Reset',
|
||
style: 'destructive',
|
||
onPress: async () => {
|
||
try {
|
||
await AsyncStorage.multiRemove([
|
||
'@pezkuwi_wallets',
|
||
'@pezkuwi_selected_account',
|
||
'@pezkuwi_selected_network'
|
||
]);
|
||
showAlert('Success', 'Wallet data cleared. Restart the app to see changes.');
|
||
} catch {
|
||
showAlert('Error', 'Failed to clear wallet data');
|
||
}
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}}
|
||
testID="reset-wallet-button"
|
||
/>
|
||
</View>
|
||
)}
|
||
|
||
{/* LOGOUT */}
|
||
<View style={[styles.section, { backgroundColor: colors.surface, marginTop: 20 }]}>
|
||
<SettingItem
|
||
icon="🚪"
|
||
title="Sign Out"
|
||
textColor="#FF3B30"
|
||
showArrow={false}
|
||
onPress={() => {
|
||
showAlert(
|
||
'Sign Out',
|
||
'Are you sure you want to sign out?',
|
||
[
|
||
{ text: 'Cancel', style: 'cancel' },
|
||
{ text: 'Sign Out', style: 'destructive', onPress: signOut }
|
||
]
|
||
);
|
||
}}
|
||
testID="sign-out-button"
|
||
/>
|
||
</View>
|
||
|
||
<View style={styles.footer}>
|
||
<Text style={[styles.versionText, { color: colors.textSecondary }]}>Pezkuwi Super App v1.0.0</Text>
|
||
<Text style={[styles.copyright, { color: colors.textSecondary }]}>© 2026 Digital Kurdistan</Text>
|
||
</View>
|
||
<View style={{ height: 40 }} />
|
||
</ScrollView>
|
||
|
||
{/* NETWORK MODAL */}
|
||
<Modal visible={showNetworkModal} transparent animationType="fade">
|
||
<View style={styles.modalOverlay}>
|
||
<View style={[styles.modalContent, { backgroundColor: colors.surface }]}>
|
||
<Text style={[styles.modalTitle, { color: colors.text }]}>Select Network</Text>
|
||
|
||
<TouchableOpacity
|
||
style={[styles.networkOption, currentNetwork === 'pezkuwi' && styles.selectedNetwork]}
|
||
onPress={() => handleNetworkChange('pezkuwi')}
|
||
testID="network-option-mainnet"
|
||
>
|
||
<Text style={styles.networkName}>{NETWORKS.pezkuwi.displayName}</Text>
|
||
<Text style={styles.networkUrl}>{NETWORKS.pezkuwi.rpcEndpoint}</Text>
|
||
</TouchableOpacity>
|
||
|
||
<TouchableOpacity
|
||
style={[styles.networkOption, currentNetwork === 'bizinikiwi' && styles.selectedNetwork]}
|
||
onPress={() => handleNetworkChange('bizinikiwi')}
|
||
testID="network-option-testnet"
|
||
>
|
||
<Text style={styles.networkName}>{NETWORKS.bizinikiwi.displayName}</Text>
|
||
<Text style={styles.networkUrl}>{NETWORKS.bizinikiwi.rpcEndpoint}</Text>
|
||
</TouchableOpacity>
|
||
|
||
<TouchableOpacity
|
||
style={[styles.networkOption, currentNetwork === 'zombienet' && styles.selectedNetwork]}
|
||
onPress={() => handleNetworkChange('zombienet')}
|
||
testID="network-option-zombienet"
|
||
>
|
||
<Text style={styles.networkName}>{NETWORKS.zombienet.displayName}</Text>
|
||
<Text style={styles.networkUrl}>{NETWORKS.zombienet.rpcEndpoint}</Text>
|
||
</TouchableOpacity>
|
||
|
||
<TouchableOpacity
|
||
style={styles.cancelButton}
|
||
onPress={() => setShowNetworkModal(false)}
|
||
testID="network-modal-cancel"
|
||
>
|
||
<Text style={styles.cancelText}>Cancel</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
</Modal>
|
||
|
||
{/* PROFILE EDIT MODAL */}
|
||
<Modal visible={showProfileEdit} animationType="slide">
|
||
<SafeAreaView style={[styles.fullModal, { backgroundColor: colors.background }]}>
|
||
<View style={[styles.header, { backgroundColor: colors.surface }]}>
|
||
<TouchableOpacity onPress={() => setShowProfileEdit(false)}>
|
||
<Text style={{ fontSize: 16, color: colors.text }}>Cancel</Text>
|
||
</TouchableOpacity>
|
||
<Text style={[styles.headerTitle, { color: colors.text }]}>Edit Profile</Text>
|
||
<TouchableOpacity onPress={saveProfileInfo}>
|
||
<Text style={{ fontSize: 16, color: KurdistanColors.kesk, fontWeight: 'bold' }}>Save</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
|
||
<View style={{ padding: 20 }}>
|
||
<Text style={[styles.label, { color: colors.textSecondary }]}>Full Name</Text>
|
||
<TextInput
|
||
style={[styles.input, { backgroundColor: colors.surface, color: colors.text, borderColor: colors.border }]}
|
||
value={editName}
|
||
onChangeText={setEditName}
|
||
placeholder="Enter your name"
|
||
placeholderTextColor="#999"
|
||
/>
|
||
|
||
<Text style={[styles.label, { color: colors.textSecondary, marginTop: 20 }]}>Bio</Text>
|
||
<TextInput
|
||
style={[styles.input, { backgroundColor: colors.surface, color: colors.text, borderColor: colors.border, height: 100 }]}
|
||
value={editBio}
|
||
onChangeText={setEditBio}
|
||
placeholder="Tell us about yourself"
|
||
placeholderTextColor="#999"
|
||
multiline
|
||
/>
|
||
</View>
|
||
</SafeAreaView>
|
||
</Modal>
|
||
|
||
{/* FONT SIZE MODAL */}
|
||
<Modal visible={showFontSizeModal} transparent animationType="fade" testID="font-size-modal">
|
||
<View style={styles.modalOverlay}>
|
||
<View style={[styles.modalContent, { backgroundColor: colors.surface }]}>
|
||
<Text style={[styles.modalTitle, { color: colors.text }]}>Select Font Size</Text>
|
||
|
||
{FONT_SIZE_OPTIONS.map((option) => (
|
||
<TouchableOpacity
|
||
key={option.value}
|
||
style={[styles.networkOption, fontSize === option.value && styles.selectedNetwork]}
|
||
onPress={() => handleFontSizeChange(option.value)}
|
||
testID={`font-size-option-${option.value}`}
|
||
>
|
||
<Text style={styles.networkName}>{option.label}</Text>
|
||
<Text style={styles.networkUrl}>{option.description}</Text>
|
||
</TouchableOpacity>
|
||
))}
|
||
|
||
<TouchableOpacity
|
||
style={styles.cancelButton}
|
||
onPress={() => setShowFontSizeModal(false)}
|
||
testID="font-size-modal-cancel"
|
||
>
|
||
<Text style={styles.cancelText}>Cancel</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
</Modal>
|
||
|
||
{/* AUTO-LOCK TIMER MODAL */}
|
||
<Modal visible={showAutoLockModal} transparent animationType="fade" testID="auto-lock-modal">
|
||
<View style={styles.modalOverlay}>
|
||
<View style={[styles.modalContent, { backgroundColor: colors.surface }]}>
|
||
<Text style={[styles.modalTitle, { color: colors.text }]}>Auto-Lock Timer</Text>
|
||
<Text style={[styles.modalSubtitle, { color: colors.textSecondary }]}>
|
||
Lock app after inactivity
|
||
</Text>
|
||
|
||
{AUTO_LOCK_OPTIONS.map((option) => (
|
||
<TouchableOpacity
|
||
key={option.value}
|
||
style={[styles.networkOption, autoLockTimer === option.value && styles.selectedNetwork]}
|
||
onPress={() => handleAutoLockChange(option.value)}
|
||
testID={`auto-lock-option-${option.value}`}
|
||
>
|
||
<Text style={styles.networkName}>{option.label}</Text>
|
||
</TouchableOpacity>
|
||
))}
|
||
|
||
<TouchableOpacity
|
||
style={styles.cancelButton}
|
||
onPress={() => setShowAutoLockModal(false)}
|
||
testID="auto-lock-modal-cancel"
|
||
>
|
||
<Text style={styles.cancelText}>Cancel</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
</Modal>
|
||
|
||
{/* WALLET BACKUP MODAL */}
|
||
<Modal visible={showBackupModal} transparent animationType="fade" testID="backup-modal">
|
||
<View style={styles.modalOverlay}>
|
||
<View style={[styles.modalContent, { backgroundColor: colors.surface }]}>
|
||
<Text style={[styles.modalTitle, { color: colors.text }]}>🔐 Recovery Phrase</Text>
|
||
<Text style={[styles.warningText, { color: '#EF4444' }]}>
|
||
⚠️ NEVER share this with anyone! Write it down and store safely.
|
||
</Text>
|
||
|
||
<View style={styles.mnemonicContainer}>
|
||
<Text style={[styles.mnemonicText, { color: colors.text }]}>{backupMnemonic}</Text>
|
||
</View>
|
||
|
||
<View style={styles.backupActions}>
|
||
<TouchableOpacity
|
||
style={styles.copyButton}
|
||
onPress={() => {
|
||
Clipboard.setString(backupMnemonic);
|
||
showAlert('Copied', 'Recovery phrase copied to clipboard');
|
||
}}
|
||
>
|
||
<Text style={styles.copyButtonText}>📋 Copy</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
|
||
<TouchableOpacity
|
||
style={[styles.confirmButton, { backgroundColor: KurdistanColors.kesk }]}
|
||
onPress={() => {
|
||
setShowBackupModal(false);
|
||
setBackupMnemonic('');
|
||
}}
|
||
>
|
||
<Text style={styles.confirmButtonText}>Done</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
</Modal>
|
||
|
||
</SafeAreaView>
|
||
);
|
||
};
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
},
|
||
header: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
paddingHorizontal: 16,
|
||
paddingVertical: 12,
|
||
borderBottomWidth: 1,
|
||
},
|
||
backButton: {
|
||
width: 40,
|
||
height: 40,
|
||
justifyContent: 'center',
|
||
},
|
||
backButtonText: {
|
||
fontSize: 24,
|
||
color: KurdistanColors.kesk,
|
||
fontWeight: 'bold',
|
||
},
|
||
headerTitle: {
|
||
fontSize: 18,
|
||
fontWeight: 'bold',
|
||
},
|
||
sectionHeader: {
|
||
marginTop: 24,
|
||
marginBottom: 8,
|
||
paddingHorizontal: 16,
|
||
},
|
||
sectionTitle: {
|
||
fontSize: 12,
|
||
fontWeight: '700',
|
||
letterSpacing: 0.5,
|
||
},
|
||
section: {
|
||
marginHorizontal: 16,
|
||
borderRadius: 12,
|
||
overflow: 'hidden',
|
||
},
|
||
settingItem: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
padding: 16,
|
||
borderBottomWidth: 1,
|
||
},
|
||
settingIcon: {
|
||
width: 32,
|
||
height: 32,
|
||
borderRadius: 8,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginRight: 12,
|
||
},
|
||
settingIconText: {
|
||
fontSize: 18,
|
||
},
|
||
settingContent: {
|
||
flex: 1,
|
||
},
|
||
settingTitle: {
|
||
fontSize: 16,
|
||
fontWeight: '500',
|
||
},
|
||
settingSubtitle: {
|
||
fontSize: 13,
|
||
marginTop: 2,
|
||
},
|
||
arrow: {
|
||
fontSize: 18,
|
||
},
|
||
footer: {
|
||
alignItems: 'center',
|
||
marginTop: 32,
|
||
},
|
||
versionText: {
|
||
fontSize: 13,
|
||
fontWeight: '600',
|
||
},
|
||
copyright: {
|
||
fontSize: 11,
|
||
marginTop: 4,
|
||
},
|
||
// Modal Styles
|
||
modalOverlay: {
|
||
flex: 1,
|
||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||
justifyContent: 'center',
|
||
padding: 20,
|
||
},
|
||
modalContent: {
|
||
borderRadius: 16,
|
||
padding: 20,
|
||
},
|
||
modalTitle: {
|
||
fontSize: 18,
|
||
fontWeight: 'bold',
|
||
marginBottom: 20,
|
||
textAlign: 'center',
|
||
},
|
||
modalSubtitle: {
|
||
fontSize: 13,
|
||
textAlign: 'center',
|
||
marginTop: -12,
|
||
marginBottom: 16,
|
||
},
|
||
networkOption: {
|
||
padding: 16,
|
||
borderRadius: 12,
|
||
backgroundColor: '#f5f5f5',
|
||
marginBottom: 10,
|
||
borderWidth: 1,
|
||
borderColor: '#eee',
|
||
},
|
||
selectedNetwork: {
|
||
borderColor: KurdistanColors.kesk,
|
||
backgroundColor: '#e8f5e9',
|
||
},
|
||
networkName: {
|
||
fontWeight: 'bold',
|
||
fontSize: 16,
|
||
color: '#333',
|
||
},
|
||
networkUrl: {
|
||
fontSize: 12,
|
||
color: '#666',
|
||
marginTop: 2,
|
||
},
|
||
cancelButton: {
|
||
marginTop: 10,
|
||
padding: 16,
|
||
alignItems: 'center',
|
||
},
|
||
cancelText: {
|
||
color: '#FF3B30',
|
||
fontWeight: '600',
|
||
fontSize: 16,
|
||
},
|
||
fullModal: {
|
||
flex: 1,
|
||
},
|
||
label: {
|
||
fontSize: 14,
|
||
fontWeight: '600',
|
||
marginBottom: 8,
|
||
},
|
||
input: {
|
||
borderWidth: 1,
|
||
borderRadius: 8,
|
||
padding: 12,
|
||
fontSize: 16,
|
||
},
|
||
// Backup Modal Styles
|
||
warningText: {
|
||
fontSize: 12,
|
||
textAlign: 'center',
|
||
marginBottom: 16,
|
||
fontWeight: '600',
|
||
},
|
||
mnemonicContainer: {
|
||
backgroundColor: '#FEF9E7',
|
||
padding: 16,
|
||
borderRadius: 12,
|
||
marginBottom: 16,
|
||
borderWidth: 1,
|
||
borderColor: '#F5D76E',
|
||
},
|
||
mnemonicText: {
|
||
fontSize: 14,
|
||
lineHeight: 24,
|
||
textAlign: 'center',
|
||
},
|
||
backupActions: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'center',
|
||
marginBottom: 16,
|
||
},
|
||
copyButton: {
|
||
backgroundColor: '#F5F5F5',
|
||
paddingVertical: 12,
|
||
paddingHorizontal: 24,
|
||
borderRadius: 8,
|
||
},
|
||
copyButtonText: {
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
color: '#333',
|
||
},
|
||
confirmButton: {
|
||
width: '100%',
|
||
paddingVertical: 16,
|
||
borderRadius: 12,
|
||
alignItems: 'center',
|
||
},
|
||
confirmButtonText: {
|
||
color: '#FFFFFF',
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
},
|
||
});
|
||
|
||
export default SettingsScreen; |