From 69789548e797ee8e2706b9b02a24962fb7fae181 Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Mon, 27 Apr 2026 15:00:58 +0300 Subject: [PATCH] fix: prevent 'API not ready' on mobile by blocking wallet connect until blockchain initializes - Add isApiInitializing state (true during WS connect, false on ready/fail) - Add isApiReadyRef for closure-safe polling in connectWalletConnect - connectWalletConnect now waits up to 30s for API instead of throwing immediately - WalletModal connect buttons disabled + show spinner while blockchain is initializing --- web/src/components/wallet/WalletModal.tsx | 31 +++++++++++++++++++---- web/src/contexts/PezkuwiContext.tsx | 24 ++++++++++++++++-- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/web/src/components/wallet/WalletModal.tsx b/web/src/components/wallet/WalletModal.tsx index adf298f1..51944b0e 100644 --- a/web/src/components/wallet/WalletModal.tsx +++ b/web/src/components/wallet/WalletModal.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { Wallet, Chrome, ExternalLink, Copy, Check, LogOut, Award, Users, TrendingUp, Shield, Smartphone } from 'lucide-react'; +import { Wallet, Chrome, ExternalLink, Copy, Check, LogOut, Award, Users, TrendingUp, Shield, Smartphone, Loader2 } from 'lucide-react'; import { useIsMobile } from '@/hooks/use-mobile'; import { Dialog, @@ -29,6 +29,7 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) => disconnectWallet, api, isApiReady, + isApiInitializing, peopleApi, walletSource, wcPeerName, @@ -350,9 +351,19 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) => onClick={handleConnect} className="w-full bg-gradient-to-r from-purple-600 to-cyan-400 hover:from-purple-700 hover:to-cyan-500" size="sm" + disabled={isApiInitializing} > - - {t('walletModal.extensionConnect')} + {isApiInitializing ? ( + <> + + {t('walletModal.connectingBlockchain', 'Connecting to blockchain...')} + + ) : ( + <> + + {t('walletModal.extensionConnect')} + + )} = ({ isOpen, onClose }) => variant="outline" className="w-full border-purple-500/30 hover:border-purple-500/60 hover:bg-purple-500/10" size="sm" + disabled={isApiInitializing} > - - {t('walletModal.mobileConnect')} + {isApiInitializing ? ( + <> + + {t('walletModal.connectingBlockchain', 'Connecting to blockchain...')} + + ) : ( + <> + + {t('walletModal.mobileConnect')} + + )}
{t('walletModal.mobileComingSoon')} diff --git a/web/src/contexts/PezkuwiContext.tsx b/web/src/contexts/PezkuwiContext.tsx index 99cf4f6f..dd05bf0d 100644 --- a/web/src/contexts/PezkuwiContext.tsx +++ b/web/src/contexts/PezkuwiContext.tsx @@ -19,7 +19,7 @@ if (typeof window !== 'undefined' && !import.meta.env.DEV) { }; } -import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'; +import React, { createContext, useContext, useEffect, useState, useRef, ReactNode } from 'react'; import { ApiPromise, WsProvider } from '@pezkuwi/api'; import { web3Accounts, web3Enable } from '@pezkuwi/extension-dapp'; import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types'; @@ -49,6 +49,7 @@ interface PezkuwiContextType { assetHubApi: ApiPromise | null; peopleApi: ApiPromise | null; isApiReady: boolean; + isApiInitializing: boolean; isAssetHubReady: boolean; isPeopleReady: boolean; isConnected: boolean; @@ -79,6 +80,8 @@ export const PezkuwiProvider: React.FC = ({ const [assetHubApi, setAssetHubApi] = useState(null); const [peopleApi, setPeopleApi] = useState(null); const [isApiReady, setIsApiReady] = useState(false); + const isApiReadyRef = useRef(false); + const [isApiInitializing, setIsApiInitializing] = useState(true); const [isAssetHubReady, setIsAssetHubReady] = useState(false); const [isPeopleReady, setIsPeopleReady] = useState(false); const [accounts, setAccounts] = useState([]); @@ -120,6 +123,7 @@ export const PezkuwiProvider: React.FC = ({ const initApi = async () => { let lastError: unknown = null; + setIsApiInitializing(true); for (const currentEndpoint of FALLBACK_ENDPOINTS) { try { @@ -146,7 +150,9 @@ export const PezkuwiProvider: React.FC = ({ await apiInstance.isReady; setApi(apiInstance); + isApiReadyRef.current = true; setIsApiReady(true); + setIsApiInitializing(false); setError(null); if (import.meta.env.DEV) { @@ -200,6 +206,7 @@ export const PezkuwiProvider: React.FC = ({ } setError('Failed to connect to blockchain network. Please try again later.'); setIsApiReady(false); + setIsApiInitializing(false); }; // Initialize Asset Hub API for PEZ token @@ -489,8 +496,20 @@ export const PezkuwiProvider: React.FC = ({ // Connect via WalletConnect v2 - returns pairing URI for QR code const connectWalletConnect = async (): Promise => { + // Wait up to 30s for API to finish initializing instead of failing immediately + if (!isApiReady) { + await new Promise((resolve, reject) => { + const deadline = Date.now() + 30_000; + const check = () => { + if (isApiReadyRef.current) return resolve(); + if (Date.now() >= deadline) return reject(new Error('Blockchain connection timed out. Please refresh and try again.')); + setTimeout(check, 300); + }; + check(); + }); + } if (!api || !isApiReady) { - throw new Error('API not ready. Please wait for blockchain connection.'); + throw new Error('Blockchain connection failed. Please refresh and try again.'); } setError(null); @@ -581,6 +600,7 @@ export const PezkuwiProvider: React.FC = ({ assetHubApi, peopleApi, isApiReady, + isApiInitializing, isAssetHubReady, isPeopleReady, isConnected: isApiReady,