feat: refactor Product page to match prototype design

Redesigned ProductCard and Product page based on original prototype:

ProductCard Component:
- Added gradient background matching prototype style
- Copied all SVG icons from prototype (US flags, HK flag, etc.)
- Implemented proper icon display with white background container
- Added category color variants (blue, green, purple)
- Risk level indicators with accurate colors
- Styled progress bar matching prototype
- Proper spacing and typography from CSS

Visual Elements:
- Radial gradient backgrounds on cards
- Glass-morphism effect with transparency
- Shadow and border effects from prototype
- Icon container with white background and shadow ring
- Category badges with colored backgrounds

Assets Added:
- frame-9230.svg, frame-9231.svg (US flag icons)
- hk0.svg (HK flag icon)
- chart-square1.svg
- All component SVG files from prototype
- View toggle icons

Styling:
- Followed prototype's style.css specifications
- Maintained exact color values and gradients
- Proper border radius and spacing
- Typography matching prototype

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-30 05:35:08 +00:00
parent 4c86b7cce1
commit b98500e61f
66 changed files with 443 additions and 291 deletions

View File

@@ -1,12 +1,12 @@
"use client";
import Image from "next/image";
import { Button } from "@heroui/react";
interface ProductCardProps {
name: string;
category: string;
icon?: string;
categoryColor: "blue" | "green" | "purple";
iconType: "us-flag-1" | "hk-flag" | "us-flag-2";
yieldAPY: string;
poolCap: string;
maturity: string;
@@ -21,7 +21,8 @@ interface ProductCardProps {
export default function ProductCard({
name,
category,
icon,
categoryColor,
iconType,
yieldAPY,
poolCap,
maturity,
@@ -32,153 +33,196 @@ export default function ProductCard({
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";
const getCategoryStyle = () => {
switch (categoryColor) {
case "blue":
return "bg-[rgba(59,130,246,0.1)] border-[rgba(59,130,246,0.3)]";
case "green":
return "bg-[rgba(34,197,94,0.1)] border-[rgba(34,197,94,0.3)]";
case "purple":
return "bg-[rgba(168,85,247,0.1)] border-[rgba(168,85,247,0.3)]";
default:
return "bg-gray-300";
return "bg-[rgba(59,130,246,0.1)] border-[rgba(59,130,246,0.3)]";
}
};
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";
const getRiskBarColor = () => {
switch (riskLevel) {
case 1:
return "#10b981"; // green
case 2:
return "#f59e0b"; // yellow/orange
case 3:
return "#ef4444"; // red
default:
return "#9ca3af";
}
};
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";
default:
return "/frame-9230.svg";
}
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">
<div
className="rounded-3xl border border-[rgba(255,255,255,0.6)] p-6 flex-1 relative overflow-hidden"
style={{
background: `
radial-gradient(closest-side, rgba(186, 230, 253, 0.15) 0%, rgba(0, 0, 0, 0) 50%),
radial-gradient(closest-side, rgba(199, 210, 254, 0.15) 0%, rgba(0, 0, 0, 0) 50%),
linear-gradient(to left, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.4))
`,
boxShadow: "0px 8px 32px 0px rgba(0, 0, 0, 0.03)",
}}
>
{/* 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="pb-6 border-b-0">
<div className="flex items-center gap-4">
{/* Icon Container */}
<div
className={`inline-flex self-start px-3 py-1 rounded-full border text-caption-tiny font-medium ${getCategoryColor(
category
)}`}
className="flex items-center justify-center w-16 h-16 rounded-2xl border border-[rgba(255,255,255,0.8)]"
style={{
background: "rgba(255, 255, 255, 0.4)",
boxShadow: "0px 0px 0px 4px rgba(255, 255, 255, 0.1)",
}}
>
{category}
<div className="relative w-12 h-12">
<Image
src={getIconSrc()}
alt={name}
width={48}
height={48}
className="w-full h-full object-contain"
/>
</div>
</div>
{/* Title and Category */}
<div className="flex flex-col gap-0">
<h3 className="text-lg font-bold text-[#111827] dark:text-white leading-[150%]">
{name}
</h3>
<div
className={`inline-flex self-start px-3 py-1 rounded-full border mt-1 ${getCategoryStyle()}`}
>
<span className="text-xs font-medium text-[#111827] dark:text-white">
{category}
</span>
</div>
</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">
{/* Metrics Section */}
<div className="py-6 flex gap-8">
<div className="flex flex-col gap-1">
<span className="text-sm text-[#6b7280] dark:text-gray-400">
Yield APY
</span>
<span className="text-base font-bold text-[#111827] dark:text-white">
{yieldAPY}
</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-sm text-[#6b7280] dark:text-gray-400">
Pool CaP
</span>
<span className="text-base font-bold text-[#111827] dark:text-white">
{poolCap}
</span>
</div>
</div>
{/* Details Grid */}
<div className="flex gap-6 pb-6">
<div className="flex flex-col gap-4 flex-1">
<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">
<span className="text-sm text-[#6b7280] dark:text-gray-400">
Maturity
</span>
<span className="text-body-default font-medium text-text-primary dark:text-white">
<span className="text-sm font-medium text-[#111827] 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">
<span className="text-sm text-[#6b7280] dark:text-gray-400">
Lock-Up
</span>
<span className="text-body-default font-medium text-text-primary dark:text-white">
<span className="text-sm font-medium text-[#111827] dark:text-white">
{lockUp}
</span>
</div>
</div>
{/* Column 2 */}
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-4 flex-1">
<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">
<span className="text-sm text-[#6b7280] 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">
<span className="text-sm font-medium text-[#111827] 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"
}`}
className="w-1 h-3 rounded-sm"
style={{
backgroundColor:
level <= riskLevel ? getRiskBarColor() : "#d1d5db",
}}
/>
))}
</div>
</div>
</div>
<div className="flex flex-col gap-1">
<span className="text-body-small text-text-tertiary dark:text-gray-400">
<span className="text-sm text-[#6b7280] dark:text-gray-400">
Circulating supply
</span>
<span className="text-body-default font-medium text-text-primary dark:text-white">
<span className="text-sm font-medium text-[#111827] 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}%` }}
/>
{/* Pool Capacity & Button */}
<div className="pt-6 border-t border-[rgba(0,0,0,0.05)]">
<div className="flex items-end gap-6">
<div className="flex-1 flex flex-col gap-2">
<div className="flex items-center justify-between">
<span className="text-sm text-[#6b7280] dark:text-gray-400">
Pool Capacity
</span>
<span className="text-sm font-medium text-[#111827] dark:text-white">
{poolCapacityPercent}% Filled
</span>
</div>
<div className="w-full h-2 bg-[#e5e7eb] dark:bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full bg-[#111827] dark:bg-blue-500 rounded-full transition-all"
style={{ width: `${poolCapacityPercent}%` }}
/>
</div>
</div>
<button
onClick={onInvest}
className="px-6 py-3 bg-[#111827] dark:bg-blue-600 text-white font-bold text-sm rounded-xl hover:bg-gray-800 dark:hover:bg-blue-700 transition-colors whitespace-nowrap"
>
Invest
</button>
</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>
);