Checkpoint: 修复钱包连接Modal问题:1) 将showWalletModal状态提升到Home主组件;2) Modal通过Portal渲染到document.body,脱离导航栏backdropFilter层叠上下文;3) EVMPurchasePanel内嵌WalletSelector替换为统一的Connect Wallet按钮,触发同一个顶层Modal;4) 停止旧预售合约0xc65e7a27...
This commit is contained in:
parent
a7aa132b71
commit
1d0e293bdb
|
|
@ -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 (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-white/60 text-center">{t("buy_connect_msg")}</p>
|
||||
<WalletSelector
|
||||
lang={lang}
|
||||
connectedAddress={wallet.address ?? undefined}
|
||||
onAddressDetected={async (addr, _network, rawProvider) => {
|
||||
// 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
|
||||
/>
|
||||
<button
|
||||
onClick={() => onOpenWalletModal?.()}
|
||||
className="w-full py-3 rounded-xl text-base font-bold transition-all hover:opacity-90 flex items-center justify-center gap-2"
|
||||
style={{ background: "linear-gradient(135deg, rgba(240,180,41,0.9) 0%, rgba(255,215,0,0.9) 100%)", color: "#0a0a0f", fontFamily: "'Space Grotesk', sans-serif", boxShadow: "0 0 16px rgba(240,180,41,0.3)" }}
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/>
|
||||
<path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/>
|
||||
<path d="M18 12a2 2 0 0 0 0 4h4v-4z"/>
|
||||
</svg>
|
||||
{lang === "zh" ? "连接钱包" : "Connect Wallet"}
|
||||
</button>
|
||||
<div className="text-xs text-white/40 text-center">{t("buy_connect_hint")}</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -809,10 +806,9 @@ function ChatSupport({ lang }: { lang: Lang }) {
|
|||
|
||||
// ─── Navbar Wallet Button ─────────────────────────────────────────────────────
|
||||
type WalletHookReturn = ReturnType<typeof useWallet>;
|
||||
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<HTMLDivElement>(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<string, unknown>).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
|
||||
// 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);
|
||||
}
|
||||
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 });
|
||||
}
|
||||
};
|
||||
|
||||
if (!wallet.isConnected) {
|
||||
|
|
@ -871,79 +842,6 @@ function NavWalletButton({ lang, wallet }: { lang: Lang; wallet: WalletHookRetur
|
|||
{wallet.isConnecting ? t("nav_connecting") : t("nav_connect")}
|
||||
</button>
|
||||
|
||||
{/* Wallet Connection Modal */}
|
||||
{showWalletModal && (
|
||||
<div
|
||||
className="fixed inset-0 z-[100] flex items-end sm:items-center justify-center sm:p-4"
|
||||
style={{ background: "rgba(0,0,0,0.85)", backdropFilter: "blur(8px)" }}
|
||||
onClick={(e) => { if (e.target === e.currentTarget) setShowWalletModal(false); }}
|
||||
>
|
||||
<div
|
||||
className="w-full sm:max-w-md rounded-t-2xl sm:rounded-2xl p-5 relative overflow-y-auto"
|
||||
style={{ background: "rgba(10,10,20,0.98)", border: "1px solid rgba(240,180,41,0.3)", boxShadow: "0 0 40px rgba(240,180,41,0.15)", maxHeight: "85vh" }}
|
||||
>
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={() => setShowWalletModal(false)}
|
||||
className="absolute top-4 right-4 text-white/40 hover:text-white/80 transition-colors"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M18 6L6 18M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<h3 className="text-lg font-bold text-white mb-1" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
|
||||
{lang === "zh" ? "连接钱包" : "Connect Wallet"}
|
||||
</h3>
|
||||
{!isMobile && (
|
||||
<p className="text-xs text-white/40 mb-4">
|
||||
{lang === "zh"
|
||||
? "选择您的钱包进行连接,或手动输入地址"
|
||||
: "Select your wallet to connect, or enter address manually"}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* MetaMask initialization guide — desktop only */}
|
||||
{!isMobile && (
|
||||
<div
|
||||
className="rounded-xl p-3 mb-4"
|
||||
style={{ background: "rgba(240,180,41,0.06)", border: "1px solid rgba(240,180,41,0.2)" }}
|
||||
>
|
||||
<p className="text-xs text-amber-300/80 leading-relaxed">
|
||||
{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."}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{isMobile && <div className="mb-3" />}
|
||||
|
||||
<WalletSelector
|
||||
lang={lang}
|
||||
compact={false}
|
||||
showTron={false}
|
||||
connectedAddress={undefined}
|
||||
onAddressDetected={async (addr, _network, rawProvider) => {
|
||||
// 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)}`);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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() {
|
|||
</span>
|
||||
</Link>
|
||||
<LangToggle lang={lang} setLang={setLang} />
|
||||
<NavWalletButton lang={lang} wallet={wallet} />
|
||||
<NavWalletButton lang={lang} wallet={wallet} showWalletModal={showWalletModal} setShowWalletModal={setShowWalletModal} />
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
|
@ -1266,8 +1166,8 @@ export default function Home() {
|
|||
</p>
|
||||
</div>
|
||||
)}
|
||||
{activeNetwork === "BSC" && <EVMPurchasePanel network="BSC" lang={lang} wallet={wallet} />}
|
||||
{activeNetwork === "ETH" && <EVMPurchasePanel network="ETH" lang={lang} wallet={wallet} />}
|
||||
{activeNetwork === "BSC" && <EVMPurchasePanel network="BSC" lang={lang} wallet={wallet} onOpenWalletModal={() => setShowWalletModal(true)} />}
|
||||
{activeNetwork === "ETH" && <EVMPurchasePanel network="ETH" lang={lang} wallet={wallet} onOpenWalletModal={() => setShowWalletModal(true)} />}
|
||||
{activeNetwork === "TRON" && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
|
|
@ -1386,6 +1286,64 @@ export default function Home() {
|
|||
{/* ── Chat Support Widget ── */}
|
||||
<ChatSupport lang={lang} />
|
||||
|
||||
{/* ── Global Wallet Connection Modal (Portal) ── */}
|
||||
{showWalletModal && createPortal(
|
||||
<div
|
||||
className="fixed inset-0 z-[9999] flex items-end sm:items-center justify-center sm:p-4"
|
||||
style={{ background: "rgba(0,0,0,0.85)", backdropFilter: "blur(8px)" }}
|
||||
onClick={(e) => { if (e.target === e.currentTarget) setShowWalletModal(false); }}
|
||||
>
|
||||
<div
|
||||
className="w-full sm:max-w-md rounded-t-2xl sm:rounded-2xl p-5 relative overflow-y-auto"
|
||||
style={{ background: "rgba(10,10,20,0.98)", border: "1px solid rgba(240,180,41,0.3)", boxShadow: "0 0 40px rgba(240,180,41,0.15)", maxHeight: "85vh" }}
|
||||
>
|
||||
<button
|
||||
onClick={() => setShowWalletModal(false)}
|
||||
className="absolute top-4 right-4 text-white/40 hover:text-white/80 transition-colors"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M18 6L6 18M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
<h3 className="text-lg font-bold text-white mb-1" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
|
||||
{lang === "zh" ? "连接钱包" : "Connect Wallet"}
|
||||
</h3>
|
||||
{!/Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) && (
|
||||
<>
|
||||
<p className="text-xs text-white/40 mb-4">
|
||||
{lang === "zh" ? "选择您的钱包进行连接,或手动输入地址" : "Select your wallet to connect, or enter address manually"}
|
||||
</p>
|
||||
<div className="rounded-xl p-3 mb-4" style={{ background: "rgba(240,180,41,0.06)", border: "1px solid rgba(240,180,41,0.2)" }}>
|
||||
<p className="text-xs text-amber-300/80 leading-relaxed">
|
||||
{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."}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<WalletSelector
|
||||
lang={lang}
|
||||
compact={false}
|
||||
showTron={false}
|
||||
connectedAddress={undefined}
|
||||
onAddressDetected={async (addr, _network, rawProvider) => {
|
||||
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)}`);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
, document.body)}
|
||||
|
||||
<style>{`
|
||||
@keyframes fadeInDown {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
|
|
|
|||
Loading…
Reference in New Issue