Files
assetx/components/fundmarket/ProductCard.tsx

264 lines
9.0 KiB
TypeScript
Raw Permalink Normal View History

2026-02-03 19:56:21 +08:00
"use client";
import Image from "next/image";
import { Button, Card, CardHeader, CardBody, CardFooter } from "@heroui/react";
import { buttonStyles } from "@/lib/buttonStyles";
interface ProductCardProps {
name: string;
category: string;
categoryColor: "orange" | "green" | "blue" | "purple" | "red";
iconType: "us-flag-1" | "hk-flag" | "us-flag-2" | "sg-flag" | "uk-flag";
yieldAPY: string;
poolCap: string;
maturity: string;
risk: string;
riskLevel: 1 | 2 | 3;
lockUp: string;
circulatingSupply: string;
poolCapacityPercent: number;
onInvest?: () => void;
}
export default function ProductCard({
name,
category,
categoryColor,
iconType,
yieldAPY,
poolCap,
maturity,
risk,
riskLevel,
lockUp,
circulatingSupply,
poolCapacityPercent,
onInvest,
}: ProductCardProps) {
const getCategoryColors = () => {
switch (categoryColor) {
case "orange":
return {
bg: "bg-orange-50",
text: "text-orange-600",
gradient: "from-orange-500/5 via-orange-300/3 to-white/0",
};
case "green":
return {
bg: "bg-green-50",
text: "text-green-600",
gradient: "from-green-500/5 via-green-300/3 to-white/0",
};
case "blue":
return {
bg: "bg-blue-50",
text: "text-blue-600",
gradient: "from-blue-500/5 via-blue-300/3 to-white/0",
};
case "purple":
return {
bg: "bg-purple-50",
text: "text-purple-600",
gradient: "from-purple-500/5 via-purple-300/3 to-white/0",
};
case "red":
return {
bg: "bg-red-50",
text: "text-red-600",
gradient: "from-red-500/5 via-red-300/3 to-white/0",
};
default:
return {
bg: "bg-orange-50",
text: "text-orange-600",
gradient: "from-orange-500/5 via-orange-300/3 to-white/0",
};
}
};
const getRiskBars = () => {
const bars = [
{ height: "h-[5px]", active: riskLevel >= 1 },
{ height: "h-[7px]", active: riskLevel >= 2 },
{ height: "h-[11px]", active: riskLevel >= 3 },
];
const activeColor =
riskLevel === 1
? "bg-green-500"
: riskLevel === 2
? "bg-amber-400"
: "bg-red-500";
return bars.map((bar, index) => (
<div
key={index}
className={`${bar.height} w-[3px] rounded-sm ${
bar.active ? activeColor : "bg-gray-400"
}`}
/>
));
};
const getIconSrc = () => {
switch (iconType) {
case "us-flag-1":
return "/frame-9230.svg";
case "hk-flag":
return "/hk0.svg";
case "us-flag-2":
return "/frame-9231.svg";
case "sg-flag":
return "/frame-9230.svg"; // TODO: Add Singapore flag
case "uk-flag":
return "/frame-9230.svg"; // TODO: Add UK flag
default:
return "/frame-9230.svg";
}
};
const colors = getCategoryColors();
return (
<Card
className={`bg-bg-subtle dark:bg-gray-800 border border-border-gray dark:border-gray-700 shadow-lg h-full relative overflow-hidden bg-gradient-to-br ${colors.gradient}`}
>
{/* Product Header */}
<CardHeader className="pb-6 px-6 pt-6">
<div className="flex gap-4 items-center">
{/* Icon Container */}
<div className="bg-white/40 dark:bg-gray-700/40 rounded-2xl border border-white/80 dark:border-gray-600/80 w-16 h-16 flex items-center justify-center shadow-[0_0_0_4px_rgba(255,255,255,0.1)] flex-shrink-0">
<div className="w-12 h-12 relative rounded-3xl border-[0.5px] border-gray-100 dark:border-gray-600 overflow-hidden flex items-center justify-center">
<div className="bg-white dark:bg-gray-800 rounded-full w-12 h-12 absolute left-0 top-0" />
<div className="relative z-10 flex items-center justify-center w-12 h-12">
<Image
src={getIconSrc()}
alt={name}
width={48}
height={48}
className="w-full h-full object-cover"
/>
</div>
</div>
</div>
{/* Title and Category */}
<div className="flex flex-col gap-0">
<h3 className="text-text-primary dark:text-white text-body-large font-bold font-inter">
{name}
</h3>
<div
className={`${colors.bg} dark:${colors.bg}/50 rounded-full px-2 py-0.5 inline-flex self-start`}
>
<span
className={`${colors.text} dark:${colors.text} text-caption-tiny font-medium font-inter`}
>
{category}
</span>
</div>
</div>
</div>
</CardHeader>
<CardBody className="py-0 px-6">
{/* Yield APY & Pool Cap */}
<div className="border-b border-gray-50 dark:border-gray-700 pb-6 flex">
<div className="flex-1 flex flex-col">
<span className="text-gray-600 dark:text-gray-400 text-caption-tiny font-bold font-inter">
Yield APY
</span>
<span className="text-text-primary dark:text-white text-heading-h3 font-extrabold font-jetbrains">
{yieldAPY}
</span>
</div>
<div className="flex-1 flex flex-col items-end">
<span className="text-gray-600 dark:text-gray-400 text-caption-tiny font-bold font-inter text-right">
Pool Cap
</span>
<span className="text-green-500 dark:text-green-400 text-heading-h3 font-extrabold font-jetbrains text-right">
{poolCap}
</span>
</div>
</div>
{/* Details Section */}
<div className="pt-6 flex flex-col gap-4">
{/* Maturity & Risk */}
<div className="flex gap-4">
<div className="flex-1 flex flex-col">
<span className="text-text-tertiary dark:text-gray-400 text-[10px] font-medium leading-[150%] tracking-[0.01em] font-inter">
Maturity
</span>
<span className="text-gray-600 dark:text-gray-300 text-body-small font-bold font-inter">
{maturity}
</span>
</div>
<div className="flex-1 flex flex-col items-end">
<span className="text-text-tertiary dark:text-gray-400 text-[10px] font-medium leading-[150%] tracking-[0.01em] font-inter text-right">
Risk
</span>
<div className="flex gap-1 items-center">
<span className="text-gray-600 dark:text-gray-300 text-body-small font-bold font-inter">
{risk}
</span>
<div className="flex gap-0.5 items-end">{getRiskBars()}</div>
</div>
</div>
</div>
{/* Lock-Up & Circulating Supply */}
<div className="flex gap-4">
<div className="flex-1 flex flex-col">
<span className="text-text-tertiary dark:text-gray-400 text-[10px] font-medium leading-[150%] tracking-[0.01em] font-inter">
Lock-Up
</span>
<span className="text-gray-600 dark:text-gray-300 text-body-small font-bold font-inter">
{lockUp}
</span>
</div>
<div className="flex-1 flex flex-col items-end">
<span className="text-text-tertiary dark:text-gray-400 text-[10px] font-medium leading-[150%] tracking-[0.01em] font-inter text-right">
Circulating supply
</span>
<span className="text-gray-600 dark:text-gray-300 text-body-small font-bold font-inter text-right">
{circulatingSupply}
</span>
</div>
</div>
</div>
</CardBody>
{/* Pool Capacity & Invest Button */}
<CardFooter className="pt-8 pb-6 px-6 flex-col gap-4">
<div className="flex flex-col gap-2 w-full">
<div className="flex justify-between">
<span className="text-text-tertiary dark:text-gray-400 text-caption-tiny font-bold font-inter">
Pool Capacity
</span>
<span className="text-text-primary dark:text-white text-body-small font-bold font-inter">
{poolCapacityPercent}% Filled
</span>
</div>
<div className="bg-gray-50 dark:bg-gray-700 rounded-full h-2 relative overflow-hidden">
<div
className="absolute left-0 top-0 bottom-0 rounded-full shadow-[0_0_15px_0_rgba(15,23,42,0.2)]"
style={{
background:
"linear-gradient(90deg, rgba(30, 41, 59, 1) 0%, rgba(51, 65, 85, 1) 50%, rgba(15, 23, 42, 1) 100%)",
width: `${poolCapacityPercent}%`,
}}
/>
</div>
</div>
<Button
color="default"
variant="solid"
onPress={onInvest}
className={buttonStyles({ intent: "theme" })}
>
Invest
</Button>
</CardFooter>
</Card>
);
}