Files
assetx/webapp/components/lending/repay/RepayStats.tsx

94 lines
3.9 KiB
TypeScript
Raw Normal View History

"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>
);
}