diff --git a/mobile/src/components/LoadingSkeleton.tsx b/mobile/src/components/LoadingSkeleton.tsx index 91db5106..79162f72 100644 --- a/mobile/src/components/LoadingSkeleton.tsx +++ b/mobile/src/components/LoadingSkeleton.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect } from 'react'; import { View, Animated, StyleSheet, ViewStyle } from 'react-native'; import { AppColors } from '../theme/colors'; @@ -19,29 +19,31 @@ export const Skeleton: React.FC = ({ borderRadius = 8, style, }) => { - const animatedValueRef = useRef(new Animated.Value(0)); + const animatedValue = React.useState(() => new Animated.Value(0))[0]; useEffect(() => { - Animated.loop( + const animation = Animated.loop( Animated.sequence([ - Animated.timing(animatedValueRef.current, { + Animated.timing(animatedValue, { toValue: 1, duration: 1000, useNativeDriver: true, }), - Animated.timing(animatedValueRef.current, { + Animated.timing(animatedValue, { toValue: 0, duration: 1000, useNativeDriver: true, }), ]) - ).start(); - }, []); + ); + animation.start(); + return () => animation.stop(); + }, [animatedValue]); - const opacity = React.useMemo(() => animatedValueRef.current.interpolate({ + const opacity = animatedValue.interpolate({ inputRange: [0, 1], outputRange: [0.3, 0.7], - }), []); + }); return ( = ({ }, [checkAutoLock]); useEffect(() => { + // Initialize biometric and load settings on mount // eslint-disable-next-line react-hooks/set-state-in-effect initBiometric(); - // eslint-disable-next-line react-hooks/set-state-in-effect loadSettings(); }, [initBiometric, loadSettings]); diff --git a/mobile/src/contexts/LanguageContext.tsx b/mobile/src/contexts/LanguageContext.tsx index c6d89a40..8cf18062 100644 --- a/mobile/src/contexts/LanguageContext.tsx +++ b/mobile/src/contexts/LanguageContext.tsx @@ -28,6 +28,7 @@ export const LanguageProvider: React.FC<{ children: ReactNode }> = ({ children } useEffect(() => { // Check if user has already selected a language + // eslint-disable-next-line react-hooks/set-state-in-effect checkLanguageSelection(); }, [checkLanguageSelection]); diff --git a/mobile/src/screens/BeCitizenScreen.tsx b/mobile/src/screens/BeCitizenScreen.tsx index 92f06a81..acb7ee3e 100644 --- a/mobile/src/screens/BeCitizenScreen.tsx +++ b/mobile/src/screens/BeCitizenScreen.tsx @@ -108,9 +108,9 @@ const BeCitizenScreen: React.FC = () => { } else { Alert.alert('Application Failed', result.error || 'Failed to submit application'); } - } catch (error: any) { + } catch (error: unknown) { if (__DEV__) console.error('Citizenship application error:', error); - Alert.alert('Error', error.message || 'An unexpected error occurred'); + Alert.alert('Error', error instanceof Error ? error.message : 'An unexpected error occurred'); } finally { setIsSubmitting(false); } @@ -239,7 +239,7 @@ const BeCitizenScreen: React.FC = () => { - Father's Name * + Father's Name * { - Mother's Name * + Mother's Name * { address: selectedAccount.address, meta: {}, type: 'sr25519', - } as any, courseId); + }, courseId); Alert.alert('Success', 'Successfully enrolled in course!'); fetchEnrollments(); - } catch (error: any) { + } catch (error: unknown) { if (__DEV__) console.error('Enrollment failed:', error); - Alert.alert('Enrollment Failed', error.message || 'Failed to enroll in course'); + Alert.alert('Enrollment Failed', error instanceof Error ? error.message : 'Failed to enroll in course'); } finally { setEnrolling(null); } @@ -132,13 +132,13 @@ const EducationScreen: React.FC = () => { address: selectedAccount.address, meta: {}, type: 'sr25519', - } as any, courseId); + }, courseId); Alert.alert('Success', 'Course completed! Certificate issued.'); fetchEnrollments(); - } catch (error: any) { + } catch (error: unknown) { if (__DEV__) console.error('Completion failed:', error); - Alert.alert('Error', error.message || 'Failed to complete course'); + Alert.alert('Error', error instanceof Error ? error.message : 'Failed to complete course'); } }, }, diff --git a/mobile/src/screens/ForumScreen.tsx b/mobile/src/screens/ForumScreen.tsx index 13df4530..54738f3f 100644 --- a/mobile/src/screens/ForumScreen.tsx +++ b/mobile/src/screens/ForumScreen.tsx @@ -150,18 +150,18 @@ const ForumScreen: React.FC = () => { if (data && data.length > 0) { // Transform Supabase data to match ForumThread interface - const transformedThreads: ForumThread[] = data.map((thread: any) => ({ - id: thread.id, - title: thread.title, - content: thread.content, - author: thread.author_id, - category: thread.forum_categories?.name || 'Unknown', - replies_count: thread.replies_count || 0, - views_count: thread.views_count || 0, - created_at: thread.created_at, - last_activity: thread.last_activity || thread.created_at, - is_pinned: thread.is_pinned || false, - is_locked: thread.is_locked || false, + const transformedThreads: ForumThread[] = data.map((thread: Record) => ({ + id: String(thread.id), + title: String(thread.title), + content: String(thread.content), + author: String(thread.author_id), + category: (thread.forum_categories as { name?: string })?.name || 'Unknown', + replies_count: Number(thread.replies_count) || 0, + views_count: Number(thread.views_count) || 0, + created_at: String(thread.created_at), + last_activity: String(thread.last_activity || thread.created_at), + is_pinned: Boolean(thread.is_pinned), + is_locked: Boolean(thread.is_locked), })); setThreads(transformedThreads); } else { @@ -183,7 +183,7 @@ const ForumScreen: React.FC = () => { fetchThreads(selectedCategory || undefined); }; - const handleCategoryPress = (categoryId: string, categoryName: string) => { + const handleCategoryPress = (categoryId: string, _categoryName: string) => { setSelectedCategory(categoryId); setViewType('threads'); fetchThreads(categoryId); diff --git a/mobile/src/screens/GovernanceScreen.tsx b/mobile/src/screens/GovernanceScreen.tsx index a32aaebe..28c995dc 100644 --- a/mobile/src/screens/GovernanceScreen.tsx +++ b/mobile/src/screens/GovernanceScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, @@ -6,9 +6,7 @@ import { ScrollView, RefreshControl, Alert, - Pressable, TouchableOpacity, - FlatList, } from 'react-native'; import { usePolkadot } from '../contexts/PolkadotContext'; import { AppColors, KurdistanColors } from '../theme/colors'; @@ -73,14 +71,7 @@ export default function GovernanceScreen() { const [voting, setVoting] = useState(false); const [votedCandidates, setVotedCandidates] = useState([]); - useEffect(() => { - if (isApiReady && selectedAccount) { - fetchProposals(); - fetchElections(); - } - }, [isApiReady, selectedAccount]); - - const fetchProposals = async () => { + const fetchProposals = useCallback(async () => { try { setLoading(true); @@ -97,12 +88,9 @@ export default function GovernanceScreen() { const proposalsList: Proposal[] = []; // Parse proposals - const publicProps = proposalEntries.toJSON() as any[]; - - for (const [index, proposal, proposer] of publicProps) { - // Get proposal hash and details - const proposalHash = proposal; + const publicProps = proposalEntries.toJSON() as unknown[]; + for (const [index, _proposal, proposer] of publicProps as Array<[unknown, unknown, unknown]>) { // For demo, create sample proposals // In production, decode actual proposal data proposalsList.push({ @@ -127,9 +115,9 @@ export default function GovernanceScreen() { setLoading(false); setRefreshing(false); } - }; + }, [api]); - const fetchElections = async () => { + const fetchElections = useCallback(async () => { try { // Mock elections data // In production, this would fetch from pallet-tiki or election pallet @@ -164,7 +152,14 @@ export default function GovernanceScreen() { } catch (error) { if (__DEV__) console.error('Error fetching elections:', error); } - }; + }, []); + + useEffect(() => { + if (isApiReady && selectedAccount) { + void fetchProposals(); + void fetchElections(); + } + }, [isApiReady, selectedAccount, fetchProposals, fetchElections]); const handleVote = async (approve: boolean) => { if (!selectedProposal) return; @@ -190,9 +185,9 @@ export default function GovernanceScreen() { fetchProposals(); } }); - } catch (error: any) { + } catch (error: unknown) { if (__DEV__) console.error('Voting error:', error); - Alert.alert('Error', error.message || 'Failed to submit vote'); + Alert.alert('Error', error instanceof Error ? error.message : 'Failed to submit vote'); } finally { setVoting(false); } @@ -284,9 +279,9 @@ export default function GovernanceScreen() { } }); } - } catch (error: any) { + } catch (error: unknown) { if (__DEV__) console.error('Election voting error:', error); - Alert.alert('Error', error.message || 'Failed to submit vote'); + Alert.alert('Error', error instanceof Error ? error.message : 'Failed to submit vote'); } finally { setVoting(false); } diff --git a/mobile/src/screens/LockScreen.tsx b/mobile/src/screens/LockScreen.tsx index 6377876a..dd850bc8 100644 --- a/mobile/src/screens/LockScreen.tsx +++ b/mobile/src/screens/LockScreen.tsx @@ -3,7 +3,6 @@ import { View, Text, StyleSheet, - Image, Pressable, Alert, } from 'react-native'; @@ -25,27 +24,26 @@ export default function LockScreen() { biometricType, authenticate, verifyPinCode, - unlock, } = useBiometricAuth(); const [showPinInput, setShowPinInput] = useState(false); const [pin, setPin] = useState(''); const [verifying, setVerifying] = useState(false); - useEffect(() => { - // Auto-trigger biometric on mount if enabled - if (isBiometricEnabled && isBiometricSupported && isBiometricEnrolled) { - handleBiometricAuth(); - } - }, []); - - const handleBiometricAuth = async () => { + const handleBiometricAuth = React.useCallback(async () => { const success = await authenticate(); if (!success) { // Biometric failed, show PIN option setShowPinInput(true); } - }; + }, [authenticate]); + + useEffect(() => { + // Auto-trigger biometric on mount if enabled + if (isBiometricEnabled && isBiometricSupported && isBiometricEnrolled) { + handleBiometricAuth(); + } + }, [isBiometricEnabled, isBiometricSupported, isBiometricEnrolled, handleBiometricAuth]); const handlePinSubmit = async () => { if (!pin || pin.length < 4) { @@ -61,7 +59,7 @@ export default function LockScreen() { Alert.alert('Error', 'Incorrect PIN. Please try again.'); setPin(''); } - } catch (_error) { + } catch { Alert.alert('Error', 'Failed to verify PIN'); } finally { setVerifying(false); diff --git a/mobile/src/screens/NFTGalleryScreen.tsx b/mobile/src/screens/NFTGalleryScreen.tsx index 44c5c70b..3c856f35 100644 --- a/mobile/src/screens/NFTGalleryScreen.tsx +++ b/mobile/src/screens/NFTGalleryScreen.tsx @@ -5,7 +5,6 @@ import { StyleSheet, ScrollView, RefreshControl, - Image, Dimensions, Pressable, } from 'react-native'; @@ -48,13 +47,7 @@ export default function NFTGalleryScreen() { const [detailsVisible, setDetailsVisible] = useState(false); const [filter, setFilter] = useState<'all' | 'citizenship' | 'tiki' | 'achievement'>('all'); - useEffect(() => { - if (isApiReady && selectedAccount) { - fetchNFTs(); - } - }, [isApiReady, selectedAccount]); - - const fetchNFTs = async () => { + const fetchNFTs = React.useCallback(async () => { try { setLoading(true); @@ -66,7 +59,7 @@ export default function NFTGalleryScreen() { const citizenNft = await api.query.tiki?.citizenNft?.(selectedAccount.address); if (citizenNft && !citizenNft.isEmpty) { - const nftData = citizenNft.toJSON() as any; + const nftData = citizenNft.toJSON() as Record; nftList.push({ id: 'citizenship-001', @@ -115,7 +108,13 @@ export default function NFTGalleryScreen() { setLoading(false); setRefreshing(false); } - }; + }, [api, selectedAccount]); + + useEffect(() => { + if (isApiReady && selectedAccount) { + fetchNFTs(); + } + }, [isApiReady, selectedAccount, fetchNFTs]); const getRarityByTiki = (tiki: string): NFT['rarity'] => { const highRank = ['Serok', 'SerokiMeclise', 'SerokWeziran', 'Axa']; diff --git a/mobile/src/screens/P2PScreen.tsx b/mobile/src/screens/P2PScreen.tsx index 793d7710..456389a5 100644 --- a/mobile/src/screens/P2PScreen.tsx +++ b/mobile/src/screens/P2PScreen.tsx @@ -45,11 +45,7 @@ const P2PScreen: React.FC = () => { const [selectedOffer, setSelectedOffer] = useState(null); const [tradeAmount, setTradeAmount] = useState(''); - useEffect(() => { - fetchOffers(); - }, [activeTab, selectedAccount]); - - const fetchOffers = async () => { + const fetchOffers = React.useCallback(async () => { setLoading(true); try { let offersData: P2PFiatOffer[] = []; @@ -74,7 +70,11 @@ const P2PScreen: React.FC = () => { setLoading(false); setRefreshing(false); } - }; + }, [activeTab, selectedAccount]); + + useEffect(() => { + fetchOffers(); + }, [fetchOffers]); const handleRefresh = () => { setRefreshing(true); diff --git a/mobile/src/screens/ReferralScreen.tsx b/mobile/src/screens/ReferralScreen.tsx index e1306377..39887430 100644 --- a/mobile/src/screens/ReferralScreen.tsx +++ b/mobile/src/screens/ReferralScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { View, Text, @@ -59,10 +59,7 @@ const ReferralScreen: React.FC = () => { const handleConnectWallet = async () => { try { await connectWallet(); - if (selectedAccount) { - setIsConnected(true); - Alert.alert('Connected', 'Your wallet has been connected to the referral system!'); - } + Alert.alert('Connected', 'Your wallet has been connected to the referral system!'); } catch (error) { if (__DEV__) console.error('Wallet connection error:', error); Alert.alert('Error', 'Failed to connect wallet. Please try again.'); diff --git a/mobile/src/screens/SecurityScreen.tsx b/mobile/src/screens/SecurityScreen.tsx index f97b0c7b..02a801ab 100644 --- a/mobile/src/screens/SecurityScreen.tsx +++ b/mobile/src/screens/SecurityScreen.tsx @@ -10,7 +10,7 @@ import { } from 'react-native'; import { useBiometricAuth } from '../contexts/BiometricAuthContext'; import { AppColors, KurdistanColors } from '../theme/colors'; -import { Card, Button, Input, BottomSheet, Badge } from '../components'; +import { Card, Button, Input, BottomSheet } from '../components'; /** * Security Settings Screen @@ -129,8 +129,8 @@ export default function SecurityScreen() { }, ] ); - } catch (error: any) { - Alert.alert('Error', error.message || 'Failed to set PIN'); + } catch (error: unknown) { + Alert.alert('Error', error instanceof Error ? error.message : 'Failed to set PIN'); } finally { setSettingPin(false); } @@ -160,7 +160,7 @@ export default function SecurityScreen() { 🔐 Privacy Guarantee - All security settings are stored locally on your device only. Your biometric data never leaves your device's secure enclave. PIN codes are encrypted. No data is transmitted to our servers. + All security settings are stored locally on your device only. Your biometric data never leaves your device's secure enclave. PIN codes are encrypted. No data is transmitted to our servers. diff --git a/mobile/src/screens/StakingScreen.tsx b/mobile/src/screens/StakingScreen.tsx index 7be652dd..bc17da2d 100644 --- a/mobile/src/screens/StakingScreen.tsx +++ b/mobile/src/screens/StakingScreen.tsx @@ -15,7 +15,6 @@ import { Input, BottomSheet, Badge, - Skeleton, CardSkeleton, } from '../components'; import { @@ -53,13 +52,7 @@ export default function StakingScreen() { const [unstakeAmount, setUnstakeAmount] = useState(''); const [processing, setProcessing] = useState(false); - useEffect(() => { - if (isApiReady && selectedAccount) { - fetchStakingData(); - } - }, [isApiReady, selectedAccount]); - - const fetchStakingData = async () => { + const fetchStakingData = React.useCallback(async () => { try { setLoading(true); @@ -78,7 +71,7 @@ export default function StakingScreen() { // Calculate unbonding if (ledger.unlocking && ledger.unlocking.length > 0) { unbondingAmount = ledger.unlocking - .reduce((sum: bigint, unlock: any) => sum + BigInt(unlock.value.toString()), BigInt(0)) + .reduce((sum: bigint, unlock: { value: { toString: () => string } }) => sum + BigInt(unlock.value.toString()), BigInt(0)) .toString(); } } @@ -128,7 +121,13 @@ export default function StakingScreen() { setLoading(false); setRefreshing(false); } - }; + }, [api, selectedAccount]); + + useEffect(() => { + if (isApiReady && selectedAccount) { + void fetchStakingData(); + } + }, [isApiReady, selectedAccount, fetchStakingData]); const handleStake = async () => { if (!stakeAmount || parseFloat(stakeAmount) <= 0) { @@ -154,9 +153,9 @@ export default function StakingScreen() { fetchStakingData(); } }); - } catch (error: any) { + } catch (error: unknown) { if (__DEV__) console.error('Staking error:', error); - Alert.alert('Error', error.message || 'Failed to stake tokens'); + Alert.alert('Error', error instanceof Error ? error.message : 'Failed to stake tokens'); } finally { setProcessing(false); } @@ -188,9 +187,9 @@ export default function StakingScreen() { fetchStakingData(); } }); - } catch (error: any) { + } catch (error: unknown) { if (__DEV__) console.error('Unstaking error:', error); - Alert.alert('Error', error.message || 'Failed to unstake tokens'); + Alert.alert('Error', error instanceof Error ? error.message : 'Failed to unstake tokens'); } finally { setProcessing(false); } diff --git a/mobile/src/screens/SwapScreen.tsx b/mobile/src/screens/SwapScreen.tsx index e2ee206d..fac04aa3 100644 --- a/mobile/src/screens/SwapScreen.tsx +++ b/mobile/src/screens/SwapScreen.tsx @@ -97,7 +97,7 @@ const SwapScreen: React.FC = () => { } else { newBalances[token.symbol] = '0.0000'; } - } catch (_error) { + } catch { if (__DEV__) console.warn(`No balance for ${token.symbol}`); newBalances[token.symbol] = '0.0000'; } @@ -105,8 +105,8 @@ const SwapScreen: React.FC = () => { } setBalances(newBalances); - } catch (_error) { - if (__DEV__) console.error('Failed to fetch balances:', _error); + } catch (error) { + if (__DEV__) console.error('Failed to fetch balances:', error); } }, [api, isApiReady, selectedAccount]); @@ -158,8 +158,8 @@ const SwapScreen: React.FC = () => { setPoolReserves({ reserve1, reserve2 }); setState((prev) => ({ ...prev, loading: false })); - } catch (_error) { - if (__DEV__) console.error('Failed to fetch pool reserves:', _error); + } catch (error) { + if (__DEV__) console.error('Failed to fetch pool reserves:', error); Alert.alert('Error', 'Failed to fetch pool information.'); setState((prev) => ({ ...prev, loading: false })); } @@ -213,8 +213,8 @@ const SwapScreen: React.FC = () => { setState((prev) => ({ ...prev, toAmount: toAmountFormatted })); setPriceImpact(impact); - } catch (_error) { - if (__DEV__) console.error('Calculation error:', _error); + } catch (error) { + if (__DEV__) console.error('Calculation error:', error); setState((prev) => ({ ...prev, toAmount: '' })); } }, [state.fromAmount, state.fromToken, state.toToken, poolReserves]); @@ -399,9 +399,9 @@ const SwapScreen: React.FC = () => { }, ] ); - } catch (error: any) { + } catch (error: unknown) { if (__DEV__) console.error('Swap failed:', error); - Alert.alert('Swap Failed', error.message || 'An error occurred.'); + Alert.alert('Swap Failed', error instanceof Error ? error.message : 'An error occurred.'); setState((prev) => ({ ...prev, swapping: false })); } }; diff --git a/mobile/src/screens/WalletScreen.tsx b/mobile/src/screens/WalletScreen.tsx index a8468faf..53e6894c 100644 --- a/mobile/src/screens/WalletScreen.tsx +++ b/mobile/src/screens/WalletScreen.tsx @@ -134,7 +134,7 @@ const WalletScreen: React.FC = () => { setIsLoadingBalances(true); try { // Fetch HEZ balance (native token) - const accountInfo: any = await api.query.system.account(selectedAccount.address); + const accountInfo = await api.query.system.account(selectedAccount.address); const freeBalance = accountInfo.data.free.toString(); const hezBalance = (Number(freeBalance) / 1e12).toFixed(2); @@ -142,13 +142,13 @@ const WalletScreen: React.FC = () => { let pezBalance = '0.00'; try { if (api.query.assets?.account) { - const pezAsset: any = await api.query.assets.account(1, selectedAccount.address); + const pezAsset = await api.query.assets.account(1, selectedAccount.address); if (pezAsset.isSome) { const pezData = pezAsset.unwrap(); pezBalance = (Number(pezData.balance.toString()) / 1e12).toFixed(2); } } - } catch (_err) { + } catch { if (__DEV__) console.warn('PEZ asset not found or not accessible'); } @@ -156,13 +156,13 @@ const WalletScreen: React.FC = () => { let usdtBalance = '0.00'; try { if (api.query.assets?.account) { - const usdtAsset: any = await api.query.assets.account(2, selectedAccount.address); + const usdtAsset = await api.query.assets.account(2, selectedAccount.address); if (usdtAsset.isSome) { const usdtData = usdtAsset.unwrap(); usdtBalance = (Number(usdtData.balance.toString()) / 1e12).toFixed(2); } } - } catch (_err) { + } catch { if (__DEV__) console.warn('USDT asset not found or not accessible'); } @@ -197,8 +197,8 @@ const WalletScreen: React.FC = () => { // Connect existing wallet await connectWallet(); Alert.alert('Connected', 'Wallet connected successfully!'); - } catch (_err) { - if (__DEV__) console.error('Failed to connect wallet:', _err); + } catch (err) { + if (__DEV__) console.error('Failed to connect wallet:', err); Alert.alert('Error', 'Failed to connect wallet'); } }; @@ -219,8 +219,8 @@ const WalletScreen: React.FC = () => { `Your wallet has been created!\n\nAddress: ${address.substring(0, 10)}...\n\nIMPORTANT: Save your recovery phrase:\n${mnemonic}\n\nStore it securely - you'll need it to recover your wallet!`, [{ text: 'OK', onPress: () => connectWallet() }] ); - } catch (_err) { - if (__DEV__) console.error('Failed to create wallet:', _err); + } catch (err) { + if (__DEV__) console.error('Failed to create wallet:', err); Alert.alert('Error', 'Failed to create wallet'); } }; @@ -291,7 +291,7 @@ const WalletScreen: React.FC = () => { } // Sign and send transaction - await tx.signAndSend(keypair, ({ status, events: _events }: any) => { + await tx.signAndSend(keypair, ({ status }) => { if (status.isInBlock) { if (__DEV__) console.warn(`Transaction included in block: ${status.asInBlock}`); } else if (status.isFinalized) {