Files
assetx/components/ProductCard.tsx
default f3b0c0db6e feat: implement Product page with fund listing
Created a complete Product page based on prototype design with:

Page Features:
- New /product route with full page layout
- AssetX Fund Market title section
- 4 statistics cards (TVL, Cumulative Yield, Balance, Earnings)
- Assets listing section with view toggle (list/grid)
- Product cards grid layout

ProductCard Component:
- Reusable product card component with HeroUI integration
- Product header with icon and category badge
- Two-column metric display (Yield APY, Pool Cap, Maturity, etc.)
- Risk indicator with color-coded bars (Low/Medium/High)
- Pool capacity progress bar with gradient
- Invest button with HeroUI Button component
- Hover effects and transitions
- Color-coded category badges (Quant Strategy, Real Estate)

Assets:
- Copied view toggle icons from prototype
- edit-list-unordered0.svg (list view)
- menu-more-grid-small0.svg (grid view)

Internationalization:
- Added productPage section to en.json and zh.json
- All labels translated (English and Chinese)
- Consistent with existing i18n pattern

Technical Implementation:
- Full responsive design
- Dark mode support
- Gradient styling for visual appeal
- Smooth animations and transitions
- Proper TypeScript types
- Follows existing design system

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-30 04:11:58 +00:00

186 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import Image from "next/image";
import { Button } from "@heroui/react";
interface ProductCardProps {
name: string;
category: string;
icon?: string;
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,
icon,
yieldAPY,
poolCap,
maturity,
risk,
riskLevel,
lockUp,
circulatingSupply,
poolCapacityPercent,
onInvest,
}: ProductCardProps) {
const getRiskColor = (level: number) => {
switch (level) {
case 1:
return "bg-green-500";
case 2:
return "bg-yellow-500";
case 3:
return "bg-red-500";
default:
return "bg-gray-300";
}
};
const getCategoryColor = (cat: string) => {
if (cat.includes("Quant")) {
return "bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800 text-blue-700 dark:text-blue-300";
} else if (cat.includes("Real Estate")) {
return "bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800 text-green-700 dark:text-green-300";
}
return "bg-bg-subtle dark:bg-gray-700 border-border-normal dark:border-gray-600 text-text-tertiary dark:text-gray-400";
};
return (
<div className="bg-bg-surface dark:bg-gray-800 rounded-2xl border border-border-gray dark:border-gray-700 p-6 flex flex-col gap-6 hover:shadow-lg transition-shadow">
{/* Product Header */}
<div className="flex items-start gap-4">
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center flex-shrink-0">
{icon ? (
<Image src={icon} alt={name} width={32} height={32} />
) : (
<span className="text-2xl">🏛</span>
)}
</div>
<div className="flex flex-col gap-2 flex-1">
<h3 className="text-body-large font-bold text-text-primary dark:text-white leading-tight">
{name}
</h3>
<div
className={`inline-flex self-start px-3 py-1 rounded-full border text-caption-tiny font-medium ${getCategoryColor(
category
)}`}
>
{category}
</div>
</div>
</div>
{/* Product Metrics - Two Columns */}
<div className="grid grid-cols-2 gap-x-8 gap-y-4">
{/* Column 1 */}
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<span className="text-body-small text-text-tertiary dark:text-gray-400">
Yield APY
</span>
<span className="text-body-large font-bold text-green-600 dark:text-green-400">
{yieldAPY}
</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-body-small text-text-tertiary dark:text-gray-400">
Maturity
</span>
<span className="text-body-default font-medium text-text-primary dark:text-white">
{maturity}
</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-body-small text-text-tertiary dark:text-gray-400">
Lock-Up
</span>
<span className="text-body-default font-medium text-text-primary dark:text-white">
{lockUp}
</span>
</div>
</div>
{/* Column 2 */}
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<span className="text-body-small text-text-tertiary dark:text-gray-400">
Pool CaP
</span>
<span className="text-body-large font-bold text-text-primary dark:text-white">
{poolCap}
</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-body-small text-text-tertiary dark:text-gray-400">
Risk
</span>
<div className="flex items-center gap-2">
<span className="text-body-default font-medium text-text-primary dark:text-white">
{risk}
</span>
<div className="flex items-center gap-1">
{[1, 2, 3].map((level) => (
<div
key={level}
className={`w-1.5 h-4 rounded-sm transition-colors ${
level <= riskLevel
? getRiskColor(riskLevel)
: "bg-gray-300 dark:bg-gray-600"
}`}
/>
))}
</div>
</div>
</div>
<div className="flex flex-col gap-1">
<span className="text-body-small text-text-tertiary dark:text-gray-400">
Circulating supply
</span>
<span className="text-body-default font-medium text-text-primary dark:text-white">
{circulatingSupply}
</span>
</div>
</div>
</div>
{/* Divider */}
<div className="border-t border-border-gray dark:border-gray-700" />
{/* Pool Capacity & Invest Button */}
<div className="flex items-end justify-between gap-6">
<div className="flex-1 flex flex-col gap-2">
<div className="flex items-center justify-between">
<span className="text-body-small text-text-tertiary dark:text-gray-400">
Pool Capacity
</span>
<span className="text-body-small font-bold text-text-primary dark:text-white">
{poolCapacityPercent}% Filled
</span>
</div>
<div className="w-full h-2.5 bg-bg-subtle dark:bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-blue-500 to-purple-600 rounded-full transition-all duration-300"
style={{ width: `${poolCapacityPercent}%` }}
/>
</div>
</div>
<Button
onPress={onInvest}
className="px-8 py-3 bg-text-primary dark:bg-blue-600 text-white font-bold text-body-default rounded-xl h-auto min-w-[120px]"
>
Invest
</Button>
</div>
</div>
);
}