mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-21 23:47:56 +00:00
chore: Fix 430+ TS errors, Safe Area issues, WebView SSO, and mock Alice for testing
This commit is contained in:
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "Pezkuwi",
|
||||
"slug": "pezkuwi",
|
||||
"name": "PezkuwiApp",
|
||||
"slug": "pezkuwiapp",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
|
||||
+6
-6
@@ -13,8 +13,8 @@ if (__DEV__) console.warn('📦 [INDEX] Setting up Buffer...');
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
// Global polyfills for Polkadot.js
|
||||
// @ts-expect-error Global Buffer assignment for polyfill
|
||||
global.Buffer = Buffer;
|
||||
// Global Buffer assignment for polyfill
|
||||
global.Buffer = global.Buffer || require('buffer').Buffer;
|
||||
if (__DEV__) console.warn('✅ [INDEX] Buffer configured');
|
||||
|
||||
// TextEncoder/TextDecoder polyfill
|
||||
@@ -22,10 +22,10 @@ if (__DEV__) console.warn('📦 [INDEX] Setting up TextEncoder/TextDecoder...');
|
||||
if (typeof global.TextEncoder === 'undefined') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { TextEncoder, TextDecoder } = require('text-encoding');
|
||||
// @ts-expect-error Global TextEncoder assignment for polyfill
|
||||
global.TextEncoder = TextEncoder;
|
||||
// @ts-expect-error Global TextDecoder assignment for polyfill
|
||||
global.TextDecoder = TextDecoder;
|
||||
// Global TextEncoder assignment for polyfill
|
||||
global.TextEncoder = require('text-encoding').TextEncoder;
|
||||
// Global TextDecoder assignment for polyfill
|
||||
global.TextDecoder = require('text-encoding').TextDecoder;
|
||||
if (__DEV__) console.warn('✅ [INDEX] TextEncoder/TextDecoder configured');
|
||||
} else {
|
||||
if (__DEV__) console.warn('ℹ️ [INDEX] TextEncoder/TextDecoder already available');
|
||||
|
||||
@@ -16,8 +16,8 @@ const projectRoot = __dirname;
|
||||
// ============================================
|
||||
// CUSTOM MODULE RESOLUTION
|
||||
// ============================================
|
||||
// DISABLED: Custom resolver causes empty-module.js resolution issues
|
||||
// Using npm packages directly instead
|
||||
// Note: @pezkuwi packages have incorrect main field in npm (cjs/cjs/index.js)
|
||||
// Fixed via postinstall script or manual patch
|
||||
|
||||
// ============================================
|
||||
// FILE EXTENSIONS
|
||||
|
||||
+14
-14
@@ -32,9 +32,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@pezkuwi/api": "^16.5.9",
|
||||
"@pezkuwi/api": "^16.5.11",
|
||||
"@pezkuwi/keyring": "14.0.11",
|
||||
"@pezkuwi/types": "16.5.9",
|
||||
"@pezkuwi/types": "^16.5.11",
|
||||
"@pezkuwi/util": "14.0.11",
|
||||
"@pezkuwi/util-crypto": "14.0.11",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
@@ -74,17 +74,17 @@
|
||||
},
|
||||
"overrides": {
|
||||
"react-test-renderer": "19.1.0",
|
||||
"@pezkuwi/api-augment": "16.5.9",
|
||||
"@pezkuwi/api-base": "16.5.9",
|
||||
"@pezkuwi/api-derive": "16.5.9",
|
||||
"@pezkuwi/rpc-augment": "16.5.9",
|
||||
"@pezkuwi/rpc-core": "16.5.9",
|
||||
"@pezkuwi/rpc-provider": "16.5.9",
|
||||
"@pezkuwi/types": "16.5.9",
|
||||
"@pezkuwi/types-augment": "16.5.9",
|
||||
"@pezkuwi/types-codec": "16.5.9",
|
||||
"@pezkuwi/types-create": "16.5.9",
|
||||
"@pezkuwi/types-known": "16.5.9",
|
||||
"@pezkuwi/api-augment": "^16.5.11",
|
||||
"@pezkuwi/api-base": "^16.5.11",
|
||||
"@pezkuwi/api-derive": "^16.5.11",
|
||||
"@pezkuwi/rpc-augment": "^16.5.11",
|
||||
"@pezkuwi/rpc-core": "^16.5.11",
|
||||
"@pezkuwi/rpc-provider": "^16.5.11",
|
||||
"@pezkuwi/types": "^16.5.11",
|
||||
"@pezkuwi/types-augment": "^16.5.11",
|
||||
"@pezkuwi/types-codec": "^16.5.11",
|
||||
"@pezkuwi/types-create": "^16.5.11",
|
||||
"@pezkuwi/types-known": "^16.5.11",
|
||||
"@pezkuwi/networks": "14.0.11",
|
||||
"@pezkuwi/keyring": "14.0.11",
|
||||
"@pezkuwi/util": "14.0.11",
|
||||
@@ -92,7 +92,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/ngrok": "^4.1.0",
|
||||
"react-test-renderer": "19.1.0",
|
||||
"@pezkuwi/extension-dapp": "^0.62.20",
|
||||
"@pezkuwi/extension-inject": "^0.62.20",
|
||||
"@testing-library/jest-native": "^5.4.3",
|
||||
@@ -111,6 +110,7 @@
|
||||
"globals": "^16.5.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-expo": "^54.0.13",
|
||||
"react-test-renderer": "19.1.0",
|
||||
"typescript": "~5.9.2",
|
||||
"typescript-eslint": "^8.47.0"
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Image,
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import { KurdistanColors } from '../theme/colors';
|
||||
@@ -21,7 +22,7 @@ const showAlert = (title: string, message: string, buttons?: Array<{text: string
|
||||
window.alert(`${title}\n\n${message}`);
|
||||
if (buttons?.[0]?.onPress) buttons[0].onPress();
|
||||
} else {
|
||||
showAlert(title, message, buttons);
|
||||
Alert.alert(title, message, buttons);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -109,7 +110,7 @@ const AvatarPickerModal: React.FC<AvatarPickerModalProps> = ({
|
||||
setIsUploading(true);
|
||||
const imageUri = result.assets[0].uri;
|
||||
|
||||
if (__DEV__) console.log('[AvatarPicker] Uploading image:', imageUri);
|
||||
if (__DEV__) console.warn('[AvatarPicker] Uploading image:', imageUri);
|
||||
|
||||
// Upload to Supabase Storage
|
||||
const uploadedUrl = await uploadImageToSupabase(imageUri);
|
||||
@@ -117,7 +118,7 @@ const AvatarPickerModal: React.FC<AvatarPickerModalProps> = ({
|
||||
setIsUploading(false);
|
||||
|
||||
if (uploadedUrl) {
|
||||
if (__DEV__) console.log('[AvatarPicker] Upload successful:', uploadedUrl);
|
||||
if (__DEV__) console.warn('[AvatarPicker] Upload successful:', uploadedUrl);
|
||||
setUploadedImageUri(uploadedUrl);
|
||||
setSelectedAvatar(null); // Clear emoji selection
|
||||
showAlert('Success', 'Photo uploaded successfully!');
|
||||
@@ -215,7 +216,7 @@ const AvatarPickerModal: React.FC<AvatarPickerModalProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__) console.log('[AvatarPicker] Saving avatar:', avatarToSave);
|
||||
if (__DEV__) console.warn('[AvatarPicker] Saving avatar:', avatarToSave);
|
||||
|
||||
setIsSaving(true);
|
||||
|
||||
@@ -232,7 +233,7 @@ const AvatarPickerModal: React.FC<AvatarPickerModalProps> = ({
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (__DEV__) console.log('[AvatarPicker] Avatar saved successfully:', data);
|
||||
if (__DEV__) console.warn('[AvatarPicker] Avatar saved successfully:', data);
|
||||
|
||||
showAlert('Success', 'Avatar updated successfully!');
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export const Input: React.FC<InputProps> = ({
|
||||
<TextInput
|
||||
{...props}
|
||||
editable={props.editable !== undefined ? props.editable : !disabled}
|
||||
style={[styles.input, leftIcon && styles.inputWithLeftIcon, style]}
|
||||
style={[styles.input, !!leftIcon && styles.inputWithLeftIcon, style]}
|
||||
onFocus={(e) => {
|
||||
setIsFocused(true);
|
||||
props.onFocus?.(e);
|
||||
|
||||
@@ -49,7 +49,7 @@ export const Skeleton: React.FC<SkeletonProps> = ({
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.skeleton,
|
||||
{ width, height, borderRadius, opacity },
|
||||
{ width: width as any, height, borderRadius, opacity },
|
||||
style,
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { KurdistanColors } from '../theme/colors';
|
||||
import { usePezkuwi } from '../contexts/PezkuwiContext';
|
||||
import { supabaseHelpers } from '../lib/supabase';
|
||||
@@ -32,6 +33,7 @@ export const NotificationCenterModal: React.FC<NotificationCenterModalProps> = (
|
||||
visible,
|
||||
onClose,
|
||||
}) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const { selectedAccount } = usePezkuwi();
|
||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||
const [_loading, setLoading] = useState(false);
|
||||
@@ -178,7 +180,7 @@ export const NotificationCenterModal: React.FC<NotificationCenterModalProps> = (
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={[styles.modalContent, { paddingBottom: Math.max(insets.bottom, 20) }]}>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<View>
|
||||
|
||||
@@ -14,6 +14,8 @@ import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||
import type { NavigationProp } from '@react-navigation/native';
|
||||
import { KurdistanColors } from '../theme/colors';
|
||||
import { usePezkuwi } from '../contexts/PezkuwiContext';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { supabase } from '../lib/supabase';
|
||||
|
||||
type RootStackParamList = {
|
||||
Wallet: undefined;
|
||||
@@ -49,6 +51,25 @@ const PezkuwiWebView: React.FC<PezkuwiWebViewProps> = ({
|
||||
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { selectedAccount, getKeyPair, api, isApiReady } = usePezkuwi();
|
||||
const { user } = useAuth();
|
||||
const [sessionToken, setSessionToken] = useState<string | null>(null);
|
||||
const [refreshToken, setRefreshToken] = useState<string | null>(null);
|
||||
|
||||
// Get Supabase session token for WebView authentication
|
||||
React.useEffect(() => {
|
||||
const getSession = async () => {
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (session?.access_token) {
|
||||
setSessionToken(session.access_token);
|
||||
setRefreshToken(session.refresh_token || null);
|
||||
}
|
||||
} catch (error) {
|
||||
if (__DEV__) console.warn('[WebView] Failed to get session:', error);
|
||||
}
|
||||
};
|
||||
getSession();
|
||||
}, [user]);
|
||||
|
||||
// JavaScript to inject into the WebView
|
||||
// This creates a bridge between the web app and native app
|
||||
@@ -62,6 +83,36 @@ const PezkuwiWebView: React.FC<PezkuwiWebViewProps> = ({
|
||||
${selectedAccount ? `window.PEZKUWI_ADDRESS = '${selectedAccount.address}';` : ''}
|
||||
${selectedAccount ? `window.PEZKUWI_ACCOUNT_NAME = '${selectedAccount.meta?.name || 'Mobile Wallet'}';` : ''}
|
||||
|
||||
// Inject auth session for automatic login
|
||||
${sessionToken ? `window.PEZKUWI_SESSION_TOKEN = '${sessionToken}';` : ''}
|
||||
${refreshToken ? `window.PEZKUWI_REFRESH_TOKEN = '${refreshToken}';` : ''}
|
||||
${user ? `window.PEZKUWI_USER_ID = '${user.id}';` : ''}
|
||||
${user?.email ? `window.PEZKUWI_USER_EMAIL = '${user.email}';` : ''}
|
||||
|
||||
// Auto-authenticate with Supabase if session token exists
|
||||
if (window.PEZKUWI_SESSION_TOKEN) {
|
||||
(function autoAuth(attempts = 0) {
|
||||
if (attempts > 50) {
|
||||
console.warn('[Mobile] Auto-auth timed out: window.supabase not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.supabase && window.supabase.auth) {
|
||||
window.supabase.auth.setSession({
|
||||
access_token: window.PEZKUWI_SESSION_TOKEN,
|
||||
refresh_token: window.PEZKUWI_REFRESH_TOKEN || ''
|
||||
}).then(function(res) {
|
||||
if (res.error) console.warn('[Mobile] Auto-auth error:', res.error);
|
||||
else console.log('[Mobile] Auto-authenticated successfully');
|
||||
}).catch(function(err) {
|
||||
console.warn('[Mobile] Auto-auth promise failed:', err);
|
||||
});
|
||||
} else {
|
||||
setTimeout(function() { autoAuth(attempts + 1); }, 100);
|
||||
}
|
||||
})(0);
|
||||
}
|
||||
|
||||
// Override console.log to send to React Native (for debugging)
|
||||
const originalConsoleLog = console.log;
|
||||
console.log = function(...args) {
|
||||
@@ -164,7 +215,7 @@ const PezkuwiWebView: React.FC<PezkuwiWebViewProps> = ({
|
||||
const { section, method, args } = payload;
|
||||
|
||||
if (__DEV__) {
|
||||
console.log('[WebView] Building transaction:', { section, method, args });
|
||||
console.warn('[WebView] Building transaction:', { section, method, args });
|
||||
}
|
||||
|
||||
// Get the transaction method from API
|
||||
@@ -187,13 +238,13 @@ const PezkuwiWebView: React.FC<PezkuwiWebViewProps> = ({
|
||||
if (result.status.isInBlock) {
|
||||
const hash = result.status.asInBlock?.toString() || '';
|
||||
if (__DEV__) {
|
||||
console.log('[WebView] Transaction included in block:', hash);
|
||||
console.warn('[WebView] Transaction included in block:', hash);
|
||||
}
|
||||
resolve(hash);
|
||||
} else if (result.status.isFinalized) {
|
||||
const hash = result.status.asFinalized?.toString() || '';
|
||||
if (__DEV__) {
|
||||
console.log('[WebView] Transaction finalized:', hash);
|
||||
console.warn('[WebView] Transaction finalized:', hash);
|
||||
}
|
||||
}
|
||||
if (result.dispatchError) {
|
||||
@@ -222,7 +273,7 @@ const PezkuwiWebView: React.FC<PezkuwiWebViewProps> = ({
|
||||
|
||||
case 'CONNECT_WALLET':
|
||||
// Handle wallet connection request from WebView
|
||||
if (__DEV__) console.log('WebView requested wallet connection');
|
||||
if (__DEV__) console.warn('WebView requested wallet connection');
|
||||
|
||||
if (selectedAccount) {
|
||||
// Already connected, notify WebView
|
||||
@@ -267,13 +318,13 @@ const PezkuwiWebView: React.FC<PezkuwiWebViewProps> = ({
|
||||
case 'CONSOLE_LOG':
|
||||
// Forward console logs from WebView (debug only)
|
||||
if (__DEV__) {
|
||||
console.log('[WebView]:', message.payload);
|
||||
console.warn('[WebView]:', message.payload);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (__DEV__) {
|
||||
console.log('Unknown message type:', message.type);
|
||||
console.warn('Unknown message type:', message.type);
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
|
||||
@@ -25,7 +25,7 @@ export function ValidatorSelectionSheet({
|
||||
onClose,
|
||||
onConfirmNominations,
|
||||
}: ValidatorSelectionSheetProps) {
|
||||
const { api, isApiReady, _selectedAccount } = usePezkuwi();
|
||||
const { api, isApiReady } = usePezkuwi();
|
||||
const [validators, setValidators] = useState<Validator[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [processing, _setProcessing] = useState(false);
|
||||
@@ -42,29 +42,28 @@ export function ValidatorSelectionSheet({
|
||||
// Attempt to fetch from pallet-validator-pool first
|
||||
if (api.query.validatorPool && api.query.validatorPool.validators) {
|
||||
const rawValidators = await api.query.validatorPool.validators();
|
||||
// Assuming rawValidators is a list of validator addresses or objects
|
||||
// This parsing logic will need adjustment based on the exact structure returned
|
||||
for (const rawValidator of rawValidators.toHuman() as unknown[]) {
|
||||
// Placeholder: Assume rawValidator is just an address for now
|
||||
const validatorList = rawValidators.toHuman() as string[];
|
||||
for (const rawValidator of validatorList) {
|
||||
chainValidators.push({
|
||||
address: rawValidator.toString(), // or rawValidator.address if it's an object
|
||||
commission: 0.05, // Placeholder: Fetch actual commission
|
||||
totalStake: '0 HEZ', // Placeholder: Fetch actual stake
|
||||
selfStake: '0 HEZ', // Placeholder: Fetch actual self stake
|
||||
nominators: 0, // Placeholder: Fetch actual nominators
|
||||
address: String(rawValidator),
|
||||
commission: 0.05,
|
||||
totalStake: '0 HEZ',
|
||||
selfStake: '0 HEZ',
|
||||
nominators: 0,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Fallback to general staking validators if validatorPool pallet is not found/used
|
||||
const rawStakingValidators = await api.query.staking.validators() as { keys?: { args: unknown[] }[] };
|
||||
for (const validatorAddress of (rawStakingValidators.keys || [])) {
|
||||
const address = validatorAddress.args[0].toString();
|
||||
// Fetch more details about each validator if needed, e.g., commission, total stake
|
||||
const validatorPrefs = await api.query.staking.validators(address) as { commission: { toNumber: () => number } };
|
||||
const commission = validatorPrefs.commission.toNumber() / 10_000_000; // Assuming 10^7 for percentage
|
||||
// Fallback to session validators
|
||||
const sessionValidators = await api.query.session.validators();
|
||||
const validatorAddresses = sessionValidators.toJSON() as string[];
|
||||
|
||||
for (const address of validatorAddresses) {
|
||||
const validatorPrefs = await api.query.staking.validators(address);
|
||||
const prefsJson = validatorPrefs.toJSON() as { commission?: number } | null;
|
||||
const commission = prefsJson?.commission
|
||||
? Number(prefsJson.commission) / 1_000_000_000
|
||||
: 0.05;
|
||||
|
||||
// For simplicity, total stake and nominators are placeholders for now
|
||||
// A more complete implementation would query for detailed exposure
|
||||
chainValidators.push({
|
||||
address: address,
|
||||
commission: commission,
|
||||
|
||||
@@ -56,9 +56,9 @@ export const AddTokenModal: React.FC<AddTokenModalProps> = ({
|
||||
} else {
|
||||
const metadata = metadataOption.toJSON() as { symbol?: string; decimals?: number; name?: string } | null;
|
||||
setTokenMetadata({
|
||||
symbol: metadata.symbol || 'UNKNOWN',
|
||||
decimals: metadata.decimals || 12,
|
||||
name: metadata.name || 'Unknown Token',
|
||||
symbol: metadata?.symbol || 'UNKNOWN',
|
||||
decimals: metadata?.decimals || 12,
|
||||
name: metadata?.name || 'Unknown Token',
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
TextInput,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
ActivityIndicator,
|
||||
Modal,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { usePezkuwi } from '../contexts/PezkuwiContext';
|
||||
import { uploadToIPFS, FOUNDER_ADDRESS } from '../../shared/lib/citizenship-workflow';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { SafeAreaView, StyleSheet } from 'react-native';
|
||||
import { PezkuwiWebView } from '../components';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
/**
|
||||
* Be Citizen Screen
|
||||
|
||||
@@ -252,9 +252,9 @@ const DashboardScreen: React.FC<DashboardScreenProps> = () => {
|
||||
>
|
||||
<View style={[styles.appIconBox, comingSoon && styles.appIconDisabled]}>
|
||||
{isEmoji ? (
|
||||
<Text style={styles.emojiIcon}>{icon}</Text>
|
||||
<Text style={styles.emojiIcon}>{icon as string}</Text>
|
||||
) : (
|
||||
<Image source={icon} style={styles.imageIcon} resizeMode="cover" />
|
||||
<Image source={icon as ImageSourcePropType} style={styles.imageIcon} resizeMode="cover" />
|
||||
)}
|
||||
{comingSoon && (
|
||||
<View style={styles.lockBadge}>
|
||||
@@ -468,7 +468,7 @@ const DashboardScreen: React.FC<DashboardScreenProps> = () => {
|
||||
{kycStatus === 'NotStarted' && (
|
||||
<TouchableOpacity
|
||||
style={styles.kycButton}
|
||||
onPress={() => navigation.navigate('BeCitizen')}
|
||||
onPress={() => navigation.navigate('BeCitizenChoice')}
|
||||
>
|
||||
<Text style={styles.kycButtonText}>Apply</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { SafeAreaView, StyleSheet } from 'react-native';
|
||||
import { PezkuwiWebView } from '../components';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
/**
|
||||
* Education (Perwerde) Screen
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { SafeAreaView, StyleSheet } from 'react-native';
|
||||
import { PezkuwiWebView } from '../components';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
/**
|
||||
* Forum Screen
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { SafeAreaView, StyleSheet } from 'react-native';
|
||||
import { PezkuwiWebView } from '../components';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
/**
|
||||
* Governance Screen
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { SafeAreaView, StyleSheet } from 'react-native';
|
||||
import { PezkuwiWebView } from '../components';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
/**
|
||||
* P2P Trading Screen
|
||||
|
||||
@@ -61,7 +61,7 @@ type TabType = 'courses' | 'enrolled' | 'completed';
|
||||
|
||||
const PerwerdeScreen: React.FC = () => {
|
||||
const _navigation = useNavigation();
|
||||
const { selectedAccount, api, isApiReady } = usePezkuwi();
|
||||
const { selectedAccount, api, isApiReady, getKeyPair } = usePezkuwi();
|
||||
const isConnected = !!selectedAccount;
|
||||
|
||||
// State
|
||||
@@ -199,11 +199,17 @@ const PerwerdeScreen: React.FC = () => {
|
||||
|
||||
try {
|
||||
const extrinsic = api.tx.perwerde.enroll(courseId);
|
||||
const keypair = await getKeyPair(selectedAccount.address);
|
||||
|
||||
if (!keypair) {
|
||||
Alert.alert('Xeletî / Error', 'Could not get signer');
|
||||
setEnrolling(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
extrinsic.signAndSend(
|
||||
selectedAccount.address,
|
||||
{ signer: selectedAccount.signer },
|
||||
keypair,
|
||||
({ status, dispatchError }) => {
|
||||
if (dispatchError) {
|
||||
if (dispatchError.isModule) {
|
||||
@@ -767,7 +773,7 @@ const styles = StyleSheet.create({
|
||||
statusAvailable: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: KurdistanColors.yer,
|
||||
color: KurdistanColors.zer,
|
||||
},
|
||||
statusEnrolled: {
|
||||
fontSize: 12,
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
Alert,
|
||||
@@ -15,8 +14,10 @@ import {
|
||||
TextInput,
|
||||
Platform
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import * as LocalAuthentication from 'expo-local-authentication';
|
||||
import { Clipboard } from 'react-native';
|
||||
import { useNavigation, NavigationProp } from '@react-navigation/native';
|
||||
import type { AlertButton } from 'react-native';
|
||||
@@ -76,10 +77,10 @@ const AUTO_LOCK_OPTIONS: { value: number; label: string }[] = [
|
||||
// --- COMPONENTS (Internal for simplicity) ---
|
||||
|
||||
const SectionHeader = ({ title }: { title: string }) => {
|
||||
const { colors } = useTheme();
|
||||
const { colors, fontScale } = useTheme();
|
||||
return (
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text style={[styles.sectionTitle, { color: colors.textSecondary }]}>{title}</Text>
|
||||
<Text style={[styles.sectionTitle, { color: colors.textSecondary, fontSize: 12 * fontScale }]}>{title}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -101,7 +102,7 @@ const SettingItem = ({
|
||||
textColor?: string;
|
||||
testID?: string;
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
const { colors, fontScale } = useTheme();
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.settingItem, { borderBottomColor: colors.border }]}
|
||||
@@ -112,8 +113,8 @@ const SettingItem = ({
|
||||
<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>}
|
||||
<Text style={[styles.settingTitle, { color: textColor || colors.text, fontSize: 16 * fontScale }]}>{title}</Text>
|
||||
{subtitle && <Text style={[styles.settingSubtitle, { color: colors.textSecondary, fontSize: 13 * fontScale }]}>{subtitle}</Text>}
|
||||
</View>
|
||||
{showArrow && <Text style={[styles.arrow, { color: colors.textSecondary }]}>→</Text>}
|
||||
</TouchableOpacity>
|
||||
@@ -137,15 +138,15 @@ const SettingToggle = ({
|
||||
loading?: boolean;
|
||||
testID?: string;
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
const { colors, fontScale } = 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>}
|
||||
<Text style={[styles.settingTitle, { color: colors.text, fontSize: 16 * fontScale }]}>{title}</Text>
|
||||
{subtitle && <Text style={[styles.settingSubtitle, { color: colors.textSecondary, fontSize: 13 * fontScale }]}>{subtitle}</Text>}
|
||||
</View>
|
||||
{loading ? (
|
||||
<ActivityIndicator size="small" color={KurdistanColors.kesk} />
|
||||
@@ -187,6 +188,8 @@ const SettingsScreen: React.FC = () => {
|
||||
const [showFontSizeModal, setShowFontSizeModal] = useState(false);
|
||||
const [showAutoLockModal, setShowAutoLockModal] = useState(false);
|
||||
const [showBackupModal, setShowBackupModal] = useState(false);
|
||||
const [showTermsModal, setShowTermsModal] = useState(false);
|
||||
const [showPrivacyModal, setShowPrivacyModal] = useState(false);
|
||||
const [backupMnemonic, setBackupMnemonic] = useState('');
|
||||
const [editName, setEditName] = useState('');
|
||||
const [editBio, setEditBio] = useState('');
|
||||
@@ -209,7 +212,7 @@ const SettingsScreen: React.FC = () => {
|
||||
setEditBio(data.bio || '');
|
||||
}
|
||||
} catch (_err) {
|
||||
console.log('Error fetching profile:', _err);
|
||||
if (__DEV__) console.warn('Error fetching profile:', _err);
|
||||
} finally {
|
||||
setLoadingProfile(false);
|
||||
}
|
||||
@@ -324,34 +327,68 @@ const SettingsScreen: React.FC = () => {
|
||||
return option ? option.label : '5 minutes';
|
||||
};
|
||||
|
||||
// 8. Wallet Backup Handler
|
||||
// 8. Wallet Backup Handler - with security confirmation
|
||||
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;
|
||||
// Security warning before showing recovery phrase
|
||||
showAlert(
|
||||
'⚠️ Security Warning',
|
||||
'Your recovery phrase is the only way to restore your wallet. NEVER share it with anyone.\n\n• Anyone with this phrase can steal your funds\n• Pezkuwi will NEVER ask for your phrase\n• Write it down and store it safely\n\nDo you want to continue?',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'I Understand',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
// If biometric is enabled, require authentication
|
||||
if (isBiometricEnabled && Platform.OS !== 'web') {
|
||||
try {
|
||||
const result = await LocalAuthentication.authenticateAsync({
|
||||
promptMessage: 'Authenticate to view recovery phrase',
|
||||
cancelLabel: 'Cancel',
|
||||
disableDeviceFallback: false,
|
||||
});
|
||||
|
||||
if (Platform.OS === 'web') {
|
||||
storedMnemonic = await AsyncStorage.getItem(seedKey);
|
||||
} else {
|
||||
storedMnemonic = await SecureStore.getItemAsync(seedKey);
|
||||
}
|
||||
if (!result.success) {
|
||||
showAlert('Authentication Failed', 'Could not verify your identity.');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Biometric auth error:', error);
|
||||
showAlert('Error', 'Authentication failed.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
// Retrieve mnemonic from secure storage
|
||||
try {
|
||||
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 (
|
||||
@@ -378,10 +415,10 @@ const SettingsScreen: React.FC = () => {
|
||||
testID="edit-profile-button"
|
||||
/>
|
||||
<SettingItem
|
||||
icon="💳"
|
||||
title="Wallet Management"
|
||||
subtitle="Manage your connected keys"
|
||||
onPress={() => navigation.navigate('Wallet')}
|
||||
icon="👛"
|
||||
title="My Wallet"
|
||||
subtitle={selectedAccount ? `View balance & transactions` : 'Set up your wallet'}
|
||||
onPress={() => selectedAccount ? navigation.navigate('Wallet') : navigation.navigate('WalletSetup')}
|
||||
testID="wallet-management-button"
|
||||
/>
|
||||
</View>
|
||||
@@ -470,13 +507,13 @@ const SettingsScreen: React.FC = () => {
|
||||
<SettingItem
|
||||
icon="📄"
|
||||
title="Terms of Service"
|
||||
onPress={() => showAlert('Terms', 'Terms of service content...')}
|
||||
onPress={() => setShowTermsModal(true)}
|
||||
testID="terms-of-service-button"
|
||||
/>
|
||||
<SettingItem
|
||||
icon="🔒"
|
||||
title="Privacy Policy"
|
||||
onPress={() => showAlert('Privacy', 'Privacy policy content...')}
|
||||
onPress={() => setShowPrivacyModal(true)}
|
||||
testID="privacy-policy-button"
|
||||
/>
|
||||
<SettingItem
|
||||
@@ -733,6 +770,161 @@ const SettingsScreen: React.FC = () => {
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
{/* TERMS OF SERVICE MODAL */}
|
||||
<Modal visible={showTermsModal} animationType="slide" testID="terms-modal">
|
||||
<SafeAreaView style={[styles.fullModal, { backgroundColor: colors.background }]}>
|
||||
<View style={[styles.header, { backgroundColor: colors.surface, borderBottomColor: colors.border }]}>
|
||||
<View style={{ width: 60 }} />
|
||||
<Text style={[styles.headerTitle, { color: colors.text }]}>Terms of Service</Text>
|
||||
<TouchableOpacity onPress={() => setShowTermsModal(false)}>
|
||||
<Text style={{ fontSize: 16, color: KurdistanColors.kesk, fontWeight: '600' }}>Done</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ScrollView style={{ flex: 1, padding: 20 }}>
|
||||
<Text style={[styles.legalTitle, { color: colors.text }]}>Pezkuwi Terms of Service</Text>
|
||||
<Text style={[styles.legalDate, { color: colors.textSecondary }]}>Effective Date: {new Date().getFullYear()}</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>1. Acceptance of Terms</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
By accessing or using the Pezkuwi application ("App"), you agree to be bound by these Terms of Service. If you do not agree to these terms, please do not use the App.
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>2. Description of Service</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
Pezkuwi is a decentralized application that provides access to the Digital Kurdistan blockchain network. The App enables users to:{'\n'}
|
||||
• Create and manage blockchain wallets{'\n'}
|
||||
• Send and receive PEZ and HEZ tokens{'\n'}
|
||||
• Participate in governance and voting{'\n'}
|
||||
• Access decentralized services within the ecosystem
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>3. User Responsibilities</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
You are solely responsible for:{'\n'}
|
||||
• Maintaining the security of your wallet and recovery phrase{'\n'}
|
||||
• All activities conducted through your account{'\n'}
|
||||
• Ensuring compliance with local laws and regulations{'\n'}
|
||||
• Any transactions you authorize on the blockchain
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>4. Wallet Security</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
Your wallet is secured by a recovery phrase (seed phrase). This phrase is the ONLY way to restore access to your wallet. Pezkuwi does not store your recovery phrase and cannot recover it if lost. You must:{'\n'}
|
||||
• Never share your recovery phrase with anyone{'\n'}
|
||||
• Store your recovery phrase securely offline{'\n'}
|
||||
• Understand that lost phrases cannot be recovered
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>5. Disclaimer of Warranties</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
The App is provided "as is" without warranties of any kind. We do not guarantee uninterrupted access, error-free operation, or specific outcomes from using the App.
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>6. Limitation of Liability</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
Pezkuwi and its affiliates shall not be liable for any direct, indirect, incidental, consequential, or punitive damages arising from your use of the App, including but not limited to loss of funds or data.
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>7. Modifications</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
We reserve the right to modify these Terms at any time. Continued use of the App after changes constitutes acceptance of the modified Terms.
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>8. Contact</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
For questions about these Terms, contact us at:{'\n'}
|
||||
support@pezkuwichain.io
|
||||
</Text>
|
||||
|
||||
<View style={{ height: 40 }} />
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
|
||||
{/* PRIVACY POLICY MODAL */}
|
||||
<Modal visible={showPrivacyModal} animationType="slide" testID="privacy-modal">
|
||||
<SafeAreaView style={[styles.fullModal, { backgroundColor: colors.background }]}>
|
||||
<View style={[styles.header, { backgroundColor: colors.surface, borderBottomColor: colors.border }]}>
|
||||
<View style={{ width: 60 }} />
|
||||
<Text style={[styles.headerTitle, { color: colors.text }]}>Privacy Policy</Text>
|
||||
<TouchableOpacity onPress={() => setShowPrivacyModal(false)}>
|
||||
<Text style={{ fontSize: 16, color: KurdistanColors.kesk, fontWeight: '600' }}>Done</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ScrollView style={{ flex: 1, padding: 20 }}>
|
||||
<Text style={[styles.legalTitle, { color: colors.text }]}>Pezkuwi Privacy Policy</Text>
|
||||
<Text style={[styles.legalDate, { color: colors.textSecondary }]}>Effective Date: {new Date().getFullYear()}</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>1. Introduction</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
Pezkuwi ("we", "our", "us") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, and safeguard your information when you use our application.
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>2. Information We Collect</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
<Text style={{ fontWeight: '600' }}>Account Information:</Text> Email address (if provided for authentication), username, and profile information you choose to provide.{'\n\n'}
|
||||
<Text style={{ fontWeight: '600' }}>Blockchain Data:</Text> Your public wallet address and transaction history (which is publicly visible on the blockchain).{'\n\n'}
|
||||
<Text style={{ fontWeight: '600' }}>Device Information:</Text> Device type, operating system, and app version for troubleshooting and improvement purposes.{'\n\n'}
|
||||
<Text style={{ fontWeight: '600' }}>We DO NOT collect:</Text> Your recovery phrase, private keys, or biometric data (stored only on your device).
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>3. How We Use Information</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
We use collected information to:{'\n'}
|
||||
• Provide and maintain our services{'\n'}
|
||||
• Process your transactions on the blockchain{'\n'}
|
||||
• Send important notifications about your account{'\n'}
|
||||
• Improve our application and user experience{'\n'}
|
||||
• Comply with legal obligations
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>4. Data Storage and Security</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
• Your recovery phrase and private keys are stored ONLY on your device using secure storage mechanisms{'\n'}
|
||||
• We employ industry-standard security measures to protect your data{'\n'}
|
||||
• Blockchain transactions are permanently recorded on the public ledger
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>5. Data Sharing</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
We do not sell your personal information. We may share data with:{'\n'}
|
||||
• Service providers who assist in operating our services{'\n'}
|
||||
• Legal authorities when required by law{'\n'}
|
||||
• Blockchain networks for transaction processing (public data)
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>6. Your Rights</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
You have the right to:{'\n'}
|
||||
• Access your personal data{'\n'}
|
||||
• Request correction of inaccurate data{'\n'}
|
||||
• Request deletion of your account{'\n'}
|
||||
• Opt-out of non-essential communications{'\n\n'}
|
||||
Note: Blockchain data cannot be deleted due to its immutable nature.
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>7. Children's Privacy</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
Our services are not intended for users under 18 years of age. We do not knowingly collect information from children.
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>8. Updates to Policy</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
We may update this Privacy Policy periodically. We will notify you of significant changes through the App or via email.
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.legalSectionTitle, { color: colors.text }]}>9. Contact Us</Text>
|
||||
<Text style={[styles.legalText, { color: colors.textSecondary }]}>
|
||||
For privacy-related inquiries:{'\n'}
|
||||
Email: privacy@pezkuwichain.io{'\n'}
|
||||
Support: support@pezkuwichain.io
|
||||
</Text>
|
||||
|
||||
<View style={{ height: 40 }} />
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
@@ -937,6 +1129,26 @@ const styles = StyleSheet.create({
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
// Legal Modal Styles
|
||||
legalTitle: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
},
|
||||
legalDate: {
|
||||
fontSize: 13,
|
||||
marginBottom: 24,
|
||||
},
|
||||
legalSectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
marginTop: 20,
|
||||
marginBottom: 10,
|
||||
},
|
||||
legalText: {
|
||||
fontSize: 14,
|
||||
lineHeight: 22,
|
||||
},
|
||||
});
|
||||
|
||||
export default SettingsScreen;
|
||||
@@ -104,6 +104,7 @@ const WalletScreen: React.FC = () => {
|
||||
accounts,
|
||||
selectedAccount,
|
||||
setSelectedAccount,
|
||||
connectWallet,
|
||||
createWallet,
|
||||
deleteWallet,
|
||||
getKeyPair,
|
||||
@@ -114,8 +115,8 @@ const WalletScreen: React.FC = () => {
|
||||
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 [createWalletModalVisible, setCreateWalletModalVisible] = useState(false);
|
||||
const [importWalletModalVisible, setImportWalletModalVisible] = useState(false);
|
||||
const [backupModalVisible, setBackupModalVisible] = useState(false);
|
||||
const [networkSelectorVisible, setNetworkSelectorVisible] = useState(false);
|
||||
const [walletSelectorVisible, setWalletSelectorVisible] = useState(false);
|
||||
@@ -127,16 +128,16 @@ const WalletScreen: React.FC = () => {
|
||||
const [hiddenTokens, setHiddenTokens] = useState<string[]>([]);
|
||||
const [recipientAddress, setRecipientAddress] = useState('');
|
||||
const [sendAmount, setSendAmount] = useState('');
|
||||
const [walletName, _setWalletName] = useState('');
|
||||
const [walletName, setWalletName] = useState('');
|
||||
const [importMnemonic, setImportMnemonic] = useState('');
|
||||
const [_importWalletName, _setImportWalletName] = useState('');
|
||||
const [importWalletName, setImportWalletName] = useState('');
|
||||
const [userMnemonic, setUserMnemonic] = useState<string>('');
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [isLoadingBalances, setIsLoadingBalances] = useState(false);
|
||||
|
||||
// Transaction History State (TODO: implement transaction history display)
|
||||
const [_transactions, _setTransactions] = useState<Transaction[]>([]);
|
||||
const [_isLoadingHistory, _setIsLoadingHistory] = useState(false);
|
||||
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
||||
const [isLoadingHistory, setIsLoadingHistory] = useState(false);
|
||||
|
||||
const [balances, setBalances] = useState<{ [key: string]: string }>({
|
||||
HEZ: '0.00',
|
||||
@@ -570,7 +571,7 @@ const WalletScreen: React.FC = () => {
|
||||
throw new Error('Unknown token type');
|
||||
}
|
||||
|
||||
await tx.signAndSend(keypair, ({ status, _events }) => {
|
||||
await tx.signAndSend(keypair, ({ status, events }) => {
|
||||
if (status.isInBlock) {
|
||||
if (__DEV__) console.warn('[Wallet] Transaction in block:', status.asInBlock.toHex());
|
||||
}
|
||||
@@ -598,7 +599,7 @@ const WalletScreen: React.FC = () => {
|
||||
|
||||
const _handleCreateWallet = async () => {
|
||||
try {
|
||||
const { _address, mnemonic } = await createWallet(walletName);
|
||||
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() }]);
|
||||
@@ -685,7 +686,7 @@ const WalletScreen: React.FC = () => {
|
||||
// Redirect to WalletSetupScreen if no wallet exists
|
||||
useEffect(() => {
|
||||
if (!selectedAccount && accounts.length === 0) {
|
||||
navigation.replace('WalletSetup');
|
||||
(navigation as any).replace('WalletSetup');
|
||||
}
|
||||
}, [selectedAccount, accounts, navigation]);
|
||||
|
||||
@@ -1491,11 +1492,6 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
mainTokenLogo: {
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 28,
|
||||
},
|
||||
mainTokenSymbol: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
|
||||
@@ -181,9 +181,10 @@ const WalletSetupScreen: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Finish setup and go to wallet
|
||||
const handleFinish = () => {
|
||||
navigation.replace('Wallet');
|
||||
const handleSuccess = () => {
|
||||
// Navigate to main wallet screen
|
||||
// Using replace to prevent going back to setup
|
||||
(navigation as any).replace('Wallet');
|
||||
};
|
||||
|
||||
// Go back to previous step (TODO: add back button UI)
|
||||
@@ -459,7 +460,7 @@ const WalletSetupScreen: React.FC = () => {
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.primaryButton}
|
||||
onPress={handleFinish}
|
||||
onPress={handleSuccess}
|
||||
testID="wallet-setup-done-button"
|
||||
>
|
||||
<Text style={styles.primaryButtonText}>Go to Wallet</Text>
|
||||
|
||||
@@ -75,7 +75,7 @@ const DelegationScreen: React.FC = () => {
|
||||
const votingEntries = await api.query.democracy.voting.entries();
|
||||
const delegatesMap = new Map<string, { delegated: bigint; count: number }>();
|
||||
|
||||
votingEntries.forEach(([key, value]: [{ args: [{ toString: () => string }] }, { isDelegating: boolean; asDelegating: { target: { toString: () => string }; balance: { toString: () => string } } }]) => {
|
||||
votingEntries.forEach(([key, value]: any) => {
|
||||
const _voter = key.args[0].toString();
|
||||
const voting = value;
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ const ProposalsScreen: React.FC = () => {
|
||||
// Fetch democracy referenda
|
||||
if (api.query.democracy?.referendumInfoOf) {
|
||||
const referendaData = await api.query.democracy.referendumInfoOf.entries();
|
||||
const parsedProposals: Proposal[] = referendaData.map(([key, value]: [{ args: [{ toNumber(): number }] }, { unwrap(): { isOngoing?: boolean; asOngoing?: { tally?: { ayes?: { toString(): string }; nays?: { toString(): string } }; proposalHash?: { toString(): string }; end?: { toNumber(): number } } } }]) => {
|
||||
const parsedProposals: Proposal[] = referendaData.map(([key, value]: any) => {
|
||||
const index = key.args[0].toNumber();
|
||||
const info = value.unwrap();
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ const TreasuryScreen: React.FC = () => {
|
||||
// Fetch treasury proposals
|
||||
if (api.query.treasury?.proposals) {
|
||||
const proposalsData = await api.query.treasury.proposals.entries();
|
||||
const parsedProposals: TreasuryProposal[] = proposalsData.map(([key, value]: [{ args: [{ toNumber(): number }] }, { unwrap(): { beneficiary: { toString(): string }; value: { toString(): string }; proposer: { toString(): string }; bond: { toString(): string } } }]) => {
|
||||
const parsedProposals: TreasuryProposal[] = proposalsData.map(([key, value]: any) => {
|
||||
const proposalIndex = key.args[0].toNumber();
|
||||
const proposal = value.unwrap();
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface TokenInfo {
|
||||
usdValue: string;
|
||||
priceUsd: number;
|
||||
change24h: number;
|
||||
logo: string | null;
|
||||
logo: ImageSourcePropType | null;
|
||||
isNative: boolean;
|
||||
isFrozen: boolean;
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@
|
||||
},
|
||||
"typeRoots": ["./src/types", "./node_modules/@types"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "shared/**/*.ts", "App.tsx", "index.ts", "__mocks__/**/*.ts", "__mocks__/**/*.tsx"]
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "shared/**/*.ts", "App.tsx", "index.ts"],
|
||||
"exclude": ["**/__tests__/**/*", "**/__mocks__/**/*", "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user