"use client"; import { useState, useEffect, useCallback } 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 { useLendingSupply, useSuppliedBalance } from '@/hooks/useLendingSupply'; import { getTxUrl } from '@/lib/contracts'; import { useTokenDecimalsFromAddress } from '@/hooks/useTokenDecimals'; import { useHealthFactor, useSupplyAPY } from '@/hooks/useHealthFactor'; import { toast } from "sonner"; import { useWalletStatus } from '@/hooks/useWalletStatus'; import TokenSelector from '@/components/common/TokenSelector'; import { useAppKit } from "@reown/appkit/react"; import { Token } from '@/lib/api/tokens'; type StablecoinType = 'USDC' | 'USDT'; export default function SupplyPanel() { const { t } = useApp(); const [amount, setAmount] = useState(""); // 稳定币选择(用于余额显示) const [stablecoin, setStablecoin] = useState('USDC'); const [stablecoinObj, setStablecoinObj] = useState(); // 使用统一的钱包状态 Hook const { address, isConnected, chainId, mounted } = useWalletStatus(); const { open } = useAppKit(); // 处理稳定币选择 const handleStablecoinSelect = useCallback((token: Token) => { setStablecoinObj(token); setStablecoin(token.symbol as StablecoinType); setAmount(""); // 清空输入金额 }, []); // 优先从产品合约地址读取精度,失败时回退到 token.decimals const usdcInputDecimals = useTokenDecimalsFromAddress( stablecoinObj?.contractAddress, stablecoinObj?.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(usdcBalance)) { setAmount(truncateDecimals(usdcBalance, usdcDisplayDecimals)); return; } setAmount(value); }; const { formattedBalance: usdcBalance, isLoading: isBalanceLoading, refetch: refetchBalance } = useUSDCBalance(); const { formattedBalance: suppliedBalance, refetch: refetchSupplied } = useSuppliedBalance(); // 根据选择的 token 类型决定使用哪个 hook // 目前只支持 USDC supply,YT token 选择仅用于显示 const { status: supplyStatus, error: supplyError, isLoading: isSupplyLoading, approveHash, supplyHash, executeApproveAndSupply, reset: resetSupply, } = useLendingSupply(); // 健康因子和 APY const { formattedHealthFactor, status: healthStatus, utilization, isLoading: isHealthLoading, refetch: refetchHealthFactor, } = useHealthFactor(); const { apy } = useSupplyAPY(); const SUPPLY_TOAST_ID = 'lending-supply-tx'; // Approve 交易提交 useEffect(() => { if (approveHash && supplyStatus === 'approving') { toast.loading(t("supply.toast.approvalSubmitted"), { id: SUPPLY_TOAST_ID, description: t("supply.toast.waitingConfirmation"), action: { label: t("supply.toast.viewTx"), onClick: () => window.open(getTxUrl(approveHash), '_blank'), }, }); } }, [approveHash, supplyStatus]); // Supply 交易提交 useEffect(() => { if (supplyHash && supplyStatus === 'supplying') { toast.loading(t("supply.toast.supplySubmitted"), { id: SUPPLY_TOAST_ID, description: t("supply.toast.supplyingNow"), action: { label: t("supply.toast.viewTx"), onClick: () => window.open(getTxUrl(supplyHash), '_blank'), }, }); } }, [supplyHash, supplyStatus]); // 成功后刷新余额 useEffect(() => { if (supplyStatus === 'success') { toast.success(t("supply.toast.suppliedSuccess"), { id: SUPPLY_TOAST_ID, description: t("supply.toast.suppliedSuccessDesc"), duration: 5000, }); refetchBalance(); refetchSupplied(); refetchHealthFactor(); const timer = setTimeout(() => { resetSupply(); setAmount(''); }, 3000); return () => clearTimeout(timer); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [supplyStatus]); // 错误提示 useEffect(() => { if (supplyError) { if (supplyError === 'Transaction cancelled') { toast.dismiss(SUPPLY_TOAST_ID); } else { toast.error(t("supply.toast.supplyFailed"), { id: SUPPLY_TOAST_ID, duration: 5000, }); } } }, [supplyError]); return (
{/* Token Balance & Supplied */}
{/* Token Balance */}
{t("supply.tokenBalance")}
{!mounted ? '0' : isBalanceLoading ? '...' : parseFloat(truncateDecimals(usdcBalance, usdcDisplayDecimals)).toLocaleString('en-US', { maximumFractionDigits: usdcDisplayDecimals })} ${!mounted ? '0' : isBalanceLoading ? '...' : parseFloat(truncateDecimals(usdcBalance, usdcDisplayDecimals)).toLocaleString('en-US', { maximumFractionDigits: usdcDisplayDecimals })}
{/* 稳定币选择器 */}
{/* Supplied - 同步显示选中的稳定币 */}
{t("supply.supplied")}
{!mounted ? '0' : parseFloat(suppliedBalance).toLocaleString('en-US', { maximumFractionDigits: 6 })} {stablecoin}
${!mounted ? '0' : parseFloat(suppliedBalance).toLocaleString('en-US', { maximumFractionDigits: 6 })}
{/* Deposit Section */}
{/* Deposit Label and Available */}
{t("supply.deposit")}
{!mounted ? `0 ${stablecoin}` : isBalanceLoading ? '...' : `${parseFloat(truncateDecimals(usdcBalance, usdcDisplayDecimals)).toLocaleString('en-US', { maximumFractionDigits: usdcDisplayDecimals })} ${stablecoin}`}
{/* Input Row */}
{/* Amount Input */}
handleAmountChange(e.target.value)} placeholder="0.00" className="text-heading-h3 font-bold text-text-input-box dark:text-gray-500 leading-[130%] tracking-[-0.005em] text-right bg-transparent outline-none w-full" /> --
{/* 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")}
{/* Rainbow Gradient Progress Bar */}
{/* Background Rainbow Gradient */}
{/* Active Progress with Rainbow Gradient */}
0 ? '0 0 8px rgba(59, 130, 246, 0.5)' : 'none', }} />
{/* Estimated Returns */}
{t("supply.estimatedReturns")}
{t("supply.estApy")} {apy > 0 ? `${apy.toFixed(2)}%` : '--'}
{t("supply.estReturnsYear")} {amount && apy > 0 ? `~ $${(parseFloat(amount) * apy / 100).toFixed(2)}` : '~ $0.00'}
{/* Supply Button */}
); }