diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index b5e94fc..1ffe73e 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -4,6 +4,7 @@ // Colors: Amber Gold #f0b429 | Quantum Blue #00d4ff | Deep Black #0a0a0f import { useState, useEffect, useCallback, useRef, useMemo } from "react"; +import { createPortal } from "react-dom"; import { toast } from "sonner"; import { Link } from "wouter"; import { useWallet } from "@/hooks/useWallet"; @@ -315,7 +316,7 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u } // ─── EVM Purchase Panel ───────────────────────────────────────────────────── -function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; lang: Lang; wallet: WalletHookReturn }) { +function EVMPurchasePanel({ network, lang, wallet, onOpenWalletModal }: { network: "BSC" | "ETH"; lang: Lang; wallet: WalletHookReturn; onOpenWalletModal?: () => void }) { const { t } = useTranslation(lang); const { purchaseState, buyWithUSDT, reset, calcTokens, getUsdtBalance } = usePresale(wallet, network); const [usdtInput, setUsdtInput] = useState("100"); @@ -361,22 +362,18 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l return (

{t("buy_connect_msg")}

- { - // KEY FIX: call connectWithProvider to sync wallet state immediately - // Do NOT rely on accountsChanged event — it only fires for window.ethereum listeners - if (rawProvider) { - await wallet.connectWithProvider(rawProvider, addr); - } else { - // Manual address entry — no provider available, set address-only state - await wallet.connectWithProvider({ request: async () => [] } as unknown as import("@/hooks/useWallet").EthProvider, addr); - } - toast.success(lang === "zh" ? `已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`); - }} - compact - /> +
{t("buy_connect_hint")}
); @@ -809,10 +806,9 @@ function ChatSupport({ lang }: { lang: Lang }) { // ─── Navbar Wallet Button ───────────────────────────────────────────────────── type WalletHookReturn = ReturnType; -function NavWalletButton({ lang, wallet }: { lang: Lang; wallet: WalletHookReturn }) { +function NavWalletButton({ lang, wallet, showWalletModal, setShowWalletModal }: { lang: Lang; wallet: WalletHookReturn; showWalletModal: boolean; setShowWalletModal: (v: boolean) => void }) { const { t } = useTranslation(lang); const [showMenu, setShowMenu] = useState(false); - const [showWalletModal, setShowWalletModal] = useState(false); const menuRef = useRef(null); useEffect(() => { @@ -823,35 +819,10 @@ function NavWalletButton({ lang, wallet }: { lang: Lang; wallet: WalletHookRetur return () => document.removeEventListener("mousedown", handleClick); }, []); - // Detect mobile browser - const isMobile = typeof window !== "undefined" && /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); - // Detect if running inside a wallet's in-app browser (window.ethereum is injected) - const isInWalletBrowser = typeof window !== "undefined" && !!((window as unknown as Record).ethereum); - - // Handle connect button click — show wallet selector modal - const handleConnectClick = async () => { - // On mobile AND inside a wallet's in-app browser: try direct connect first - // (window.ethereum is injected by the wallet app, so direct connect works) - if (isMobile && isInWalletBrowser) { - const result = await wallet.connect(); - if (!result.success) { - // If direct connect failed, show modal as fallback - setShowWalletModal(true); - } - return; - } - // On mobile browser (NOT in wallet app): show modal with DeepLink guide - if (isMobile) { - setShowWalletModal(true); - return; - } - // On desktop: first try direct connect (works if wallet is already set up and locked) - const result = await wallet.connect(); - if (!result.success && result.error) { - // If direct connect failed, show the wallet selector modal for guided setup - setShowWalletModal(true); - toast.error(result.error, { duration: 6000 }); - } + // Handle connect button click — always show wallet selector modal + // Both desktop and mobile users see the modal to choose their wallet + const handleConnectClick = () => { + setShowWalletModal(true); }; if (!wallet.isConnected) { @@ -871,79 +842,6 @@ function NavWalletButton({ lang, wallet }: { lang: Lang; wallet: WalletHookRetur {wallet.isConnecting ? t("nav_connecting") : t("nav_connect")} - {/* Wallet Connection Modal */} - {showWalletModal && ( -
{ if (e.target === e.currentTarget) setShowWalletModal(false); }} - > -
- {/* Close button */} - - -

