diff --git a/src/components/common/cardano-objects/connect-wallet.tsx b/src/components/common/cardano-objects/connect-wallet.tsx index 7f4f6e2..c4679b0 100644 --- a/src/components/common/cardano-objects/connect-wallet.tsx +++ b/src/components/common/cardano-objects/connect-wallet.tsx @@ -10,7 +10,7 @@ import { } from "@/components/ui/dropdown-menu"; import { useWallet, useWalletList } from "@meshsdk/react"; import { useSiteStore } from "@/lib/zustand/site"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import useUser from "@/hooks/useUser"; import { useUserStore } from "@/lib/zustand/user"; import { getProvider } from "@/utils/get-provider"; @@ -35,21 +35,87 @@ export default function ConnectWallet() { const fetchingNetworkRef = useRef(false); const lastNetworkWalletRef = useRef(null); const userAssets = useUserStore((state) => state.userAssets); + const [walletsLoading, setWalletsLoading] = useState(true); + const [isMounted, setIsMounted] = useState(false); + const walletsRetryTimeoutRef = useRef(null); + + // Ensure component only runs on client side (important for SSR/production) + useEffect(() => { + setIsMounted(true); + }, []); async function connectWallet(walletId: string) { setPastWallet(walletId); await connect(walletId); } + // Monitor wallet list loading state and retry if empty + useEffect(() => { + // Only run on client side + if (!isMounted) return; + + if (wallets.length > 0) { + setWalletsLoading(false); + // Clear any pending retry timeout + if (walletsRetryTimeoutRef.current) { + clearTimeout(walletsRetryTimeoutRef.current); + walletsRetryTimeoutRef.current = null; + } + } else if (!connected) { + // Only show loading state if not connected (wallets might load after connection) + // If wallets are empty, wait a bit and check again + // This handles cases where wallet extensions load asynchronously + if (walletsRetryTimeoutRef.current === null) { + setWalletsLoading(true); + let retryCount = 0; + const maxRetries = 10; // Try for up to 10 seconds in production (longer timeout) + + const checkWallets = () => { + retryCount++; + // Re-check wallets array length (it might have updated) + if (wallets.length > 0) { + setWalletsLoading(false); + walletsRetryTimeoutRef.current = null; + return; + } + + if (retryCount < maxRetries) { + walletsRetryTimeoutRef.current = setTimeout(checkWallets, 1000); // Check every second + } else { + console.warn("Wallet list still empty after retries, wallets may not be available"); + setWalletsLoading(false); // Stop showing loading state + walletsRetryTimeoutRef.current = null; + } + }; + + walletsRetryTimeoutRef.current = setTimeout(checkWallets, 1000); + } + } else { + // If connected but no wallets, they're probably not needed + setWalletsLoading(false); + } + + return () => { + if (walletsRetryTimeoutRef.current) { + clearTimeout(walletsRetryTimeoutRef.current); + walletsRetryTimeoutRef.current = null; + } + }; + }, [wallets, connected, isMounted]); + /** * Try to connect the wallet when the user loads the application, if user had connected before, * but only if: - * 1. The wallet list has been loaded (wallets.length > 0) - * 2. The pastWallet exists in the available wallets - * 3. We're not already connected - * 4. We're not already attempting to connect + * 1. Component is mounted (client-side only) + * 2. The wallet list has been loaded (wallets.length > 0) + * 3. The pastWallet exists in the available wallets + * 4. We're not already connected + * 5. We're not already attempting to connect */ useEffect(() => { + // Only run on client side + if (!isMounted) return; + async function handleAutoWalletConnect() { // Don't attempt if already connected or already connecting if (connected || connectingRef.current) { @@ -65,6 +131,7 @@ export default function ConnectWallet() { // If wallets array is empty, wallets might still be loading // The effect will re-run when wallets become available if (wallets.length === 0) { + console.log("Waiting for wallets to load..."); return; } @@ -96,7 +163,7 @@ export default function ConnectWallet() { } handleAutoWalletConnect(); - }, [pastWallet, connected, wallets, connect, setPastWallet]); + }, [pastWallet, connected, wallets, connect, setPastWallet, isMounted]); useEffect(() => { async function lookupWalletAssets() { @@ -226,12 +293,12 @@ export default function ConnectWallet() { } } - // Only run if wallet and connected state are available - if (wallet && connected) { + // Only run if wallet and connected state are available, and component is mounted + if (isMounted && wallet && connected) { handleNetworkChange(); getWalletAssets(); } - }, [connected, wallet, name, setNetwork, setUserAssets, setUserAssetMetadata]); + }, [connected, wallet, name, setNetwork, setUserAssets, setUserAssetMetadata, isMounted]); return ( @@ -246,13 +313,25 @@ export default function ConnectWallet() { Select Wallet - {wallets.map((wallet, i) => { - return ( - connectWallet(wallet.id)}> - {wallet.name} - - ); - })} + {walletsLoading && wallets.length === 0 ? ( + + Loading wallets... + + ) : wallets.length === 0 ? ( + + + No wallets available. Please install a Cardano wallet extension. + + + ) : ( + wallets.map((wallet, i) => { + return ( + connectWallet(wallet.id)}> + {wallet.name} + + ); + }) + )} ); diff --git a/src/components/common/overall-layout/layout.tsx b/src/components/common/overall-layout/layout.tsx index 18a138a..23f05d3 100644 --- a/src/components/common/overall-layout/layout.tsx +++ b/src/components/common/overall-layout/layout.tsx @@ -22,11 +22,17 @@ import { import LogoutWrapper from "@/components/common/overall-layout/mobile-wrappers/logout-wrapper"; import { PageHomepage } from "@/components/pages/homepage"; import Logo from "@/components/common/overall-layout/logo"; -import ConnectWallet from "@/components/common/cardano-objects/connect-wallet"; +import dynamic from "next/dynamic"; import Loading from "@/components/common/overall-layout/loading"; import { MobileNavigation } from "@/components/ui/mobile-navigation"; import { MobileActionsMenu } from "@/components/ui/mobile-actions-menu"; +// Dynamically import ConnectWallet with SSR disabled to avoid production SSR issues +const ConnectWallet = dynamic( + () => import("@/components/common/cardano-objects/connect-wallet"), + { ssr: false } +); + // Enhanced error boundary component for wallet errors class WalletErrorBoundary extends Component< { children: ReactNode; fallback: ReactNode },