initial commit
This commit is contained in:
225
components/product/APYHistoryCard.tsx
Normal file
225
components/product/APYHistoryCard.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
import * as echarts from "echarts";
|
||||
|
||||
export default function APYHistoryCard() {
|
||||
const { t } = useApp();
|
||||
const [activeTab, setActiveTab] = useState<"apy" | "price">("apy");
|
||||
const chartRef = useRef<HTMLDivElement>(null);
|
||||
const chartInstance = useRef<echarts.ECharts | null>(null);
|
||||
|
||||
// 生成从#FBEADE到#C65122的9个渐变颜色
|
||||
const getColorGradient = () => {
|
||||
const colors = [
|
||||
"#FBEADE",
|
||||
"#F5D4BE",
|
||||
"#EFBF9E",
|
||||
"#E9AA7E",
|
||||
"#E3955E",
|
||||
"#DD804E",
|
||||
"#D76B3E",
|
||||
"#D1562E",
|
||||
"#C65122",
|
||||
];
|
||||
return colors;
|
||||
};
|
||||
|
||||
const chartData = [4.2, 5.1, 5.8, 6.3, 7.1, 8.2, 9.5, 10.8, 12.4];
|
||||
|
||||
useEffect(() => {
|
||||
if (chartRef.current) {
|
||||
chartInstance.current = echarts.init(chartRef.current);
|
||||
updateChart();
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
chartInstance.current?.resize();
|
||||
};
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
chartInstance.current?.dispose();
|
||||
};
|
||||
}, [activeTab]);
|
||||
|
||||
const updateChart = () => {
|
||||
if (!chartInstance.current) return;
|
||||
|
||||
const colors = getColorGradient();
|
||||
|
||||
// 模拟价格数据
|
||||
const priceData = [1.12, 1.15, 1.18, 1.14, 1.21, 1.19, 1.25, 1.28, 1.32];
|
||||
|
||||
const option: echarts.EChartsOption = {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
show: true,
|
||||
confine: true,
|
||||
backgroundColor: "rgba(17, 24, 39, 0.9)",
|
||||
borderColor: "#374151",
|
||||
textStyle: {
|
||||
color: "#f9fafb",
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
},
|
||||
formatter: function(params: any) {
|
||||
const data = params[0];
|
||||
const suffix = activeTab === "apy" ? "%" : " USDC";
|
||||
return `<div style="padding: 4px 8px;">
|
||||
<span style="color: #9ca3af; font-size: 11px;">Day ${data.dataIndex + 1}</span><br/>
|
||||
<span style="color: #10b981; font-weight: 600; font-size: 14px;">${data.value}${suffix}</span>
|
||||
</div>`;
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: chartData.map((_, i) => i + 1),
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
activeTab === "apy"
|
||||
? {
|
||||
data: chartData.map((value, index) => ({
|
||||
value,
|
||||
itemStyle: {
|
||||
color: colors[index],
|
||||
borderRadius: [2, 2, 0, 0],
|
||||
},
|
||||
})),
|
||||
type: "bar",
|
||||
barWidth: "50%",
|
||||
}
|
||||
: {
|
||||
data: priceData,
|
||||
type: "line",
|
||||
smooth: true,
|
||||
symbol: "circle",
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: "#10b981",
|
||||
width: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
color: "#10b981",
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: "linear",
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: "rgba(16, 185, 129, 0.3)" },
|
||||
{ offset: 1, color: "rgba(16, 185, 129, 0)" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
chartInstance.current.setOption(option);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-6 flex flex-col gap-6">
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-6 border-b border-border-gray dark:border-gray-700">
|
||||
<button
|
||||
onClick={() => setActiveTab("apy")}
|
||||
className={`pb-3 px-1 text-body-small font-bold transition-colors ${
|
||||
activeTab === "apy"
|
||||
? "text-text-primary dark:text-white border-b-2 border-text-primary dark:border-white -mb-[1px]"
|
||||
: "text-text-tertiary dark:text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{t("apy.apyHistory")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("price")}
|
||||
className={`pb-3 px-1 text-body-small font-bold transition-colors ${
|
||||
activeTab === "price"
|
||||
? "text-text-primary dark:text-white border-b-2 border-text-primary dark:border-white -mb-[1px]"
|
||||
: "text-text-tertiary dark:text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{t("apy.priceHistory")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Chart Area */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-caption-tiny font-medium text-text-tertiary dark:text-gray-400">
|
||||
{t("apy.lastDays")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* ECharts Chart */}
|
||||
<div
|
||||
ref={chartRef}
|
||||
className="w-full"
|
||||
style={{
|
||||
height: "140px",
|
||||
background: activeTab === "price"
|
||||
? "linear-gradient(0deg, rgba(16, 185, 129, 0.1) 0%, transparent 100%)"
|
||||
: undefined,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-caption-tiny font-medium text-text-tertiary dark:text-gray-400">
|
||||
{t("apy.highest")}
|
||||
</span>
|
||||
<span className="text-body-small font-bold text-text-primary dark:text-white">
|
||||
{activeTab === "apy" ? "24.8%" : "$1.32"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-caption-tiny font-medium text-text-tertiary dark:text-gray-400">
|
||||
{t("apy.lowest")}
|
||||
</span>
|
||||
<span className="text-body-small font-bold text-text-primary dark:text-white">
|
||||
{activeTab === "apy" ? "18.2%" : "$1.12"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
237
components/product/AssetCustodyVerification.tsx
Normal file
237
components/product/AssetCustodyVerification.tsx
Normal file
@@ -0,0 +1,237 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
interface VerificationCardProps {
|
||||
icon: string;
|
||||
title: string;
|
||||
description: string;
|
||||
buttonText: string;
|
||||
}
|
||||
|
||||
function VerificationCard({ icon, title, description, buttonText }: VerificationCardProps) {
|
||||
return (
|
||||
<div className="flex-1 bg-bg-surface dark:bg-gray-700 rounded-2xl border border-border-normal dark:border-gray-600 p-6 flex flex-col justify-between">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-5 h-5 flex-shrink-0">
|
||||
<Image src={icon} alt={title} width={20} height={20} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-body-default font-bold text-text-primary dark:text-white">
|
||||
{title}
|
||||
</h4>
|
||||
<p className="text-caption-tiny font-regular text-[#9ca1af] dark:text-gray-400">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button className="flex items-center gap-2 hover:opacity-80 transition-opacity mt-[118px]">
|
||||
<span className="text-[10px] font-bold leading-[150%] tracking-[0.01em] text-[#9ca1af] dark:text-gray-400">
|
||||
{buttonText}
|
||||
</span>
|
||||
<Image src="/component-118.svg" alt="" width={16} height={16} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AssetCustodyVerification() {
|
||||
const { t } = useApp();
|
||||
return (
|
||||
<div className="flex flex-col gap-8 w-full">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<h2 className="text-body-large font-bold text-text-primary dark:text-white">
|
||||
{t("custody.title")}
|
||||
</h2>
|
||||
<p className="text-body-small font-regular text-[#9ca1af] dark:text-gray-400">
|
||||
{t("custody.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Holdings Table Card */}
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-8 flex flex-col gap-6">
|
||||
{/* Table Header */}
|
||||
<div className="flex flex-col gap-6 pb-6 border-b border-border-gray dark:border-gray-700">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h3 className="text-body-default font-bold text-text-primary dark:text-white">
|
||||
{t("custody.underlyingHoldings")}
|
||||
</h3>
|
||||
<p className="text-caption-tiny font-regular text-[#9ca1af] dark:text-gray-400">
|
||||
{t("custody.verifiedBy")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Image src="/component-115.svg" alt="" width={16} height={16} />
|
||||
<span className="text-caption-tiny font-medium text-[#9ca1af] dark:text-gray-400">
|
||||
{t("custody.lastUpdated")}: 2 {t("custody.minutesAgo")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="flex flex-col">
|
||||
{/* Table Header Row */}
|
||||
<div className="grid grid-cols-5 gap-4 pb-4 border-b border-border-gray dark:border-gray-700">
|
||||
<div className="text-caption-tiny font-bold uppercase tracking-wider text-[#9ca1af] dark:text-gray-400">
|
||||
{t("custody.custodian")}
|
||||
</div>
|
||||
<div className="text-caption-tiny font-bold uppercase tracking-wider text-[#9ca1af] dark:text-gray-400">
|
||||
{t("custody.assetType")}
|
||||
</div>
|
||||
<div className="text-caption-tiny font-bold uppercase tracking-wider text-[#9ca1af] dark:text-gray-400">
|
||||
{t("custody.maturity")}
|
||||
</div>
|
||||
<div className="text-caption-tiny font-bold uppercase tracking-wider text-[#9ca1af] dark:text-gray-400">
|
||||
{t("custody.valueUSD")}
|
||||
</div>
|
||||
<div className="text-caption-tiny font-bold uppercase tracking-wider text-[#9ca1af] dark:text-gray-400">
|
||||
{t("custody.status")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table Body Row */}
|
||||
<div className="grid grid-cols-5 gap-4 py-6">
|
||||
{/* Custodian */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, rgba(255, 137, 4, 1) 0%, rgba(245, 73, 0, 1) 100%)",
|
||||
}}
|
||||
>
|
||||
<span className="text-[13.5px] font-bold leading-[19px] text-white tracking-tight">
|
||||
GY
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-body-small font-bold text-text-primary dark:text-white">
|
||||
{t("custody.morganStanley")}
|
||||
</span>
|
||||
<span className="text-caption-tiny font-regular text-[#9ca1af] dark:text-gray-400">
|
||||
{t("custody.primeBroker")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Asset Type */}
|
||||
<div className="flex items-center">
|
||||
<span className="text-body-small font-medium text-text-primary dark:text-white">
|
||||
{t("custody.usEquityPortfolio")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Maturity */}
|
||||
<div className="flex flex-col justify-center">
|
||||
<span className="text-body-small font-medium text-text-primary dark:text-white">
|
||||
05 Feb 2026
|
||||
</span>
|
||||
<span className="text-caption-tiny font-regular text-[#9ca1af] dark:text-gray-400">
|
||||
(77 {t("custody.days")})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Value */}
|
||||
<div className="flex items-center">
|
||||
<span className="text-body-small font-medium text-text-primary dark:text-white">
|
||||
$12,500,000.00
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Status */}
|
||||
<div className="flex items-center">
|
||||
<div className="rounded-full px-3 py-1.5 flex items-center gap-2 bg-[#f2fcf7] dark:bg-green-900/20">
|
||||
<Image src="/component-116.svg" alt="" width={12} height={12} />
|
||||
<span className="text-[10px] font-bold leading-[150%] text-[#10b981] dark:text-green-400">
|
||||
{t("custody.verified")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table Footer Row */}
|
||||
<div className="grid grid-cols-5 gap-4 pt-6 border-t border-border-gray dark:border-gray-700">
|
||||
<div className="col-span-3 flex items-center">
|
||||
<span className="text-body-small font-bold text-text-primary dark:text-white">
|
||||
{t("custody.totalValue")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-span-2 flex items-center">
|
||||
<span className="text-body-default font-bold text-text-primary dark:text-white">
|
||||
$12,500,000.00
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Verification Cards Row */}
|
||||
<div className="flex flex-row gap-6 pt-6">
|
||||
<VerificationCard
|
||||
icon="/component-117.svg"
|
||||
title={t("custody.smartContract")}
|
||||
description={t("custody.smartContractDesc")}
|
||||
buttonText={t("custody.viewReports")}
|
||||
/>
|
||||
<VerificationCard
|
||||
icon="/component-119.svg"
|
||||
title={t("custody.compliance")}
|
||||
description={t("custody.complianceDesc")}
|
||||
buttonText={t("custody.viewReports")}
|
||||
/>
|
||||
<VerificationCard
|
||||
icon="/component-121.svg"
|
||||
title={t("custody.proofOfReserves")}
|
||||
description={t("custody.proofDesc")}
|
||||
buttonText={t("custody.viewReports")}
|
||||
/>
|
||||
|
||||
{/* Independent Verifications */}
|
||||
<div className="flex-1 rounded-2xl border bg-[#f9fafb] dark:bg-gray-700 border-[#e5e7eb] dark:border-gray-600 p-6 flex flex-col">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-body-default font-bold text-text-primary dark:text-white">
|
||||
{t("custody.independentVerifications")}
|
||||
</h3>
|
||||
<p className="text-body-small font-regular text-[#9ca1af] dark:text-gray-400">
|
||||
{t("custody.independentDesc")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 mt-6">
|
||||
<div className="rounded-2xl border bg-white dark:bg-gray-800 border-[#e5e7eb] dark:border-gray-600 p-4 flex items-center justify-between">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-body-small font-bold text-text-primary dark:text-white">
|
||||
{t("custody.attestationReport")}
|
||||
</span>
|
||||
<span className="text-caption-tiny font-regular text-[#9ca1af] dark:text-gray-400">
|
||||
November 2025
|
||||
</span>
|
||||
</div>
|
||||
<Image src="/component-123.svg" alt="" width={24} height={24} />
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border bg-white dark:bg-gray-800 border-[#e5e7eb] dark:border-gray-600 p-4 flex items-center justify-between">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-body-small font-bold text-text-primary dark:text-white">
|
||||
{t("custody.attestationReport")}
|
||||
</span>
|
||||
<span className="text-caption-tiny font-regular text-[#9ca1af] dark:text-gray-400">
|
||||
October 2025
|
||||
</span>
|
||||
</div>
|
||||
<Image src="/component-124.svg" alt="" width={24} height={24} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="flex items-center gap-2 hover:opacity-80 transition-opacity mt-4">
|
||||
<span className="text-[10px] font-bold leading-[150%] tracking-[0.01em] text-[#9ca1af] dark:text-gray-400">
|
||||
{t("custody.viewAllArchive")}
|
||||
</span>
|
||||
<Image src="/component-125.svg" alt="" width={16} height={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
components/product/AssetCustodyVerificationTab.tsx
Normal file
9
components/product/AssetCustodyVerificationTab.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import AssetCustodyVerification from "./AssetCustodyVerification";
|
||||
|
||||
export default function AssetCustodyVerificationTab() {
|
||||
return (
|
||||
<div className="flex flex-col gap-8 w-full">
|
||||
<AssetCustodyVerification />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
components/product/AssetDescriptionCard.tsx
Normal file
18
components/product/AssetDescriptionCard.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
export default function AssetDescriptionCard() {
|
||||
const { t } = useApp();
|
||||
|
||||
return (
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-8 flex flex-col gap-4 h-[380px]">
|
||||
<h3 className="text-body-large font-bold text-text-primary dark:text-white">
|
||||
{t("description.title")}
|
||||
</h3>
|
||||
<div className="text-body-default font-regular text-text-primary dark:text-gray-300 leading-relaxed whitespace-pre-line flex-1 overflow-y-auto">
|
||||
{t("description.content")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
components/product/AssetDescriptionTab.tsx
Normal file
9
components/product/AssetDescriptionTab.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import AssetDescriptionCard from "./AssetDescriptionCard";
|
||||
|
||||
export default function AssetDescriptionTab() {
|
||||
return (
|
||||
<div className="flex flex-col gap-8 w-full">
|
||||
<AssetDescriptionCard />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
116
components/product/AssetOverviewCard.tsx
Normal file
116
components/product/AssetOverviewCard.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
interface OverviewItemProps {
|
||||
icon: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function OverviewItem({ icon, label, value }: OverviewItemProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-5 h-6 flex-shrink-0">
|
||||
<Image src={icon} alt={label} width={20} height={24} />
|
||||
</div>
|
||||
<span className="text-body-small font-medium text-text-tertiary dark:text-gray-400">
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-body-small font-medium text-text-primary dark:text-white">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AssetOverviewCard() {
|
||||
const { t } = useApp();
|
||||
|
||||
return (
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-8 flex flex-col gap-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-body-large font-bold text-text-primary dark:text-white">
|
||||
{t("assetOverview.title")}
|
||||
</h3>
|
||||
<div
|
||||
className="rounded-full border flex items-center gap-2 px-3 py-1.5"
|
||||
style={{
|
||||
backgroundColor: "#fffbf5",
|
||||
borderColor: "#ffedd5",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="w-1.5 h-1.5 rounded-full flex-shrink-0"
|
||||
style={{ backgroundColor: "#ffb933" }}
|
||||
/>
|
||||
<span
|
||||
className="text-xs font-semibold leading-4"
|
||||
style={{ color: "#ffb933" }}
|
||||
>
|
||||
{t("assetOverview.mediumRisk")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Overview Items */}
|
||||
<div className="flex gap-12 w-full">
|
||||
<div className="flex-1 flex flex-col gap-5">
|
||||
<OverviewItem
|
||||
icon="/component-11.svg"
|
||||
label={t("assetOverview.underlyingAssets")}
|
||||
value={t("assetOverview.usEquityIndex")}
|
||||
/>
|
||||
<OverviewItem
|
||||
icon="/component-12.svg"
|
||||
label={t("assetOverview.maturityRange")}
|
||||
value="05 Feb 2026"
|
||||
/>
|
||||
<OverviewItem
|
||||
icon="/component-13.svg"
|
||||
label={t("assetOverview.cap")}
|
||||
value="$50,000,000"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col gap-5">
|
||||
<OverviewItem
|
||||
icon="/component-14.svg"
|
||||
label={t("assetOverview.minInvestment")}
|
||||
value="100 USDC"
|
||||
/>
|
||||
<div className="flex flex-col gap-3 w-full">
|
||||
<OverviewItem
|
||||
icon="/component-15.svg"
|
||||
label={t("assetOverview.poolCapacity")}
|
||||
value="75%"
|
||||
/>
|
||||
<div className="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-blue-500 rounded-full" style={{ width: "75%" }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="border-t border-border-gray dark:border-gray-700" />
|
||||
|
||||
{/* Current Price */}
|
||||
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl p-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Image src="/component-16.svg" alt="Price" width={24} height={24} />
|
||||
<span className="text-body-small font-medium text-text-tertiary dark:text-gray-400">
|
||||
{t("assetOverview.currentPrice")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-[20px] font-bold leading-[140%]">
|
||||
<span className="text-text-primary dark:text-white">1 GY-US = </span>
|
||||
<span style={{ color: "#10b981" }}>1.04 USDC</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
63
components/product/ContentSection.tsx
Normal file
63
components/product/ContentSection.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import TabNavigation from "./TabNavigation";
|
||||
import OverviewTab from "./OverviewTab";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
export default function ContentSection() {
|
||||
const { t } = useApp();
|
||||
|
||||
const tabs = [
|
||||
{ id: "overview", label: t("tabs.overview") },
|
||||
{ id: "asset-description", label: t("tabs.assetDescription") },
|
||||
{ id: "analytics", label: t("tabs.analytics") },
|
||||
{ id: "performance-analysis", label: t("tabs.performanceAnalysis") },
|
||||
{ id: "asset-custody", label: t("tabs.assetCustody") },
|
||||
];
|
||||
|
||||
const [activeTab, setActiveTab] = useState("overview");
|
||||
|
||||
const handleTabChange = (tabId: string) => {
|
||||
// If clicking asset-description, performance-analysis, or asset-custody, scroll to that section
|
||||
if (tabId === "asset-description" || tabId === "performance-analysis" || tabId === "asset-custody") {
|
||||
setTimeout(() => {
|
||||
const element = document.getElementById(tabId);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
}, 100);
|
||||
// Keep active tab as overview
|
||||
setActiveTab("overview");
|
||||
} else {
|
||||
setActiveTab(tabId);
|
||||
}
|
||||
};
|
||||
|
||||
// Show OverviewTab for overview, asset-description, analytics, performance-analysis, and asset-custody
|
||||
const showOverview = ["overview", "asset-description", "analytics", "performance-analysis", "asset-custody"].includes(activeTab);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-full">
|
||||
{/* Tab Navigation */}
|
||||
<TabNavigation
|
||||
tabs={tabs}
|
||||
defaultActiveId="overview"
|
||||
onTabChange={handleTabChange}
|
||||
/>
|
||||
|
||||
{/* Content Area */}
|
||||
<div className="w-full">
|
||||
{showOverview && <OverviewTab />}
|
||||
{!showOverview && (
|
||||
<div className="bg-bg-surface rounded-2xl border border-border-normal p-6">
|
||||
<p className="text-text-tertiary">
|
||||
{tabs.find((t) => t.id === activeTab)?.label} content will be
|
||||
displayed here...
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
210
components/product/MintSwapPanel.tsx
Normal file
210
components/product/MintSwapPanel.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
import { Tabs, Tab, Button } from "@heroui/react";
|
||||
import ReviewModal from "@/components/common/ReviewModal";
|
||||
import WithdrawModal from "@/components/common/WithdrawModal";
|
||||
import { buttonStyles } from "@/lib/buttonStyles";
|
||||
export default function MintSwapPanel() {
|
||||
const { t } = useApp();
|
||||
const [activeAction, setActiveAction] = useState<"deposit" | "withdraw">("deposit");
|
||||
const [amount, setAmount] = useState<string>("");
|
||||
const [isReviewModalOpen, setIsReviewModalOpen] = useState(false);
|
||||
const [isWithdrawModalOpen, setIsWithdrawModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 flex flex-col overflow-hidden">
|
||||
{/* Content */}
|
||||
<div className="flex flex-col gap-6 p-6">
|
||||
{/* Deposit/Withdraw Toggle */}
|
||||
<Tabs
|
||||
selectedKey={activeAction}
|
||||
onSelectionChange={(key) => setActiveAction(key as "deposit" | "withdraw")}
|
||||
variant="solid"
|
||||
classNames={{
|
||||
base: "w-full",
|
||||
tabList: "bg-[#f9fafb] dark:bg-gray-700 rounded-xl p-1 gap-0 w-full",
|
||||
cursor: "bg-bg-surface dark:bg-gray-600 shadow-sm",
|
||||
tab: "h-8 px-4",
|
||||
tabContent: "text-body-small font-medium text-text-tertiary dark:text-gray-400 group-data-[selected=true]:font-bold group-data-[selected=true]:text-text-primary dark:group-data-[selected=true]:text-white",
|
||||
}}
|
||||
>
|
||||
<Tab key="deposit" title={t("mintSwap.deposit")} />
|
||||
<Tab key="withdraw" title={t("mintSwap.withdraw")} />
|
||||
</Tabs>
|
||||
|
||||
{/* Input Area */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 p-4 flex flex-col gap-3">
|
||||
{/* Label and Balance */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-caption-tiny font-medium text-[#4b5563] dark:text-gray-400">
|
||||
{t("mintSwap.deposit")}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/icon0.svg" alt="" width={12} height={12} />
|
||||
<span className="text-caption-tiny font-medium text-[#4b5563] dark:text-gray-400">
|
||||
{t("mintSwap.balance")}: $12,500.00
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Input Row */}
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex flex-col items-start flex-1">
|
||||
<input
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
className="w-full text-left text-heading-h3 font-bold text-text-primary dark:text-white placeholder:text-[#d1d5db] dark:placeholder:text-gray-500 bg-transparent border-none outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
||||
/>
|
||||
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400">
|
||||
{amount ? `≈ $${amount}` : "--"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
className={buttonStyles({ intent: "max" })}
|
||||
onPress={() => setAmount("12500")}
|
||||
>
|
||||
{t("mintSwap.max")}
|
||||
</Button>
|
||||
<button className="bg-bg-surface dark:bg-gray-600 rounded-full border border-border-normal dark:border-gray-500 px-2 h-[46px] flex items-center gap-2">
|
||||
<Image
|
||||
src="/usd-coin-usdc-logo-10.svg"
|
||||
alt="USDC"
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
<span className="text-body-default font-bold text-text-primary dark:text-white">USDC</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Estimated Returns */}
|
||||
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 p-4 flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between h-5">
|
||||
<span className="text-body-small font-bold text-[#4b5563] dark:text-gray-300">
|
||||
{t("mintSwap.estimatedReturns")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between h-5">
|
||||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400">
|
||||
{t("mintSwap.estAPY")}
|
||||
</span>
|
||||
<span className="text-body-small font-bold text-[#ff6900] dark:text-orange-400">
|
||||
22%
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between h-5">
|
||||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400">
|
||||
{t("mintSwap.estReturns")}
|
||||
</span>
|
||||
<span className="text-body-small font-bold text-[#10b981] dark:text-green-400">
|
||||
~ $0.50
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Transaction Summary */}
|
||||
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 p-4 flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between h-5">
|
||||
<span className="text-body-small font-bold text-[#4b5563] dark:text-gray-300">
|
||||
{t("mintSwap.transactionSummary")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between h-5">
|
||||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400">
|
||||
{t("mintSwap.youGet")}
|
||||
</span>
|
||||
<span className="text-body-small font-bold text-[#10b981] dark:text-green-400">
|
||||
9852.21 GYUS
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between h-5">
|
||||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400">
|
||||
{t("mintSwap.salesPrice")}
|
||||
</span>
|
||||
<span className="text-body-small font-bold text-text-primary dark:text-white">$1.04 USDC</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between h-5">
|
||||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400">
|
||||
{t("mintSwap.fee")}
|
||||
</span>
|
||||
<span className="text-body-small font-bold text-[#dc2626] dark:text-red-400">
|
||||
-$50 (0.5%)
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between h-5">
|
||||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400">
|
||||
{t("mintSwap.gas")}
|
||||
</span>
|
||||
<span className="text-body-small font-bold text-[#dc2626] dark:text-red-400">
|
||||
-$0.09
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<Button
|
||||
isDisabled={!amount || parseFloat(amount) <= 0}
|
||||
color="default"
|
||||
variant="solid"
|
||||
className={buttonStyles({ intent: "theme" })}
|
||||
endContent={<Image src="/icon11.svg" alt="" width={20} height={20} />}
|
||||
onPress={() => {
|
||||
if (amount && parseFloat(amount) > 0) {
|
||||
if (activeAction === "deposit") {
|
||||
setIsReviewModalOpen(true);
|
||||
} else {
|
||||
setIsWithdrawModalOpen(true);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{activeAction === "deposit"
|
||||
? t("mintSwap.approveDeposit")
|
||||
: "Approve and Withdraw"}
|
||||
</Button>
|
||||
|
||||
{/* Review Modal for Deposit */}
|
||||
<ReviewModal
|
||||
isOpen={isReviewModalOpen}
|
||||
onClose={() => setIsReviewModalOpen(false)}
|
||||
amount={amount}
|
||||
/>
|
||||
|
||||
{/* Withdraw Modal */}
|
||||
<WithdrawModal
|
||||
isOpen={isWithdrawModalOpen}
|
||||
onClose={() => setIsWithdrawModalOpen(false)}
|
||||
amount={amount}
|
||||
/>
|
||||
|
||||
{/* Terms */}
|
||||
<div className="flex flex-col gap-0 text-center">
|
||||
<div className="text-caption-tiny font-regular">
|
||||
<span className="text-[#9ca1af] dark:text-gray-400">
|
||||
{t("mintSwap.termsText")}{" "}
|
||||
</span>
|
||||
<span className="text-[#10b981] dark:text-green-400">
|
||||
{t("mintSwap.termsOfService")}
|
||||
</span>
|
||||
<span className="text-[#9ca1af] dark:text-gray-400">
|
||||
{" "}{t("mintSwap.and")}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-caption-tiny font-regular text-[#10b981] dark:text-green-400">
|
||||
{t("mintSwap.privacyPolicy")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
55
components/product/OverviewTab.tsx
Normal file
55
components/product/OverviewTab.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import ProductHeader from "./ProductHeader";
|
||||
import StatsCards from "@/components/fundmarket/StatsCards";
|
||||
import AssetOverviewCard from "./AssetOverviewCard";
|
||||
import APYHistoryCard from "./APYHistoryCard";
|
||||
import AssetDescriptionCard from "./AssetDescriptionCard";
|
||||
import MintSwapPanel from "./MintSwapPanel";
|
||||
import ProtocolInformation from "./ProtocolInformation";
|
||||
import PerformanceAnalysis from "./PerformanceAnalysis";
|
||||
import Season1Rewards from "./Season1Rewards";
|
||||
import AssetCustodyVerification from "./AssetCustodyVerification";
|
||||
|
||||
export default function OverviewTab() {
|
||||
return (
|
||||
<div className="flex flex-col gap-8 w-full">
|
||||
{/* Product Header */}
|
||||
<ProductHeader />
|
||||
|
||||
{/* Stats Cards */}
|
||||
<StatsCards />
|
||||
|
||||
{/* Main Content Grid */}
|
||||
<div className="grid grid-cols-3 gap-8">
|
||||
{/* Left Column - 2/3 width */}
|
||||
<div className="col-span-2 flex flex-col gap-8">
|
||||
<AssetOverviewCard />
|
||||
<APYHistoryCard />
|
||||
<div id="asset-description">
|
||||
<AssetDescriptionCard />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column - 1/3 width */}
|
||||
<div className="col-span-1">
|
||||
<div className="sticky top-8 flex flex-col gap-8">
|
||||
<MintSwapPanel />
|
||||
<ProtocolInformation />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Season 1 Rewards */}
|
||||
<Season1Rewards />
|
||||
|
||||
{/* Performance Analysis */}
|
||||
<div id="performance-analysis">
|
||||
<PerformanceAnalysis />
|
||||
</div>
|
||||
|
||||
{/* Asset Custody & Verification */}
|
||||
<div id="asset-custody">
|
||||
<AssetCustodyVerification />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
212
components/product/PerformanceAnalysis.tsx
Normal file
212
components/product/PerformanceAnalysis.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
interface CalendarDayProps {
|
||||
day: number | null;
|
||||
value: string;
|
||||
type: "positive" | "negative" | "neutral" | "current";
|
||||
}
|
||||
|
||||
function CalendarDay({ day, value, type }: CalendarDayProps) {
|
||||
// Empty cell
|
||||
if (day === null) {
|
||||
return <div className="flex-1" />;
|
||||
}
|
||||
|
||||
const typeStyles = {
|
||||
positive: "bg-[#f2fcf7] dark:bg-green-900/20 border-[#cef3e0] dark:border-green-700/30",
|
||||
negative: "bg-[#fff8f7] dark:bg-red-900/20 border-[#ffdbd5] dark:border-red-700/30",
|
||||
neutral: "bg-[#f9fafb] dark:bg-gray-700 border-[#f3f4f6] dark:border-gray-600",
|
||||
current: "bg-[#111827] dark:bg-[#111827] border-[#111827] dark:border-[#111827]",
|
||||
};
|
||||
|
||||
const isCurrent = type === "current";
|
||||
const dayTextStyle = isCurrent
|
||||
? "text-[#fcfcfd]"
|
||||
: "text-[#9ca1af] dark:text-gray-400";
|
||||
|
||||
// Value text color should match the type
|
||||
let valueTextStyle = "";
|
||||
if (isCurrent) {
|
||||
valueTextStyle = "text-[#10b981]";
|
||||
} else if (type === "positive") {
|
||||
valueTextStyle = "text-[#10b981] dark:text-green-400";
|
||||
} else if (type === "negative") {
|
||||
valueTextStyle = "text-[#dc2626] dark:text-red-400";
|
||||
} else {
|
||||
valueTextStyle = "text-[#9ca1af] dark:text-gray-400";
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`rounded border flex flex-col items-center justify-center flex-1 p-3 gap-6 ${typeStyles[type]}`}
|
||||
>
|
||||
<div className="w-full flex items-start">
|
||||
<span className={`text-[10px] font-bold leading-[150%] ${dayTextStyle}`}>
|
||||
{day}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full flex flex-col items-end gap-1">
|
||||
<span className={`text-body-small font-bold leading-[150%] ${valueTextStyle}`}>
|
||||
{value}
|
||||
</span>
|
||||
{isCurrent && (
|
||||
<span className="text-[10px] font-bold leading-[150%] tracking-[0.01em] text-[#9DA1AE]">
|
||||
Today
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface StatCardProps {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function StatCard({ label, value }: StatCardProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<span className="text-[10px] font-bold leading-[150%] tracking-[0.01em] text-[#9ca1af] dark:text-gray-400">
|
||||
{label}
|
||||
</span>
|
||||
<span className="text-body-large font-bold text-[#10b981] dark:text-green-400">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PerformanceAnalysis() {
|
||||
const { t } = useApp();
|
||||
const [currentMonth] = useState("November 2025");
|
||||
|
||||
// 模拟日历数据 - 5周数据
|
||||
const weekData = [
|
||||
[
|
||||
{ day: 31, value: "0.00%", type: "neutral" as const },
|
||||
{ day: 1, value: "+0.12%", type: "positive" as const },
|
||||
{ day: 2, value: "+0.08%", type: "positive" as const },
|
||||
{ day: 3, value: "-0.03%", type: "negative" as const },
|
||||
{ day: 4, value: "+0.15%", type: "positive" as const },
|
||||
{ day: 5, value: "+0.21%", type: "positive" as const },
|
||||
{ day: 6, value: "0.00%", type: "neutral" as const },
|
||||
],
|
||||
[
|
||||
{ day: 7, value: "+0.12%", type: "positive" as const },
|
||||
{ day: 8, value: "+0.12%", type: "positive" as const },
|
||||
{ day: 9, value: "-0.03%", type: "negative" as const },
|
||||
{ day: 10, value: "+0.08%", type: "positive" as const },
|
||||
{ day: 11, value: "-0.03%", type: "negative" as const },
|
||||
{ day: 12, value: "+0.21%", type: "positive" as const },
|
||||
{ day: 13, value: "0.00%", type: "neutral" as const },
|
||||
],
|
||||
[
|
||||
{ day: 14, value: "-0.03%", type: "negative" as const },
|
||||
{ day: 15, value: "-0.03%", type: "negative" as const },
|
||||
{ day: 16, value: "+0.15%", type: "positive" as const },
|
||||
{ day: 17, value: "+0.21%", type: "positive" as const },
|
||||
{ day: 18, value: "+0.08%", type: "positive" as const },
|
||||
{ day: 19, value: "0.00%", type: "neutral" as const },
|
||||
{ day: 20, value: "+0.12%", type: "positive" as const },
|
||||
],
|
||||
[
|
||||
{ day: 21, value: "+0.08%", type: "positive" as const },
|
||||
{ day: 22, value: "+0.15%", type: "positive" as const },
|
||||
{ day: 23, value: "-0.03%", type: "negative" as const },
|
||||
{ day: 24, value: "+0.12%", type: "current" as const },
|
||||
{ day: 25, value: "0.00%", type: "neutral" as const },
|
||||
{ day: 26, value: "+0.21%", type: "positive" as const },
|
||||
{ day: 27, value: "+0.08%", type: "positive" as const },
|
||||
],
|
||||
[
|
||||
{ day: 28, value: "+0.12%", type: "positive" as const },
|
||||
{ day: 30, value: "-0.03%", type: "negative" as const },
|
||||
{ day: 29, value: "-0.03%", type: "negative" as const },
|
||||
{ day: null, value: "", type: "neutral" as const },
|
||||
{ day: null, value: "", type: "neutral" as const },
|
||||
{ day: null, value: "", type: "neutral" as const },
|
||||
{ day: null, value: "", type: "neutral" as const },
|
||||
],
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-8 flex flex-col gap-8">
|
||||
{/* Top Section - Title and Stats */}
|
||||
<div className="flex items-start justify-between pb-8 border-b border-border-gray dark:border-gray-700">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h2 className="text-body-large font-bold text-text-primary dark:text-white">
|
||||
{t("performance.title")}
|
||||
</h2>
|
||||
<p className="text-body-small font-regular text-[#9ca1af] dark:text-gray-400">
|
||||
{t("performance.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-8">
|
||||
<StatCard label={t("performance.ytd")} value="+8.7%" />
|
||||
<StatCard label={t("performance.ytd")} value="+8.7%" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Calendar Section */}
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Calendar Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-6 h-6">
|
||||
<Image src="/component-114.svg" alt="" width={24} height={24} />
|
||||
</div>
|
||||
<h3 className="text-body-small font-bold text-text-primary dark:text-white">
|
||||
{t("performance.dailyNetReturns")}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button className="w-6 h-6 rounded-lg flex items-center justify-center hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||
<Image src="/icon9.svg" alt="Previous" width={16} height={16} />
|
||||
</button>
|
||||
<span className="text-body-small font-bold text-[#0a0a0a] dark:text-white tracking-tight">
|
||||
{currentMonth}
|
||||
</span>
|
||||
<button className="w-6 h-6 rounded-lg flex items-center justify-center hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||
<Image src="/icon10.svg" alt="Next" width={16} height={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Calendar */}
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Weekday Headers */}
|
||||
<div className="grid grid-cols-7 gap-2">
|
||||
{["sun", "mon", "tue", "wed", "thu", "fri", "sat"].map((day) => (
|
||||
<div key={day} className="flex items-center justify-center">
|
||||
<span className="text-[10px] font-bold leading-[150%] text-[#94a3b8] dark:text-gray-400">
|
||||
{t(`performance.weekdays.${day}`)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Calendar Grid */}
|
||||
<div className="flex flex-col gap-1">
|
||||
{weekData.map((week, weekIndex) => (
|
||||
<div key={weekIndex} className="grid grid-cols-7 gap-2">
|
||||
{week.map((day, dayIndex) => (
|
||||
<CalendarDay
|
||||
key={`${weekIndex}-${dayIndex}`}
|
||||
day={day.day}
|
||||
value={day.value}
|
||||
type={day.type}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
components/product/PerformanceAnalysisTab.tsx
Normal file
9
components/product/PerformanceAnalysisTab.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import PerformanceAnalysis from "./PerformanceAnalysis";
|
||||
|
||||
export default function PerformanceAnalysisTab() {
|
||||
return (
|
||||
<div className="flex flex-col gap-8 w-full">
|
||||
<PerformanceAnalysis />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
components/product/ProductHeader.tsx
Normal file
35
components/product/ProductHeader.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
export default function ProductHeader() {
|
||||
const { t } = useApp();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Product Title Section */}
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex gap-6">
|
||||
<div className="flex-shrink-0">
|
||||
<Image src="/lr0.svg" alt="Product Logo" width={80} height={80} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-heading-h2 font-bold text-text-primary dark:text-white">
|
||||
{t("product.gyUsEquityIndexToken")}
|
||||
</h1>
|
||||
<p className="text-body-default font-regular text-text-tertiary dark:text-gray-400">
|
||||
High-Yield US Equity Quantitative Strategy - Institutional Grade RWA
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-bg-subtle dark:bg-gray-700 rounded-lg border border-border-gray dark:border-gray-600">
|
||||
<Image src="/group-9270.svg" alt="Contract" width={16} height={16} />
|
||||
<span className="text-caption-tiny font-medium font-jetbrains text-text-tertiary dark:text-gray-400">
|
||||
{t("product.contractAddress")}: 0x1b19...4f2c
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
components/product/ProtocolInformation.tsx
Normal file
65
components/product/ProtocolInformation.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
interface ProtocolLinkProps {
|
||||
icon: string;
|
||||
label: string;
|
||||
arrowIcon: string;
|
||||
}
|
||||
|
||||
function ProtocolLink({ icon, label, arrowIcon }: ProtocolLinkProps) {
|
||||
return (
|
||||
<div className="group cursor-pointer rounded-xl border border-border-gray dark:border-gray-600 bg-bg-subtle dark:bg-gray-700 px-4 py-3.5 flex items-center justify-between w-full transition-all hover:border-black dark:hover:border-gray-400">
|
||||
<div className="flex items-center gap-1">
|
||||
<Image
|
||||
src={icon}
|
||||
alt=""
|
||||
width={20}
|
||||
height={24}
|
||||
className="transition-all group-hover:scale-110 group-hover:brightness-0 dark:group-hover:brightness-0 dark:group-hover:invert"
|
||||
/>
|
||||
<span className="text-body-small font-medium text-text-tertiary dark:text-gray-300 transition-all group-hover:font-bold group-hover:scale-[1.02] group-hover:text-text-primary dark:group-hover:text-white">
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
<Image
|
||||
src={arrowIcon}
|
||||
alt=""
|
||||
width={20}
|
||||
height={24}
|
||||
className="transition-all group-hover:scale-110 group-hover:brightness-0 dark:group-hover:brightness-0 dark:group-hover:invert"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProtocolInformation() {
|
||||
const { t } = useApp();
|
||||
|
||||
return (
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 px-6 py-8 flex flex-col gap-4 overflow-y-auto">
|
||||
<h3 className="text-body-large font-bold text-text-primary dark:text-white">
|
||||
{t("protocol.title")}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
<ProtocolLink
|
||||
icon="/component-17.svg"
|
||||
label={t("protocol.whitepaper")}
|
||||
arrowIcon="/component-18.svg"
|
||||
/>
|
||||
<ProtocolLink
|
||||
icon="/component-19.svg"
|
||||
label={t("protocol.documentation")}
|
||||
arrowIcon="/component-110.svg"
|
||||
/>
|
||||
<ProtocolLink
|
||||
icon="/component-111.svg"
|
||||
label={t("protocol.github")}
|
||||
arrowIcon="/component-112.svg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
101
components/product/Season1Rewards.tsx
Normal file
101
components/product/Season1Rewards.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
interface RewardStatProps {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function RewardStat({ label, value }: RewardStatProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-1.5">
|
||||
<span
|
||||
className="text-[24px] font-bold leading-[130%] dark:text-white"
|
||||
style={{ color: "#111827", letterSpacing: "-0.005em" }}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
<span
|
||||
className="text-[10px] font-bold uppercase leading-[150%] text-center dark:text-gray-400"
|
||||
style={{ color: "#9ca1af", letterSpacing: "0.05em" }}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Season1Rewards() {
|
||||
const { t } = useApp();
|
||||
return (
|
||||
<div
|
||||
className="rounded-3xl border flex flex-col relative overflow-hidden"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(50% 50% at 100% 0%, rgba(255, 217, 100, 0.05) 0%, rgba(16, 185, 129, 0.05) 100%), #ffffff",
|
||||
borderColor: "rgba(255, 255, 255, 0.6)",
|
||||
paddingTop: "20px",
|
||||
paddingBottom: "20px",
|
||||
paddingLeft: "24px",
|
||||
paddingRight: "24px",
|
||||
}}
|
||||
>
|
||||
{/* Background Decoration */}
|
||||
<div
|
||||
className="absolute"
|
||||
style={{ opacity: 0.5, right: "-15px", bottom: "-20px" }}
|
||||
>
|
||||
<Image
|
||||
src="/component-113.svg"
|
||||
alt=""
|
||||
width={120}
|
||||
height={144}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content Container */}
|
||||
<div className="flex flex-row items-center relative z-10" style={{ gap: "400px" }}>
|
||||
{/* Left: Header and Description */}
|
||||
<div className="flex flex-col gap-2 flex-shrink-0">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3">
|
||||
<h3
|
||||
className="text-[20px] font-bold leading-[140%] dark:text-white"
|
||||
style={{ color: "#111827" }}
|
||||
>
|
||||
{t("rewards.season1")}
|
||||
</h3>
|
||||
<div
|
||||
className="rounded-full px-2.5 py-1 flex items-center"
|
||||
style={{ backgroundColor: "#111827" }}
|
||||
>
|
||||
<span
|
||||
className="text-[10px] font-bold leading-4"
|
||||
style={{ color: "#fcfcfd" }}
|
||||
>
|
||||
{t("rewards.live")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p
|
||||
className="text-body-small font-medium dark:text-gray-400"
|
||||
style={{ color: "#9ca1af" }}
|
||||
>
|
||||
{t("rewards.earnPoints")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right: Stats */}
|
||||
<div className="flex items-center justify-between flex-1">
|
||||
<RewardStat label={t("rewards.yourPoints")} value="-" />
|
||||
<RewardStat label={t("rewards.badgeBoost")} value="-" />
|
||||
<RewardStat label={t("rewards.referrals")} value="-" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
43
components/product/TabNavigation.tsx
Normal file
43
components/product/TabNavigation.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import { Tabs, Tab } from "@heroui/react";
|
||||
|
||||
interface TabItem {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface TabNavigationProps {
|
||||
tabs: TabItem[];
|
||||
defaultActiveId?: string;
|
||||
onTabChange?: (tabId: string) => void;
|
||||
}
|
||||
|
||||
export default function TabNavigation({
|
||||
tabs,
|
||||
defaultActiveId,
|
||||
onTabChange,
|
||||
}: TabNavigationProps) {
|
||||
const handleSelectionChange = (key: React.Key) => {
|
||||
onTabChange?.(key.toString());
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
selectedKey={defaultActiveId || tabs[0]?.id}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
variant="underlined"
|
||||
classNames={{
|
||||
base: "w-auto",
|
||||
tabList: "gap-8 w-auto p-0",
|
||||
cursor: "bg-text-primary dark:bg-white",
|
||||
tab: "px-0 h-auto",
|
||||
tabContent: "text-sm font-bold text-text-tertiary dark:text-gray-400 group-data-[selected=true]:text-text-primary dark:group-data-[selected=true]:text-white",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<Tab key={tab.id} title={tab.label} />
|
||||
))}
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user