"use client"; import { useState, useEffect, useRef } from "react"; import Image from "next/image"; import { Button } from "@heroui/react"; import { useApp } from "@/contexts/AppContext"; import { buttonStyles } from "@/lib/buttonStyles"; import { useUSDCBalance } from '@/hooks/useBalance'; import { useLendingWithdraw } from '@/hooks/useLendingWithdraw'; import { useSuppliedBalance } from '@/hooks/useLendingSupply'; import { getTxUrl } from '@/lib/contracts'; import { useTokenDecimalsFromAddress } from '@/hooks/useTokenDecimals'; import { useTokenBySymbol } from '@/hooks/useTokenBySymbol'; import { useHealthFactor, useSupplyAPY, useBorrowBalance } from '@/hooks/useHealthFactor'; import { toast } from 'sonner'; import { useWalletStatus } from '@/hooks/useWalletStatus'; export default function WithdrawPanel() { const { t } = useApp(); const [amount, setAmount] = useState(""); // 使用统一的钱包状态 Hook const { isConnected, mounted } = useWalletStatus(); const { formattedBalance: usdcBalance, isLoading: isBalanceLoading, refetch: refetchBalance } = useUSDCBalance(); const { formattedBalance: suppliedBalance, refetch: refetchSupplied } = useSuppliedBalance(); const { status: withdrawStatus, error: withdrawError, isLoading: isWithdrawLoading, withdrawHash, executeWithdraw, reset: resetWithdraw, } = useLendingWithdraw(); // 健康因子和 APY const { formattedHealthFactor, status: healthStatus, utilization, isLoading: isHealthLoading } = useHealthFactor(); const { apy } = useSupplyAPY(); const { formattedBalance: borrowBalance } = useBorrowBalance(); const hasShownBorrowWarning = useRef(false); // 从产品 API 获取 USDC token 信息,优先从合约地址读取精度 const usdcToken = useTokenBySymbol('USDC'); const usdcInputDecimals = useTokenDecimalsFromAddress(usdcToken?.contractAddress, usdcToken?.decimals ?? 18); const usdcDisplayDecimals = Math.min(usdcInputDecimals, 6); const truncateDecimals = (s: string, d: number) => { const p = s.split('.'); return p.length > 1 ? `${p[0]}.${p[1].slice(0, d)}` : s; }; const isValidAmount = (v: string) => v !== '' && !isNaN(parseFloat(v)) && parseFloat(v) > 0; const handleAmountChange = (value: string) => { if (value === '') { setAmount(value); return; } if (!/^\d*\.?\d*$/.test(value)) return; const parts = value.split('.'); if (parts.length > 1 && parts[1].length > usdcDisplayDecimals) return; if (parseFloat(value) > parseFloat(suppliedBalance)) { setAmount(truncateDecimals(suppliedBalance, usdcDisplayDecimals)); return; } setAmount(value); }; // 有借款时,挂载后弹出一次提示 useEffect(() => { if (!mounted || hasShownBorrowWarning.current) return; if (parseFloat(borrowBalance) > 0) { toast.warning(t("supply.toast.borrowWarning"), { description: t("supply.toast.borrowWarningDesc"), duration: 5000, }); hasShownBorrowWarning.current = true; } }, [mounted, borrowBalance]); const WITHDRAW_TOAST_ID = 'lending-withdraw-tx'; // Withdraw 交易提交 useEffect(() => { if (withdrawHash && withdrawStatus === 'withdrawing') { toast.loading(t("supply.toast.withdrawSubmitted"), { id: WITHDRAW_TOAST_ID, description: t("supply.toast.processingWithdrawal"), action: { label: t("supply.toast.viewTx"), onClick: () => window.open(getTxUrl(withdrawHash), '_blank'), }, }); } }, [withdrawHash, withdrawStatus]); // 成功后刷新余额 useEffect(() => { if (withdrawStatus === 'success') { toast.success(t("supply.toast.withdrawnSuccess"), { id: WITHDRAW_TOAST_ID, description: t("supply.toast.withdrawnSuccessDesc"), duration: 5000, }); refetchBalance(); refetchSupplied(); const timer = setTimeout(() => { resetWithdraw(); setAmount(''); }, 3000); return () => clearTimeout(timer); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [withdrawStatus]); // 错误提示 useEffect(() => { if (withdrawError) { if (withdrawError === 'Transaction cancelled') { toast.dismiss(WITHDRAW_TOAST_ID); } else { toast.error(t("supply.toast.withdrawFailed"), { id: WITHDRAW_TOAST_ID, duration: 5000, }); } } }, [withdrawError]); return (
{/* Token Balance & Supplied */}
{/* Token Balance */}
{t("supply.tokenBalance")}
{!mounted ? '0' : isBalanceLoading ? '...' : parseFloat(truncateDecimals(usdcBalance, usdcDisplayDecimals)).toLocaleString('en-US', { maximumFractionDigits: usdcDisplayDecimals })} USDC
${!mounted ? '0' : isBalanceLoading ? '...' : parseFloat(truncateDecimals(usdcBalance, usdcDisplayDecimals)).toLocaleString('en-US', { maximumFractionDigits: usdcDisplayDecimals })}
{/* Supplied */}
{t("supply.supplied")}
{!mounted ? '0' : parseFloat(truncateDecimals(suppliedBalance, usdcDisplayDecimals)).toLocaleString('en-US', { maximumFractionDigits: usdcDisplayDecimals })} USDC
${!mounted ? '0' : parseFloat(truncateDecimals(suppliedBalance, usdcDisplayDecimals)).toLocaleString('en-US', { maximumFractionDigits: usdcDisplayDecimals })}
{/* Withdraw Section */}
{/* Withdraw Label and Available */}
{t("supply.withdraw")}
{!mounted ? '0 USDC' : `${parseFloat(truncateDecimals(suppliedBalance, usdcDisplayDecimals)).toLocaleString('en-US', { maximumFractionDigits: usdcDisplayDecimals })} USDC`}
{/* Input Row */}
{/* USDC Token Button */}
USDC USDC
{/* Amount Input */}
handleAmountChange(e.target.value)} placeholder="0" className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em] text-right bg-transparent outline-none w-24" /> {amount ? `≈ $${amount}` : "--"}
{/* Health Factor */}
{t("supply.healthFactor")}: {isHealthLoading ? '...' : formattedHealthFactor} {utilization.toFixed(1)}% {t("supply.utilization")}
{healthStatus === 'safe' ? t("supply.safe") : healthStatus === 'warning' ? t("supply.warning") : healthStatus === 'danger' ? t("supply.danger") : t("supply.critical")}
{/* Progress Bar */}
0 ? '0 0 8px rgba(59, 130, 246, 0.5)' : 'none', }} />
{/* Current APY */}
{t("supply.currentReturns")}
{t("supply.supplyApy")} {apy > 0 ? `${apy.toFixed(2)}%` : '--'}
{t("supply.yearlyEarnings")} {apy > 0 && parseFloat(suppliedBalance) > 0 ? `~ $${(parseFloat(suppliedBalance) * apy / 100).toFixed(2)}` : '~ $0.00'}
{/* Withdraw Button */}
); }