初始化 assetx 项目,首次提交
This commit is contained in:
101
components/APYHistoryCard.tsx
Normal file
101
components/APYHistoryCard.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
export default function APYHistoryCard() {
|
||||
const { t } = useApp();
|
||||
const [activeTab, setActiveTab] = useState<"apy" | "price">("apy");
|
||||
|
||||
// 橙色渐变条形图数据
|
||||
const chartData = [
|
||||
{ height: 85, color: "#ffe9dc" },
|
||||
{ height: 93, color: "#ffc9ad" },
|
||||
{ height: 100, color: "#ffc9ad" },
|
||||
{ height: 105, color: "#ffba96" },
|
||||
{ height: 108, color: "#ffa67e" },
|
||||
{ height: 116, color: "#f48d5f" },
|
||||
{ height: 124, color: "#ff6900" },
|
||||
{ height: 127, color: "#f35b00" },
|
||||
{ height: 139, color: "#d64700" },
|
||||
];
|
||||
|
||||
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>
|
||||
|
||||
{/* Orange Gradient Chart */}
|
||||
<div
|
||||
className="flex items-end gap-4 w-full border-b border-border-gray dark:border-gray-700"
|
||||
style={{
|
||||
height: "140px",
|
||||
background:
|
||||
"linear-gradient(0deg, rgba(255, 247, 237, 0.5) 0%, rgba(255, 247, 237, 0) 100%)",
|
||||
}}
|
||||
>
|
||||
{chartData.map((bar, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex-1"
|
||||
style={{
|
||||
height: `${bar.height}px`,
|
||||
backgroundColor: bar.color,
|
||||
borderRadius: "2px 2px 0px 0px",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
24.8%
|
||||
</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">
|
||||
18.2%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
237
components/AssetCustodyVerification.tsx
Normal file
237
components/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/AssetCustodyVerificationTab.tsx
Normal file
9
components/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/AssetDescriptionCard.tsx
Normal file
18
components/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-[300px]">
|
||||
<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/AssetDescriptionTab.tsx
Normal file
9
components/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/AssetOverviewCard.tsx
Normal file
116
components/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>
|
||||
);
|
||||
}
|
||||
39
components/Breadcrumb.tsx
Normal file
39
components/Breadcrumb.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import Image from "next/image";
|
||||
|
||||
interface BreadcrumbItem {
|
||||
label: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
interface BreadcrumbProps {
|
||||
items: BreadcrumbItem[];
|
||||
}
|
||||
|
||||
export default function Breadcrumb({ items }: BreadcrumbProps) {
|
||||
return (
|
||||
<nav className="flex items-center gap-[3px] h-5">
|
||||
{items.map((item, index) => (
|
||||
<div key={index} className="flex items-center gap-[3px]">
|
||||
<span
|
||||
className={`text-sm font-medium leading-[150%] ${
|
||||
index === items.length - 1
|
||||
? "text-text-primary font-bold"
|
||||
: "text-text-tertiary"
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
{index < items.length - 1 && (
|
||||
<Image
|
||||
src="/icon-chevron-right.svg"
|
||||
alt="›"
|
||||
width={14}
|
||||
height={14}
|
||||
className="flex-shrink-0"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
63
components/ContentSection.tsx
Normal file
63
components/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>
|
||||
);
|
||||
}
|
||||
22
components/LanguageSwitch.tsx
Normal file
22
components/LanguageSwitch.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
export default function LanguageSwitch() {
|
||||
const { language, setLanguage } = useApp();
|
||||
|
||||
const toggleLanguage = () => {
|
||||
setLanguage(language === "zh" ? "en" : "zh");
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggleLanguage}
|
||||
className="bg-bg-surface dark:bg-gray-800 rounded-lg border border-border-normal dark:border-gray-700 px-3 py-2 flex items-center justify-center h-10 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<span className="text-sm font-medium text-text-primary dark:text-white">
|
||||
{language === "zh" ? "中" : "EN"}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
199
components/MintSwapPanel.tsx
Normal file
199
components/MintSwapPanel.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
export default function MintSwapPanel() {
|
||||
const { t } = useApp();
|
||||
const [activeMode, setActiveMode] = useState<"mint" | "swap">("mint");
|
||||
const [activeAction, setActiveAction] = useState<"deposit" | "withdraw">("deposit");
|
||||
|
||||
return (
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 flex flex-col">
|
||||
{/* Mint/Swap Tabs */}
|
||||
<div className="flex border-b border-border-gray dark:border-gray-700">
|
||||
<button
|
||||
onClick={() => setActiveMode("mint")}
|
||||
className={`flex-1 h-[53px] flex items-center justify-center text-body-small font-bold rounded-tl-3xl transition-colors ${
|
||||
activeMode === "mint"
|
||||
? "bg-bg-subtle dark:bg-gray-700 border-b-2 border-text-primary dark:border-blue-500 text-[#0f172b] dark:text-white"
|
||||
: "text-text-tertiary dark:text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{t("mintSwap.mint")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveMode("swap")}
|
||||
className={`flex-1 h-[53px] flex items-center justify-center text-body-small font-bold transition-colors ${
|
||||
activeMode === "swap"
|
||||
? "bg-bg-subtle dark:bg-gray-700 border-b-2 border-text-primary dark:border-blue-500 text-[#0f172b] dark:text-white"
|
||||
: "text-text-tertiary dark:text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{t("mintSwap.swap")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex flex-col gap-6 p-6">
|
||||
{/* Deposit/Withdraw Toggle */}
|
||||
<div className="bg-[#f9fafb] dark:bg-gray-700 rounded-xl p-1 flex gap-0">
|
||||
<button
|
||||
onClick={() => setActiveAction("deposit")}
|
||||
className={`flex-1 h-8 px-4 rounded-lg text-body-small transition-all ${
|
||||
activeAction === "deposit"
|
||||
? "bg-bg-surface dark:bg-gray-600 font-bold text-text-primary dark:text-white shadow-sm"
|
||||
: "font-medium text-text-tertiary dark:text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{t("mintSwap.deposit")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveAction("withdraw")}
|
||||
className={`flex-1 h-8 px-4 rounded-lg text-body-small transition-all ${
|
||||
activeAction === "withdraw"
|
||||
? "bg-bg-surface dark:bg-gray-600 font-bold text-text-primary dark:text-white shadow-sm"
|
||||
: "font-medium text-text-tertiary dark:text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{t("mintSwap.withdraw")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 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="/icon7.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>
|
||||
<button className="rounded-full px-3 h-[22px] text-[10px] font-medium bg-[#e5e7eb] dark:bg-gray-600 text-[#111827] dark:text-white">
|
||||
{t("mintSwap.max")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Input Row */}
|
||||
<div className="flex items-center justify-between">
|
||||
<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 className="flex flex-col items-end">
|
||||
<span className="text-heading-h3 font-bold text-[#d1d5db] dark:text-gray-500">
|
||||
0.00
|
||||
</span>
|
||||
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400">--</span>
|
||||
</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
|
||||
className="rounded-xl h-12 flex items-center justify-center gap-2 bg-[#9ca1af] dark:bg-gray-600"
|
||||
disabled
|
||||
>
|
||||
<span className="text-lg font-bold text-white leading-7">
|
||||
{t("mintSwap.approveDeposit")}
|
||||
</span>
|
||||
<Image src="/icon8.svg" alt="" width={20} height={20} />
|
||||
</button>
|
||||
|
||||
{/* 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>
|
||||
);
|
||||
}
|
||||
53
components/NavItem.tsx
Normal file
53
components/NavItem.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import Image from "next/image";
|
||||
|
||||
interface NavItemProps {
|
||||
icon: string;
|
||||
label: string;
|
||||
isActive: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export default function NavItem({ icon, label, isActive, onClick }: NavItemProps) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`
|
||||
rounded-xl
|
||||
pl-4
|
||||
flex
|
||||
items-center
|
||||
gap-2
|
||||
h-[42px]
|
||||
w-full
|
||||
overflow-hidden
|
||||
transition-colors
|
||||
${isActive
|
||||
? 'bg-fill-secondary-click'
|
||||
: 'hover:bg-gray-50'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="w-[22px] h-[22px] flex-shrink-0 relative">
|
||||
<Image
|
||||
src={icon}
|
||||
alt={label}
|
||||
width={22}
|
||||
height={22}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className={`
|
||||
text-sm
|
||||
leading-[150%]
|
||||
${isActive
|
||||
? 'text-text-primary font-bold'
|
||||
: 'text-text-tertiary font-medium'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
55
components/OverviewTab.tsx
Normal file
55
components/OverviewTab.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import ProductHeader from "./ProductHeader";
|
||||
import StatsCards from "./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>
|
||||
);
|
||||
}
|
||||
198
components/PerformanceAnalysis.tsx
Normal file
198
components/PerformanceAnalysis.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
"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-blue-600 border-[#111827] dark:border-blue-600",
|
||||
};
|
||||
|
||||
const isCurrent = type === "current";
|
||||
const dayTextStyle = isCurrent
|
||||
? "text-[#fcfcfd]"
|
||||
: "text-[#9ca1af] dark:text-gray-400";
|
||||
const valueTextStyle = isCurrent
|
||||
? "text-[#fcfcfd]"
|
||||
: "text-[#111827] dark:text-white";
|
||||
|
||||
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 items-end justify-end">
|
||||
<span className={`text-body-small font-bold leading-[150%] ${valueTextStyle}`}>
|
||||
{value}
|
||||
</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/PerformanceAnalysisTab.tsx
Normal file
9
components/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/ProductHeader.tsx
Normal file
35
components/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>
|
||||
);
|
||||
}
|
||||
53
components/ProtocolInformation.tsx
Normal file
53
components/ProtocolInformation.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
"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 (
|
||||
<button className="bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 px-4 py-3.5 flex items-center justify-between w-full hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors">
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src={icon} alt="" width={20} height={24} />
|
||||
<span className="text-body-small font-medium text-text-tertiary dark:text-gray-300">
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
<Image src={arrowIcon} alt="" width={20} height={24} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
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">
|
||||
<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/Season1Rewards.tsx
Normal file
101
components/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>
|
||||
);
|
||||
}
|
||||
80
components/Sidebar.tsx
Normal file
80
components/Sidebar.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useState } from "react";
|
||||
import NavItem from "./NavItem";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
export default function Sidebar() {
|
||||
const { t } = useApp();
|
||||
const [activeItem, setActiveItem] = useState("Assets");
|
||||
|
||||
const navigationItems = [
|
||||
{ icon: "/icon-assets.svg", label: t("nav.assets"), key: "Assets", path: "/" },
|
||||
{ icon: "/icon-alp.svg", label: t("nav.alp"), key: "ALP", path: "/alp" },
|
||||
{ icon: "/icon-swap.svg", label: t("nav.swap"), key: "Swap", path: "/swap" },
|
||||
{ icon: "/icon-lending.svg", label: t("nav.lending"), key: "Lending", path: "/lending" },
|
||||
{ icon: "/icon-transparency.svg", label: t("nav.transparency"), key: "Transparency", path: "/transparency" },
|
||||
{ icon: "/icon-ecosystem.svg", label: t("nav.ecosystem"), key: "Ecosystem", path: "/ecosystem" },
|
||||
{ icon: "/icon-points.svg", label: t("nav.points"), key: "Points", path: "/points" },
|
||||
];
|
||||
|
||||
return (
|
||||
<aside className="fixed left-0 top-0 bg-bg-surface dark:bg-gray-800 border-r border-border-normal dark:border-gray-700 flex flex-col items-center px-6 py-8 gap-8 h-screen w-[222px] overflow-y-auto">
|
||||
{/* Logo */}
|
||||
<div className="w-full h-10">
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt="ASSETX Logo"
|
||||
width={174}
|
||||
height={40}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex flex-col gap-2 w-[174px]">
|
||||
{navigationItems.map((item) => (
|
||||
<NavItem
|
||||
key={item.key}
|
||||
icon={item.icon}
|
||||
label={item.label}
|
||||
isActive={activeItem === item.key}
|
||||
onClick={() => setActiveItem(item.key)}
|
||||
/>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Spacer */}
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Global TVL Section */}
|
||||
<div className="w-full border-t border-border-gray dark:border-gray-700 pt-8">
|
||||
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl p-4 flex flex-col gap-1 h-[85px]">
|
||||
<p className="text-text-tertiary dark:text-gray-400 text-[10px] font-medium leading-[150%] tracking-[0.01em]">
|
||||
{t("nav.globalTVL")}
|
||||
</p>
|
||||
<p className="text-text-primary dark:text-white text-base font-extrabold leading-[150%] font-jetbrains">
|
||||
$465,000,000
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* FAQs Link */}
|
||||
<div className="rounded-xl flex items-center h-[42px] mt-8">
|
||||
<div className="flex items-center gap-0">
|
||||
<Image
|
||||
src="/icon-faq.png"
|
||||
alt="FAQ"
|
||||
width={24}
|
||||
height={24}
|
||||
className="object-cover"
|
||||
/>
|
||||
<span className="text-text-primary dark:text-white text-sm font-bold leading-[150%]">
|
||||
{t("nav.faqs")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
61
components/StatsCards.tsx
Normal file
61
components/StatsCards.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
interface StatCardProps {
|
||||
label: string;
|
||||
value: string;
|
||||
change?: string;
|
||||
changeColor?: string;
|
||||
valueColor?: string;
|
||||
}
|
||||
|
||||
function StatCard({
|
||||
label,
|
||||
value,
|
||||
change,
|
||||
changeColor = "text-green-500",
|
||||
valueColor
|
||||
}: StatCardProps) {
|
||||
const getValueColor = () => {
|
||||
if (valueColor) return valueColor;
|
||||
if (label.includes("APY") || label.includes("年化")) return "#ff6900";
|
||||
return "#111827";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-bg-subtle dark:bg-gray-700 rounded-2xl border border-border-gray dark:border-gray-600 p-4 flex flex-col gap-2">
|
||||
<p className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400 uppercase tracking-wider">
|
||||
{label}
|
||||
</p>
|
||||
<p className="text-heading-h3 font-bold dark:text-white" style={{ color: getValueColor() }}>
|
||||
{value}
|
||||
</p>
|
||||
{change && (
|
||||
<p className={`text-caption-tiny font-medium ${changeColor} dark:text-gray-400`}>
|
||||
{change}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function StatsCards() {
|
||||
const { t } = useApp();
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-5 gap-4 w-full">
|
||||
<StatCard label={t("stats.currentAPY")} value="22%" change="+2.5% WoW" changeColor="text-green-500" />
|
||||
<StatCard label={t("stats.totalValueLocked")} value="$240.5M" change="+$2.3M Today" changeColor="text-green-500" />
|
||||
<StatCard label="24h Volume" value="$12.8M" change="↑ 23% vs Avg" changeColor="text-green-500" />
|
||||
<StatCard label={t("stats.yourBalance")} value="0.00" change="$0.00 USD" changeColor="text-text-tertiary" />
|
||||
<StatCard
|
||||
label={t("stats.yourEarnings")}
|
||||
value="$0.00"
|
||||
change="All Time"
|
||||
changeColor="text-text-tertiary"
|
||||
valueColor="#10b981"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
55
components/TabNavigation.tsx
Normal file
55
components/TabNavigation.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
interface Tab {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface TabNavigationProps {
|
||||
tabs: Tab[];
|
||||
defaultActiveId?: string;
|
||||
onTabChange?: (tabId: string) => void;
|
||||
}
|
||||
|
||||
export default function TabNavigation({
|
||||
tabs,
|
||||
defaultActiveId,
|
||||
onTabChange,
|
||||
}: TabNavigationProps) {
|
||||
const [activeTab, setActiveTab] = useState(defaultActiveId || tabs[0]?.id);
|
||||
|
||||
const handleTabClick = (tabId: string) => {
|
||||
setActiveTab(tabId);
|
||||
onTabChange?.(tabId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-8">
|
||||
{tabs.map((tab) => {
|
||||
const isActive = activeTab === tab.id;
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => handleTabClick(tab.id)}
|
||||
className="flex flex-col gap-2 items-start"
|
||||
>
|
||||
<span
|
||||
className={`text-sm font-bold leading-[150%] ${
|
||||
isActive ? "text-text-primary dark:text-white" : "text-text-tertiary dark:text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</span>
|
||||
<div
|
||||
className={`self-stretch border-t-2 -mt-[2px] ${
|
||||
isActive ? "border-text-primary dark:border-white" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
46
components/ThemeSwitch.tsx
Normal file
46
components/ThemeSwitch.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
export default function ThemeSwitch() {
|
||||
const { theme, setTheme } = useApp();
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(theme === "light" ? "dark" : "light");
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="bg-bg-surface dark:bg-gray-800 rounded-lg border border-border-normal dark:border-gray-700 px-3 py-2 flex items-center justify-center h-10 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
{theme === "light" ? (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path
|
||||
d="M10 15C12.7614 15 15 12.7614 15 10C15 7.23858 12.7614 5 10 5C7.23858 5 5 7.23858 5 10C5 12.7614 7.23858 15 10 15Z"
|
||||
stroke="#111827"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10 1V3M10 17V19M19 10H17M3 10H1M16.07 16.07L14.64 14.64M5.36 5.36L3.93 3.93M16.07 3.93L14.64 5.36M5.36 14.64L3.93 16.07"
|
||||
stroke="#111827"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path
|
||||
d="M17 10.79C16.8427 12.4922 16.1039 14.0754 14.9116 15.2662C13.7192 16.4571 12.1503 17.1913 10.4501 17.3464C8.74989 17.5016 7.04992 17.0676 5.63182 16.1159C4.21372 15.1642 3.15973 13.7534 2.63564 12.1102C2.11155 10.467 2.14637 8.68739 2.73477 7.06725C3.32317 5.44711 4.43113 4.07931 5.88616 3.18637C7.3412 2.29343 9.05859 1.93047 10.7542 2.15507C12.4498 2.37967 13.9989 3.17747 15.16 4.41"
|
||||
stroke="#111827"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
50
components/TopBar.tsx
Normal file
50
components/TopBar.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import Image from "next/image";
|
||||
import Breadcrumb from "./Breadcrumb";
|
||||
import LanguageSwitch from "./LanguageSwitch";
|
||||
import ThemeSwitch from "./ThemeSwitch";
|
||||
|
||||
export default function TopBar() {
|
||||
const breadcrumbItems = [
|
||||
{ label: "ASSETX" },
|
||||
{ label: "Product" },
|
||||
{ label: "Detail" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
{/* Left: Breadcrumb */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Breadcrumb items={breadcrumbItems} />
|
||||
</div>
|
||||
|
||||
{/* Right: Actions */}
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Language Switch */}
|
||||
<LanguageSwitch />
|
||||
|
||||
{/* Theme Switch */}
|
||||
<ThemeSwitch />
|
||||
|
||||
{/* Wallet Button */}
|
||||
<button className="bg-bg-surface rounded-lg border border-border-normal px-2 py-2 flex items-center justify-center h-10">
|
||||
<Image src="/icon-wallet.svg" alt="Wallet" width={20} height={20} />
|
||||
<Image
|
||||
src="/icon-notification.svg"
|
||||
alt="Notification"
|
||||
width={14}
|
||||
height={14}
|
||||
className="ml-1"
|
||||
/>
|
||||
</button>
|
||||
|
||||
{/* Address Button */}
|
||||
<button className="bg-text-primary rounded-lg px-4 py-2 flex items-center justify-center gap-2 h-10 hover:bg-gray-800 transition-colors">
|
||||
<Image src="/icon-copy.svg" alt="Copy" width={16} height={16} />
|
||||
<span className="text-white text-sm font-bold font-jetbrains">
|
||||
0x12...4F82
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user