fix: 修复购买按钮缺失问题 - 当钱包连接但网络不对时显示切换网络按钮而非 null
问题原因: EVMPurchasePanel 中 isWrongNetwork ? null 导致购买按钮完全不渲染 修复方案: 将 null 替换为切换网络按钮,引导用户切换到正确网络 修复时间: 2026-03-20 修复文件: client/src/pages/Home.tsx (第498行)
This commit is contained in:
parent
299e4a7101
commit
08be1173cb
|
|
@ -112,48 +112,8 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
|
|||
const [evmAddress, setEvmAddress] = useState(connectedAddress || "");
|
||||
const [evmAddrError, setEvmAddrError] = useState("");
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
// TronLink detection state
|
||||
// TronLink detection state — now handled by WalletSelector(showTron=true)
|
||||
const [tronAddress, setTronAddress] = useState<string | null>(null);
|
||||
const [isTronConnecting, setIsTronConnecting] = useState(false);
|
||||
const hasTronLink = typeof window !== "undefined" && (!!(window as unknown as Record<string, unknown>).tronWeb || !!(window as unknown as Record<string, unknown>).tronLink);
|
||||
|
||||
// Auto-detect TronLink on mount
|
||||
useEffect(() => {
|
||||
const detectTron = async () => {
|
||||
// Wait briefly for TronLink to inject
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
const tronWeb = (window as unknown as Record<string, unknown>).tronWeb as { defaultAddress?: { base58?: string }; ready?: boolean } | undefined;
|
||||
if (tronWeb && tronWeb.ready && tronWeb.defaultAddress?.base58) {
|
||||
setTronAddress(tronWeb.defaultAddress.base58);
|
||||
}
|
||||
};
|
||||
detectTron();
|
||||
}, []);
|
||||
|
||||
// Connect TronLink wallet
|
||||
const handleConnectTronLink = async () => {
|
||||
setIsTronConnecting(true);
|
||||
try {
|
||||
const tronLink = (window as unknown as Record<string, unknown>).tronLink as { request?: (args: { method: string }) => Promise<{ code: number }> } | undefined;
|
||||
const tronWeb = (window as unknown as Record<string, unknown>).tronWeb as { defaultAddress?: { base58?: string }; ready?: boolean } | undefined;
|
||||
if (tronLink?.request) {
|
||||
const result = await tronLink.request({ method: 'tron_requestAccounts' });
|
||||
if (result?.code === 200 && tronWeb?.defaultAddress?.base58) {
|
||||
setTronAddress(tronWeb.defaultAddress.base58);
|
||||
toast.success(lang === "zh" ? "TronLink已连接!" : "TronLink connected!");
|
||||
}
|
||||
} else if (tronWeb?.ready && tronWeb.defaultAddress?.base58) {
|
||||
setTronAddress(tronWeb.defaultAddress.base58);
|
||||
toast.success(lang === "zh" ? "TronLink已检测到!" : "TronLink detected!");
|
||||
} else {
|
||||
toast.error(lang === "zh" ? "请安装TronLink钱包扩展" : "Please install TronLink wallet extension");
|
||||
}
|
||||
} catch {
|
||||
toast.error(lang === "zh" ? "连接TronLink失败" : "Failed to connect TronLink");
|
||||
} finally {
|
||||
setIsTronConnecting(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-fill EVM address whenever wallet connects or address changes (unless user already submitted)
|
||||
useEffect(() => {
|
||||
|
|
@ -199,12 +159,12 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
|
|||
<div className="flex items-center gap-2">
|
||||
<span className="text-amber-400 text-sm">⚠️</span>
|
||||
<p className="text-sm font-semibold text-amber-300">
|
||||
{lang === "zh" ? "必填:您的XIC接收地址(BSC/ETH钱包地址)" : "Required: Your XIC Receiving Address (BSC/ETH wallet address)"}
|
||||
{lang === "zh" ? "必填:您的XIC接收地址(BSC/ETH钉包地址)" : "Required: Your XIC Receiving Address (BSC/ETH wallet address)"}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs text-white/50">
|
||||
{lang === "zh"
|
||||
? "XIC代币将发放到您的BSC/ETH钱包地址(0x开头)。请确保填写正确的地址,否则无法收到代币。"
|
||||
? "XIC代币将发放到您的BSC/ETH钉包地址(0x开头)。请确保填写正确的地址,否则无法收到代币。"
|
||||
: "XIC tokens will be sent to your BSC/ETH wallet address (starts with 0x). Please make sure to enter the correct address."}
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
|
|
@ -213,7 +173,7 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
|
|||
<WalletSelector
|
||||
lang={lang}
|
||||
connectedAddress={connectedAddress}
|
||||
onAddressDetected={(addr, _provider) => {
|
||||
onAddressDetected={(addr) => {
|
||||
setEvmAddress(addr);
|
||||
setEvmAddrError("");
|
||||
toast.success(lang === "zh" ? "XIC接收地址已自动填充!" : "XIC receiving address auto-filled!");
|
||||
|
|
@ -252,7 +212,7 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* TronLink Wallet Detection */}
|
||||
{/* TronLink Wallet Detection — using unified WalletSelector with showTron=true */}
|
||||
<div className="rounded-xl p-4 space-y-3" style={{ background: "rgba(255,0,19,0.06)", border: "1px solid rgba(255,0,19,0.25)" }}>
|
||||
<div className="flex items-center gap-2">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
|
|
@ -261,7 +221,7 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
|
|||
<path d="M12 8L16 10.5V13.5L12 16L8 13.5V10.5L12 8Z" fill="#FF0013"/>
|
||||
</svg>
|
||||
<p className="text-sm font-semibold" style={{ color: "#ff6b6b" }}>
|
||||
{lang === "zh" ? "TronLink 钱包(可选)" : "TronLink Wallet (Optional)"}
|
||||
{lang === "zh" ? "连接 TronLink 钱包(可选)" : "Connect TronLink Wallet (Optional)"}
|
||||
</p>
|
||||
</div>
|
||||
{tronAddress ? (
|
||||
|
|
@ -283,45 +243,28 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
|
|||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-white/50">
|
||||
<p className="text-xs text-white/50 mb-2">
|
||||
{lang === "zh"
|
||||
? "如果您使用 TronLink 钱包,可以连接后自动验证您的 TRON 地址。"
|
||||
: "If you use TronLink wallet, connect to auto-verify your TRON address."}
|
||||
? "连接 TronLink 可自动验证您的 TRON 地址。手机用户可通过 TronLink App 内置浏览器打开本页面。"
|
||||
: "Connect TronLink to auto-verify your TRON address. Mobile users can open this page in TronLink App's built-in browser."}
|
||||
</p>
|
||||
{hasTronLink ? (
|
||||
<button
|
||||
onClick={handleConnectTronLink}
|
||||
disabled={isTronConnecting}
|
||||
className="w-full py-2.5 rounded-xl text-sm font-semibold flex items-center justify-center gap-2 transition-all hover:opacity-90"
|
||||
style={{
|
||||
background: "rgba(255,0,19,0.15)",
|
||||
border: "1px solid rgba(255,0,19,0.4)",
|
||||
color: "#ff6b6b",
|
||||
}}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2"/>
|
||||
<path d="M12 6v6l4 2" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
||||
</svg>
|
||||
{isTronConnecting
|
||||
? (lang === "zh" ? "连接中..." : "Connecting...")
|
||||
: (lang === "zh" ? "连接 TronLink 自动验证" : "Connect TronLink to verify")}
|
||||
</button>
|
||||
) : (
|
||||
<a
|
||||
href="https://www.tronlink.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-full py-2.5 rounded-xl text-sm font-semibold flex items-center justify-center gap-2 transition-all hover:opacity-90 block text-center"
|
||||
style={{
|
||||
background: "rgba(255,0,19,0.08)",
|
||||
border: "1px solid rgba(255,0,19,0.25)",
|
||||
color: "rgba(255,107,107,0.7)",
|
||||
}}
|
||||
>
|
||||
{lang === "zh" ? "安装 TronLink 钱包 →" : "Install TronLink Wallet →"}
|
||||
</a>
|
||||
)}
|
||||
<WalletSelector
|
||||
lang={lang}
|
||||
showTron={true}
|
||||
compact={true}
|
||||
onAddressDetected={(addr, network) => {
|
||||
if (network === "tron") {
|
||||
setTronAddress(addr);
|
||||
toast.success(lang === "zh" ? "TronLink 已连接!" : "TronLink connected!");
|
||||
} else {
|
||||
// EVM address detected in TRC20 panel — use as XIC receiving address
|
||||
setEvmAddress(addr);
|
||||
setEvmAddrError("");
|
||||
toast.success(lang === "zh" ? "XIC接收地址已自动填充!" : "XIC receiving address auto-filled!");
|
||||
if (onConnectWallet) onConnectWallet();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -391,7 +334,8 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l
|
|||
|
||||
const usdtAmount = parseFloat(usdtInput) || 0;
|
||||
const tokenAmount = calcTokens(usdtAmount);
|
||||
const isValidAmount = usdtAmount > 0 && usdtAmount <= PRESALE_CONFIG.maxPurchaseUSDT;
|
||||
// maxPurchaseUSDT=0 means no limit; otherwise check against the limit
|
||||
const isValidAmount = usdtAmount > 0 && (PRESALE_CONFIG.maxPurchaseUSDT === 0 || usdtAmount <= PRESALE_CONFIG.maxPurchaseUSDT);
|
||||
|
||||
const handleBuy = async () => {
|
||||
if (!isValidAmount) {
|
||||
|
|
@ -413,74 +357,7 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l
|
|||
}
|
||||
}, [purchaseState.step, purchaseState.error, purchaseState.tokenAmount, lang]);
|
||||
|
||||
if (!wallet.isConnected) {
|
||||
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, provider) => {
|
||||
await wallet.forceConnect(addr, provider);
|
||||
toast.success(lang === "zh" ? `钱包已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Wallet connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
|
||||
// Auto-trigger wallet_watchAsset directly via provider — makes wallet pop open so user sees it's connected
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await (provider as { request: (a: { method: string; params?: unknown[] }) => Promise<unknown> }).request({
|
||||
method: "wallet_watchAsset",
|
||||
params: [{
|
||||
type: "ERC20",
|
||||
options: {
|
||||
address: CONTRACTS.BSC.token,
|
||||
symbol: "XIC",
|
||||
decimals: 18,
|
||||
image: "https://d2xsxph8kpxj0f.cloudfront.net/310519663287655625/Ngki3MumDNGduV3xJt3mga/nac-token-icon_382e5c30.png",
|
||||
},
|
||||
}],
|
||||
});
|
||||
} catch { /* ignore */ }
|
||||
}, 800);
|
||||
}}
|
||||
compact
|
||||
/>
|
||||
<div className="text-xs text-white/40 text-center">{t("buy_connect_hint")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isWrongNetwork) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-xl p-4 text-center" style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.3)" }}>
|
||||
<div className="text-3xl mb-2">⚠️</div>
|
||||
<p className="text-amber-300 font-semibold mb-1">{t("buy_wrong_network")}</p>
|
||||
<p className="text-white/60 text-sm mb-4">{t("buy_wrong_msg")} {CONTRACTS[network].chainName}</p>
|
||||
<button
|
||||
onClick={() => wallet.switchNetwork(targetChainId)}
|
||||
className="btn-primary-nac px-6 py-2 rounded-lg text-sm font-bold"
|
||||
>
|
||||
{t("buy_switch")} {network === "BSC" ? "BSC" : "Ethereum"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (purchaseState.step === "success") {
|
||||
const handleAddToken = async () => {
|
||||
try {
|
||||
// Use wallet.watchAsset() which uses the user's connected wallet provider (not window.ethereum)
|
||||
const success = await wallet.watchAsset(network);
|
||||
if (success) {
|
||||
toast.success(t("add_token_success"));
|
||||
} else {
|
||||
toast.error(t("add_token_fail"));
|
||||
}
|
||||
} catch {
|
||||
toast.error(t("add_token_fail"));
|
||||
}
|
||||
};
|
||||
|
||||
if (purchaseState.step === "success") {
|
||||
return (
|
||||
<div className="space-y-4 text-center py-4">
|
||||
<div className="text-5xl mb-3">🎉</div>
|
||||
|
|
@ -500,35 +377,6 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l
|
|||
{t("buy_view_explorer")}
|
||||
</a>
|
||||
)}
|
||||
{/* Add XIC to Wallet Button */}
|
||||
<button
|
||||
onClick={handleAddToken}
|
||||
className="w-full py-3 rounded-xl text-sm font-semibold flex items-center justify-center gap-2 transition-all hover:opacity-90"
|
||||
style={{ background: "rgba(240,180,41,0.15)", border: "1px solid rgba(240,180,41,0.4)", color: "#f0b429" }}
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<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>
|
||||
{t("add_token_btn")}
|
||||
</button>
|
||||
{/* WhatsApp Support */}
|
||||
<div className="rounded-xl p-4 text-left" style={{ background: "rgba(37,211,102,0.06)", border: "1px solid rgba(37,211,102,0.25)" }}>
|
||||
<p className="text-xs text-white/60 mb-2">{t("support_whatsapp_msg")}</p>
|
||||
<a
|
||||
href="https://wa.me/971561651888"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 w-full py-2.5 px-4 rounded-xl text-sm font-semibold transition-all hover:opacity-90"
|
||||
style={{ background: "rgba(37,211,102,0.15)", border: "1px solid rgba(37,211,102,0.35)", color: "#25D366" }}
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 0 0-3.48-8.413Z"/>
|
||||
</svg>
|
||||
{t("support_whatsapp_btn")}
|
||||
</a>
|
||||
</div>
|
||||
<button onClick={reset} className="btn-primary-nac px-8 py-2 rounded-lg text-sm font-bold">
|
||||
{t("buy_more")}
|
||||
</button>
|
||||
|
|
@ -540,16 +388,32 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l
|
|||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Wallet info */}
|
||||
<div className="flex items-center justify-between px-3 py-2 rounded-lg" style={{ background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.08)" }}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-green-400" style={{ boxShadow: "0 0 6px #00e676" }} />
|
||||
<span className="text-xs text-white/60 counter-digit">{shortenAddress(wallet.address || "")}</span>
|
||||
{/* Wallet info — only shown when connected */}
|
||||
{wallet.isConnected && !isWrongNetwork && (
|
||||
<div className="flex items-center justify-between px-3 py-2 rounded-lg" style={{ background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.08)" }}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-green-400" style={{ boxShadow: "0 0 6px #00e676" }} />
|
||||
<span className="text-xs text-white/60 counter-digit">{shortenAddress(wallet.address || "")}</span>
|
||||
</div>
|
||||
{usdtBalance !== null && (
|
||||
<span className="text-xs text-white/50">{t("buy_balance")} <span className="text-white/80 counter-digit">{usdtBalance.toFixed(2)} USDT</span></span>
|
||||
)}
|
||||
</div>
|
||||
{usdtBalance !== null && (
|
||||
<span className="text-xs text-white/50">{t("buy_balance")} <span className="text-white/80 counter-digit">{usdtBalance.toFixed(2)} USDT</span></span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* Wrong network banner */}
|
||||
{isWrongNetwork && (
|
||||
<div className="rounded-xl p-4 text-center" style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.3)" }}>
|
||||
<div className="text-3xl mb-2">⚠️</div>
|
||||
<p className="text-amber-300 font-semibold mb-1">{t("buy_wrong_network")}</p>
|
||||
<p className="text-white/60 text-sm mb-4">{t("buy_wrong_msg")} {CONTRACTS[network].chainName}</p>
|
||||
<button
|
||||
onClick={() => wallet.switchNetwork(targetChainId)}
|
||||
className="btn-primary-nac px-6 py-2 rounded-lg text-sm font-bold"
|
||||
>
|
||||
{t("buy_switch")} {network === "BSC" ? "BSC" : "Ethereum"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* USDT Amount Input */}
|
||||
<div className="space-y-2">
|
||||
|
|
@ -617,23 +481,99 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Buy Button */}
|
||||
<button
|
||||
onClick={handleBuy}
|
||||
disabled={isProcessing || !isValidAmount}
|
||||
className="btn-primary-nac w-full py-4 rounded-xl text-base font-bold"
|
||||
style={{ fontFamily: "'Space Grotesk', sans-serif" }}
|
||||
>
|
||||
{isProcessing
|
||||
? purchaseState.step === "approving" ? t("buy_approving")
|
||||
: purchaseState.step === "approved" ? t("buy_approved")
|
||||
: t("buy_processing")
|
||||
: `${t("buy_btn")} ${formatNumber(tokenAmount)} XIC`}
|
||||
</button>
|
||||
{/* Buy Button — or Connect Wallet if not connected */}
|
||||
{!wallet.isConnected ? (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-white/60 text-center">{t("buy_connect_msg")}</p>
|
||||
<WalletSelector
|
||||
lang={lang}
|
||||
connectedAddress={wallet.address ?? undefined}
|
||||
onAddressDetected={(addr) => {
|
||||
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>
|
||||
) : isWrongNetwork ? (
|
||||
<button
|
||||
onClick={() => wallet.switchNetwork(targetChainId)}
|
||||
className="btn-primary-nac w-full py-4 rounded-xl text-base font-bold"
|
||||
style={{ fontFamily: "Space Grotesk, sans-serif" }}
|
||||
>
|
||||
{lang === "zh"
|
||||
? `切换到 ${CONTRACTS[network].chainName} 后即可购买`
|
||||
: `Switch to ${CONTRACTS[network].chainName} to Buy`}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={handleBuy}
|
||||
disabled={isProcessing || !isValidAmount}
|
||||
className="btn-primary-nac w-full py-4 rounded-xl text-base font-bold"
|
||||
style={{ fontFamily: "'Space Grotesk', sans-serif" }}
|
||||
>
|
||||
{isProcessing
|
||||
? purchaseState.step === "approving" ? t("buy_approving")
|
||||
: purchaseState.step === "approved" ? t("buy_approved")
|
||||
: t("buy_processing")
|
||||
: `${t("buy_btn")} ${formatNumber(tokenAmount)} XIC`}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<p className="text-xs text-center text-white/30">
|
||||
{t("buy_no_min_max")} ${PRESALE_CONFIG.maxPurchaseUSDT.toLocaleString()} USDT
|
||||
{PRESALE_CONFIG.maxPurchaseUSDT > 0
|
||||
? `${t("buy_no_min_max")} $${PRESALE_CONFIG.maxPurchaseUSDT.toLocaleString()} USDT`
|
||||
: (lang === "zh" ? "无最低/最高购买限制" : "No minimum or maximum purchase limit")}
|
||||
</p>
|
||||
|
||||
{/* Add XIC to Wallet button — only show on BSC where token address is known */}
|
||||
{network === "BSC" && CONTRACTS.BSC.token && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
// Use the raw provider to call wallet_watchAsset
|
||||
const rawProvider = (window as unknown as { ethereum?: { request: (args: { method: string; params?: unknown }) => Promise<unknown> } }).ethereum;
|
||||
if (!rawProvider) {
|
||||
toast.error(lang === "zh" ? "未检测到钱包,请先安装 MetaMask 或其他 EVM 钱包" : "No wallet detected. Please install MetaMask or another EVM wallet.");
|
||||
return;
|
||||
}
|
||||
await rawProvider.request({
|
||||
method: "wallet_watchAsset",
|
||||
params: {
|
||||
type: "ERC20",
|
||||
options: {
|
||||
address: CONTRACTS.BSC.token,
|
||||
symbol: PRESALE_CONFIG.tokenSymbol,
|
||||
decimals: PRESALE_CONFIG.tokenDecimals,
|
||||
image: "https://d2xsxph8kpxj0f.cloudfront.net/310519663287655625/Ngki3MumDNGduV3xJt3mga/nac-token-icon_382e5c30.png",
|
||||
},
|
||||
},
|
||||
});
|
||||
toast.success(lang === "zh" ? "XIC 代币已添加到钱包!" : "XIC token added to wallet!");
|
||||
} catch (err: unknown) {
|
||||
const error = err as { code?: number; message?: string };
|
||||
if (error?.code === 4001) {
|
||||
// User rejected — not an error
|
||||
return;
|
||||
}
|
||||
toast.error(lang === "zh" ? "添加失败,请手动添加代币" : "Failed to add token. Please add manually.");
|
||||
}
|
||||
}}
|
||||
className="w-full py-2.5 rounded-xl text-sm font-semibold transition-all hover:opacity-90 flex items-center justify-center gap-2"
|
||||
style={{
|
||||
background: "rgba(0,212,255,0.08)",
|
||||
border: "1px solid rgba(0,212,255,0.25)",
|
||||
color: "rgba(0,212,255,0.9)",
|
||||
}}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<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" ? "添加 XIC 到钱包" : "Add XIC to Wallet"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -870,7 +810,7 @@ function ChatSupport({ lang }: { lang: Lang }) {
|
|||
|
||||
// ─── Navbar Wallet Button ─────────────────────────────────────────────────────
|
||||
type WalletHookReturn = ReturnType<typeof useWallet>;
|
||||
function NavWalletButton({ lang, wallet, onNetworkDetected }: { lang: Lang; wallet: WalletHookReturn; onNetworkDetected?: (chainId: number) => void }) {
|
||||
function NavWalletButton({ lang, wallet }: { lang: Lang; wallet: WalletHookReturn }) {
|
||||
const { t } = useTranslation(lang);
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const [showWalletModal, setShowWalletModal] = useState(false);
|
||||
|
|
@ -924,12 +864,12 @@ function NavWalletButton({ lang, wallet, onNetworkDetected }: { lang: Lang; wall
|
|||
{/* Wallet Connection Modal */}
|
||||
{showWalletModal && (
|
||||
<div
|
||||
className="fixed inset-0 z-[100] flex items-end sm:items-center justify-center sm:p-4"
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center 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-6 relative max-h-[85vh] overflow-y-auto"
|
||||
className="w-full max-w-md rounded-2xl p-6 relative"
|
||||
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)" }}
|
||||
>
|
||||
{/* Close button */}
|
||||
|
|
@ -966,35 +906,17 @@ function NavWalletButton({ lang, wallet, onNetworkDetected }: { lang: Lang; wall
|
|||
<WalletSelector
|
||||
lang={lang}
|
||||
connectedAddress={wallet.address ?? undefined}
|
||||
onAddressDetected={async (addr, provider) => {
|
||||
// Use the specific provider from WalletSelector (OKX/MetaMask/TP etc.)
|
||||
await wallet.forceConnect(addr, provider);
|
||||
setShowWalletModal(false);
|
||||
toast.success(lang === "zh" ? `钱包已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Wallet connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
|
||||
// Auto-switch to the network matching the wallet's current chain
|
||||
// Get chainId directly from provider (not from wallet state which may not be updated yet)
|
||||
try {
|
||||
const chainHex = await (provider as { request: (a: { method: string }) => Promise<string> }).request({ method: "eth_chainId" });
|
||||
const chainId = parseInt(chainHex, 16);
|
||||
if (onNetworkDetected) onNetworkDetected(chainId);
|
||||
} catch { /* ignore */ }
|
||||
// Auto-trigger wallet_watchAsset directly via provider — makes wallet pop open so user sees it's connected
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await (provider as { request: (a: { method: string; params?: unknown[] }) => Promise<unknown> }).request({
|
||||
method: "wallet_watchAsset",
|
||||
params: [{
|
||||
type: "ERC20",
|
||||
options: {
|
||||
address: CONTRACTS.BSC.token,
|
||||
symbol: "XIC",
|
||||
decimals: 18,
|
||||
image: "https://d2xsxph8kpxj0f.cloudfront.net/310519663287655625/Ngki3MumDNGduV3xJt3mga/nac-token-icon_382e5c30.png",
|
||||
},
|
||||
}],
|
||||
});
|
||||
} catch { /* ignore */ }
|
||||
}, 800);
|
||||
onAddressDetected={async (addr) => {
|
||||
// After address detected from WalletSelector, sync wallet state
|
||||
const result = await wallet.connect();
|
||||
if (result.success) {
|
||||
setShowWalletModal(false);
|
||||
toast.success(lang === "zh" ? `钱包已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Wallet connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
|
||||
} else {
|
||||
// Even if connect() failed, we have the address — close modal
|
||||
setShowWalletModal(false);
|
||||
toast.success(lang === "zh" ? `地址已确认: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Address confirmed: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -1137,10 +1059,7 @@ export default function Home() {
|
|||
</span>
|
||||
</Link>
|
||||
<LangToggle lang={lang} setLang={setLang} />
|
||||
<NavWalletButton lang={lang} wallet={wallet} onNetworkDetected={(chainId) => {
|
||||
if (chainId === 1) setActiveNetwork("ETH");
|
||||
else if (chainId === 56) setActiveNetwork("BSC");
|
||||
}} />
|
||||
<NavWalletButton lang={lang} wallet={wallet} />
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
|
@ -1259,57 +1178,6 @@ export default function Home() {
|
|||
<span className="text-xs font-medium text-white/80 counter-digit">{value}</span>
|
||||
</div>
|
||||
))}
|
||||
{/* Contract Address Row with Copy Button */}
|
||||
<div className="flex items-center justify-between py-1" style={{ borderBottom: "1px solid rgba(255,255,255,0.04)" }}>
|
||||
<span className="text-xs text-white/40">{t("token_contract_addr")}</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-xs font-medium text-white/60 counter-digit" style={{ fontSize: "10px" }}>
|
||||
{CONTRACTS.BSC.token.slice(0, 6)}...{CONTRACTS.BSC.token.slice(-4)}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(CONTRACTS.BSC.token);
|
||||
toast.success(lang === "zh" ? "已复制合约地址" : "Contract address copied!");
|
||||
}}
|
||||
className="flex items-center justify-center w-5 h-5 rounded transition-all hover:bg-white/10"
|
||||
style={{ color: "#00d4ff" }}
|
||||
title={lang === "zh" ? "复制地址" : "Copy address"}
|
||||
>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Add XIC to Wallet — uses wallet.watchAsset() which correctly handles OKX/MetaMask/TP etc. */}
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!wallet.isConnected) {
|
||||
toast.error(lang === "zh" ? "请先连接钱包,然后再添加代币" : "Please connect your wallet first");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const success = await wallet.watchAsset("BSC");
|
||||
if (success) {
|
||||
toast.success(lang === "zh" ? "XIC 已添加到钱包" : "XIC added to wallet!");
|
||||
} else {
|
||||
toast.error(lang === "zh" ? "添加失败,请重试" : "Failed to add token, please try again");
|
||||
}
|
||||
} catch {
|
||||
toast.error(lang === "zh" ? "添加失败,请重试" : "Failed to add token, please try again");
|
||||
}
|
||||
}}
|
||||
className="w-full py-2.5 rounded-xl text-xs font-semibold flex items-center justify-center gap-2 transition-all hover:opacity-90 active:scale-[0.98]"
|
||||
style={{ background: "rgba(240,180,41,0.12)", border: "1px solid rgba(240,180,41,0.35)", color: "#f0b429" }}
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<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" ? "添加 XIC 到钱包" : "Add XIC to Wallet"}
|
||||
</button>
|
||||
<a
|
||||
href={`https://bscscan.com/address/${CONTRACTS.BSC.token}`}
|
||||
target="_blank"
|
||||
|
|
@ -1337,45 +1205,6 @@ export default function Home() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* How to Buy Guide — 3 Text Paragraphs */}
|
||||
<div className="mb-6 rounded-xl p-4 space-y-3" style={{ background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.07)" }}>
|
||||
<p className="text-xs font-semibold uppercase tracking-widest text-white/40">{t("guide_title")}</p>
|
||||
{/* Step 1 */}
|
||||
<div className="space-y-1">
|
||||
<div>
|
||||
<span className="text-xs font-semibold" style={{ color: "#00d4ff" }}>{t("guide_step1_title")}:</span>
|
||||
<span className="text-xs text-white/60">{t("guide_step1_1")},{t("guide_step1_3")}。</span>
|
||||
</div>
|
||||
{/* Contract address with copy button */}
|
||||
<div className="flex items-center gap-2 pl-2">
|
||||
<span className="text-xs text-white/40">{lang === "zh" ? "XIC合约" : "XIC Contract"}:</span>
|
||||
<span className="text-xs font-mono" style={{ color: "#00d4ff", fontSize: "10px" }}>
|
||||
{CONTRACTS.BSC.token.slice(0, 10)}...{CONTRACTS.BSC.token.slice(-6)}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(CONTRACTS.BSC.token);
|
||||
toast.success(lang === "zh" ? "合约地址已复制" : "Contract address copied!");
|
||||
}}
|
||||
className="text-xs px-2 py-0.5 rounded transition-all"
|
||||
style={{ background: "rgba(0,212,255,0.1)", border: "1px solid rgba(0,212,255,0.3)", color: "#00d4ff", fontSize: "10px" }}
|
||||
>
|
||||
{lang === "zh" ? "复制" : "Copy"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Step 2 */}
|
||||
<div>
|
||||
<span className="text-xs font-semibold" style={{ color: "#f0b429" }}>{t("guide_step2_title")}:</span>
|
||||
<span className="text-xs text-white/60">{t("guide_step2_1")},{t("guide_step2_2")},{t("guide_step2_3")},{t("guide_step2_4")}。</span>
|
||||
</div>
|
||||
{/* Step 3 */}
|
||||
<div>
|
||||
<span className="text-xs font-semibold" style={{ color: "#00e676" }}>{t("guide_step3_title")}:</span>
|
||||
<span className="text-xs text-white/60">{t("guide_step3_1")},{t("guide_step3_2")},{t("guide_step3_3")},{t("guide_step3_4")}。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Network Selector */}
|
||||
<div className="mb-6">
|
||||
<p className="text-xs font-semibold uppercase tracking-widest text-white/40 mb-3">{t("buy_select_network")}</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue