init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
93
webapp/components/lending/repay/RepayStats.tsx
Normal file
93
webapp/components/lending/repay/RepayStats.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
import { useLTV, useBorrowBalance } from "@/hooks/useCollateral";
|
||||
import { useSupplyAPY } from "@/hooks/useHealthFactor";
|
||||
|
||||
export default function RepayStats() {
|
||||
const { t } = useApp();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
const { ltvRaw } = useLTV();
|
||||
const { formattedBalance: borrowedBalance } = useBorrowBalance();
|
||||
const { apr: supplyApr } = useSupplyAPY();
|
||||
|
||||
useEffect(() => { setMounted(true); }, []);
|
||||
|
||||
const LIQUIDATION_LTV = 75;
|
||||
const hasPosition = mounted && parseFloat(borrowedBalance) > 0;
|
||||
const healthPct = hasPosition ? Math.max(0, Math.min(100, (1 - ltvRaw / LIQUIDATION_LTV) * 100)) : 0;
|
||||
|
||||
const getHealthLabel = () => {
|
||||
if (!hasPosition) return 'No Position';
|
||||
if (ltvRaw < 50) return `Safe ${healthPct.toFixed(0)}%`;
|
||||
if (ltvRaw < 65) return `Warning ${healthPct.toFixed(0)}%`;
|
||||
return `At Risk ${healthPct.toFixed(0)}%`;
|
||||
};
|
||||
|
||||
const getHealthColor = () => {
|
||||
if (!hasPosition) return 'text-text-tertiary dark:text-gray-400';
|
||||
if (ltvRaw < 50) return 'text-[#10b981] dark:text-green-400';
|
||||
if (ltvRaw < 65) return 'text-[#ff6900] dark:text-orange-400';
|
||||
return 'text-[#ef4444] dark:text-red-400';
|
||||
};
|
||||
|
||||
const getBarGradient = () => {
|
||||
if (!hasPosition) return undefined;
|
||||
if (ltvRaw < 50) return 'linear-gradient(90deg, rgba(0, 213, 190, 1) 0%, rgba(0, 188, 125, 1) 100%)';
|
||||
if (ltvRaw < 65) return 'linear-gradient(90deg, #ff6900 0%, #ff9900 100%)';
|
||||
return 'linear-gradient(90deg, #ef4444 0%, #dc2626 100%)';
|
||||
};
|
||||
|
||||
// Net APR = getSupplyRate() directly (already represents net yield)
|
||||
const netApr = supplyApr > 0 ? supplyApr.toFixed(4) : null;
|
||||
|
||||
return (
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-4 md:p-8 flex flex-col gap-8 flex-1 shadow-md">
|
||||
{/* Stats Info */}
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* NET APR */}
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Image src="/components/repay/icon0.svg" alt="" width={18} height={18} className="flex-shrink-0" />
|
||||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
|
||||
{t("repay.netApr")}
|
||||
</span>
|
||||
</div>
|
||||
<span className={`text-body-small font-bold leading-[150%] whitespace-nowrap flex-shrink-0 ${netApr !== null && parseFloat(netApr) >= 0 ? 'text-[#10b981] dark:text-green-400' : 'text-[#ef4444]'}`}>
|
||||
{netApr !== null ? `${parseFloat(netApr) >= 0 ? '+' : ''}${netApr}%` : '--'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Position Health */}
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Image src="/components/repay/icon2.svg" alt="" width={18} height={18} className="flex-shrink-0" />
|
||||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
|
||||
{t("repay.positionHealth")}
|
||||
</span>
|
||||
</div>
|
||||
<span className={`text-body-small font-bold leading-[150%] whitespace-nowrap flex-shrink-0 ${getHealthColor()}`}>
|
||||
{!mounted ? '--' : getHealthLabel()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className="w-full">
|
||||
<div className="w-full h-2 bg-[#f3f4f6] dark:bg-gray-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-2 rounded-full transition-all duration-500"
|
||||
style={{
|
||||
width: `${hasPosition ? healthPct : 0}%`,
|
||||
background: getBarGradient() || '#e5e7eb',
|
||||
boxShadow: hasPosition && ltvRaw < 50 ? "0px 0px 15px 0px rgba(16, 185, 129, 0.3)" : undefined,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user