- {lang === "zh" ? "连接钱包" : "Connect Wallet"} -

- {!isMobile && ( -

- {lang === "zh" - ? "选择您的钱包进行连接,或手动输入地址" - : "Select your wallet to connect, or enter address manually"} -

- )} - - {/* MetaMask initialization guide — desktop only */} - {!isMobile && ( -
-

- {lang === "zh" - ? "💡 首次使用 MetaMask?请先打开 MetaMask 扩展完成初始化(创建或导入钱包),完成后点击下方「刷新」按钮重新检测。" - : "💡 First time using MetaMask? Open the MetaMask extension and complete setup (create or import a wallet), then click Refresh below to re-detect."} -

-
- )} - {isMobile &&
} - - { - // KEY FIX: use connectWithProvider() to directly sync wallet state - // This avoids calling connect() again which would use detectProvider() - // and might fail if the wallet injects to a different window property (e.g. OKX -> window.okxwallet) - if (rawProvider) { - await wallet.connectWithProvider(rawProvider, addr); - } else { - // Fallback for manual address entry (no provider) - const result = await wallet.connect(); - if (!result.success) { - // Still mark as connected with address only - await wallet.connectWithProvider({ request: async () => [] } as unknown as import("@/hooks/useWallet").EthProvider, addr); - } - } - setShowWalletModal(false); - toast.success(lang === "zh" ? `钱包已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Wallet connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`); - }} - /> -
-
- )} ); } @@ -1042,6 +940,8 @@ export default function Home() { // 钱包状态提升到顶层,共享给NavWalletButton和EVMPurchasePanel const wallet = useWallet(); + // showWalletModal提升到顶层,供NavWalletButton和EVMPurchasePanel共用 + const [showWalletModal, setShowWalletModal] = useState(false); const networks: NetworkTab[] = ["BSC", "ETH", "TRON"]; @@ -1081,7 +981,7 @@ export default function Home() { - +
@@ -1266,8 +1166,8 @@ export default function Home() {

)} - {activeNetwork === "BSC" && } - {activeNetwork === "ETH" && } + {activeNetwork === "BSC" && setShowWalletModal(true)} />} + {activeNetwork === "ETH" && setShowWalletModal(true)} />} {activeNetwork === "TRON" && (
@@ -1386,6 +1286,64 @@ export default function Home() { {/* ── Chat Support Widget ── */} + {/* ── Global Wallet Connection Modal (Portal) ── */} + {showWalletModal && createPortal( +
{ if (e.target === e.currentTarget) setShowWalletModal(false); }} + > +
+ +

+ {lang === "zh" ? "连接钱包" : "Connect Wallet"} +

+ {!/Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) && ( + <> +

+ {lang === "zh" ? "选择您的钱包进行连接,或手动输入地址" : "Select your wallet to connect, or enter address manually"} +

+
+

+ {lang === "zh" + ? "💡 首次使用 MetaMask?请先打开 MetaMask 扩展完成初始化(创建或导入钱包),完成后点击下方「刷新」按鈕重新检测。" + : "💡 First time using MetaMask? Open the MetaMask extension and complete setup (create or import a wallet), then click Refresh below to re-detect."} +

+
+ + )} + { + if (rawProvider) { + await wallet.connectWithProvider(rawProvider, addr); + } else { + const result = await wallet.connect(); + if (!result.success) { + await wallet.connectWithProvider({ request: async () => [] } as unknown as import("@/hooks/useWallet").EthProvider, addr); + } + } + setShowWalletModal(false); + toast.success(lang === "zh" ? `钱包已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Wallet connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`); + }} + /> +
+
+ , document.body)} +