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
|
// Colors: Amber Gold #f0b429 | Quantum Blue #00d4ff | Deep Black #0a0a0f
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Link } from "wouter";
|
import { Link } from "wouter";
|
||||||
import { useWallet } from "@/hooks/useWallet";
|
import { useWallet } from "@/hooks/useWallet";
|
||||||
|
|
@ -315,7 +316,7 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── EVM Purchase Panel ─────────────────────────────────────────────────────
|
// ─── 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 { t } = useTranslation(lang);
|
||||||
const { purchaseState, buyWithUSDT, reset, calcTokens, getUsdtBalance } = usePresale(wallet, network);
|
const { purchaseState, buyWithUSDT, reset, calcTokens, getUsdtBalance } = usePresale(wallet, network);
|
||||||
const [usdtInput, setUsdtInput] = useState("100");
|
const [usdtInput, setUsdtInput] = useState("100");
|
||||||
|
|
@ -361,22 +362,18 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-sm text-white/60 text-center">{t("buy_connect_msg")}</p>
|
<p className="text-sm text-white/60 text-center">{t("buy_connect_msg")}</p>
|
||||||
<WalletSelector
|
<button
|
||||||
lang={lang}
|
onClick={() => onOpenWalletModal?.()}
|
||||||
connectedAddress={wallet.address ?? undefined}
|
className="w-full py-3 rounded-xl text-base font-bold transition-all hover:opacity-90 flex items-center justify-center gap-2"
|
||||||
onAddressDetected={async (addr, _network, rawProvider) => {
|
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)" }}
|
||||||
// KEY FIX: call connectWithProvider to sync wallet state immediately
|
>
|
||||||
// Do NOT rely on accountsChanged event — it only fires for window.ethereum listeners
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||||
if (rawProvider) {
|
<path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/>
|
||||||
await wallet.connectWithProvider(rawProvider, addr);
|
<path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/>
|
||||||
} else {
|
<path d="M18 12a2 2 0 0 0 0 4h4v-4z"/>
|
||||||
// Manual address entry — no provider available, set address-only state
|
</svg>
|
||||||
await wallet.connectWithProvider({ request: async () => [] } as unknown as import("@/hooks/useWallet").EthProvider, addr);
|
{lang === "zh" ? "连接钱包" : "Connect Wallet"}
|
||||||
}
|
</button>
|
||||||
toast.success(lang === "zh" ? `已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
|
|
||||||
}}
|
|
||||||
compact
|
|
||||||
/>
|
|
||||||
<div className="text-xs text-white/40 text-center">{t("buy_connect_hint")}</div>
|
<div className="text-xs text-white/40 text-center">{t("buy_connect_hint")}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -809,10 +806,9 @@ function ChatSupport({ lang }: { lang: Lang }) {
|
||||||
|
|
||||||
// ─── Navbar Wallet Button ─────────────────────────────────────────────────────
|
// ─── Navbar Wallet Button ─────────────────────────────────────────────────────
|
||||||
type WalletHookReturn = ReturnType<typeof useWallet>;
|
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 { t } = useTranslation(lang);
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
const [showWalletModal, setShowWalletModal] = useState(false);
|
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -823,35 +819,10 @@ function NavWalletButton({ lang, wallet }: { lang: Lang; wallet: WalletHookRetur
|
||||||
return () => document.removeEventListener("mousedown", handleClick);
|
return () => document.removeEventListener("mousedown", handleClick);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Detect mobile browser
|
// Handle connect button click — always show wallet selector modal
|
||||||
const isMobile = typeof window !== "undefined" && /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
// Both desktop and mobile users see the modal to choose their wallet
|
||||||
// Detect if running inside a wallet's in-app browser (window.ethereum is injected)
|
const handleConnectClick = () => {
|
||||||
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
|
|
||||||
setShowWalletModal(true);
|
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) {
|
if (!wallet.isConnected) {
|
||||||
|
|
@ -871,79 +842,6 @@ function NavWalletButton({ lang, wallet }: { lang: Lang; wallet: WalletHookRetur
|
||||||
{wallet.isConnecting ? t("nav_connecting") : t("nav_connect")}
|
{wallet.isConnecting ? t("nav_connecting") : t("nav_connect")}
|
||||||
</button>
|
</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
|
// 钱包状态提升到顶层,共享给NavWalletButton和EVMPurchasePanel
|
||||||
const wallet = useWallet();
|
const wallet = useWallet();
|
||||||
|
// showWalletModal提升到顶层,供NavWalletButton和EVMPurchasePanel共用
|
||||||
|
const [showWalletModal, setShowWalletModal] = useState(false);
|
||||||
|
|
||||||
const networks: NetworkTab[] = ["BSC", "ETH", "TRON"];
|
const networks: NetworkTab[] = ["BSC", "ETH", "TRON"];
|
||||||
|
|
||||||
|
|
@ -1081,7 +981,7 @@ export default function Home() {
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<LangToggle lang={lang} setLang={setLang} />
|
<LangToggle lang={lang} setLang={setLang} />
|
||||||
<NavWalletButton lang={lang} wallet={wallet} />
|
<NavWalletButton lang={lang} wallet={wallet} showWalletModal={showWalletModal} setShowWalletModal={setShowWalletModal} />
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
@ -1266,8 +1166,8 @@ export default function Home() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeNetwork === "BSC" && <EVMPurchasePanel network="BSC" 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} />}
|
{activeNetwork === "ETH" && <EVMPurchasePanel network="ETH" lang={lang} wallet={wallet} onOpenWalletModal={() => setShowWalletModal(true)} />}
|
||||||
{activeNetwork === "TRON" && (
|
{activeNetwork === "TRON" && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
@ -1386,6 +1286,64 @@ export default function Home() {
|
||||||
{/* ── Chat Support Widget ── */}
|
{/* ── Chat Support Widget ── */}
|
||||||
<ChatSupport lang={lang} />
|
<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>{`
|
<style>{`
|
||||||
@keyframes fadeInDown {
|
@keyframes fadeInDown {
|
||||||
from { opacity: 0; transform: translateY(-10px); }
|
from { opacity: 0; transform: translateY(-10px); }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue