Files
assetx/webapp/components/lending/BorrowMarket.tsx

166 lines
6.3 KiB
TypeScript
Raw Normal View History

"use client";
import { useState, useEffect } from "react";
import Image from "next/image";
import { Button } from "@heroui/react";
import { useRouter } from "next/navigation";
import { useApp } from "@/contexts/AppContext";
import { useAccount } from "wagmi";
import { useCollateralBalance, useCollateralValue } from "@/hooks/useCollateral";
import { fetchProducts } from "@/lib/api/fundmarket";
import { useTokenBySymbol } from "@/hooks/useTokenBySymbol";
interface BorrowMarketItemData {
tokenType: string;
icon: string;
iconBg: string;
name: string;
category: string;
contractAddress: string;
}
// 渐变色映射(用于不同的 YT token
const GRADIENT_COLORS: Record<string, string> = {
'YT-A': 'linear-gradient(135deg, #FF8904 0%, #F54900 100%)',
'YT-B': 'linear-gradient(135deg, #00BBA7 0%, #007A55 100%)',
'YT-C': 'linear-gradient(135deg, #667EEA 0%, #764BA2 100%)',
};
const DEFAULT_GRADIENT = 'linear-gradient(135deg, #6B7280 0%, #374151 100%)';
// 单个代币行组件
function BorrowMarketItem({ tokenData }: { tokenData: BorrowMarketItemData }) {
const { t } = useApp();
const router = useRouter();
const { isConnected } = useAccount();
const [mounted, setMounted] = useState(false);
// 查询该代币的抵押品余额
const ytToken = useTokenBySymbol(tokenData.tokenType);
const { formattedBalance, isLoading: isBalanceLoading } = useCollateralBalance(ytToken);
// 查询抵押品价值(用 valueRaw 避免 toFixed 截断误差)
const { valueRaw: collateralValueRaw } = useCollateralValue(ytToken);
useEffect(() => {
setMounted(true);
}, []);
return (
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-4 md:p-6 flex items-center gap-3">
{/* Token Info */}
<div className="flex items-center gap-3 flex-1 min-w-0">
<div className="w-10 h-10 shrink-0 rounded-full flex items-center justify-center overflow-hidden shadow-md"
style={{ background: tokenData.iconBg }}>
<Image src={tokenData.icon || "/assets/tokens/default.svg"} alt={tokenData.name} width={32} height={32} />
</div>
<div className="flex flex-col min-w-0">
<span className="text-body-default font-bold text-text-primary dark:text-white leading-[150%] truncate">
{tokenData.name}
</span>
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] truncate">
{tokenData.category}
</span>
</div>
</div>
{/* Your Balance */}
<div className="flex flex-col items-center flex-1">
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("lending.yourBalance")}
</span>
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%] whitespace-nowrap">
{!mounted || isBalanceLoading ? '...' : `$${collateralValueRaw.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`}
</span>
<span className="text-[10px] text-text-tertiary dark:text-gray-400 whitespace-nowrap">
{!mounted || isBalanceLoading ? '' : `${parseFloat(formattedBalance).toLocaleString()} ${tokenData.name}`}
</span>
</div>
{/* View Details Button */}
<Button
className={`shrink-0 rounded-xl h-10 px-4 text-body-small font-bold transition-opacity ${
!isConnected || !tokenData.contractAddress
? '!bg-[#E5E7EB] !text-[#9CA1AF] cursor-not-allowed'
: 'bg-foreground text-background hover:opacity-80'
}`}
isDisabled={!isConnected || !tokenData.contractAddress}
onPress={() => router.push(`/lending/repay?token=${tokenData.tokenType}`)}
>
{t("lending.viewDetails") || "View Details"}
</Button>
</div>
);
}
// 主组件
export default function BorrowMarket({ market }: { market: 'USDC' | 'USDT' }) {
const { t } = useApp();
const [tokens, setTokens] = useState<BorrowMarketItemData[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadTokens() {
setLoading(true);
try {
const products = await fetchProducts();
const tokenList: BorrowMarketItemData[] = products.map((product) => ({
tokenType: product.tokenSymbol,
icon: product.iconUrl,
iconBg: GRADIENT_COLORS[product.name] || 'linear-gradient(135deg, #667EEA 0%, #764BA2 100%)',
name: product.name,
category: product.category || `Yield Token ${product.name.split('-')[1]}`,
contractAddress: product.contractAddress || '',
}));
setTokens(tokenList);
} catch (error) {
console.error('Failed to fetch tokens:', error);
} finally {
setLoading(false);
}
}
loadTokens();
}, []);
if (loading) {
return (
<div className="flex flex-col gap-3">
<h2 className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em]">
{market} {t("lending.borrowMarket")}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-6 h-[120px] animate-pulse">
<div className="flex items-center gap-8">
<div className="w-10 h-10 rounded-full bg-gray-200 dark:bg-gray-700" />
<div className="flex-1 space-y-2">
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/3" />
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-1/4" />
</div>
</div>
</div>
))}
</div>
</div>
);
}
return (
<div className="flex flex-col gap-3">
{/* Title */}
<h2 className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em]">
{market} {t("lending.borrowMarket")}
</h2>
{/* Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{tokens.map((tokenData) => (
<BorrowMarketItem
key={tokenData.tokenType}
tokenData={tokenData}
/>
))}
</div>
</div>
);
}