大改变

This commit is contained in:
YoRHa
2026-02-03 19:56:21 +08:00
parent 4b13d255bc
commit 9aa9e44295
545 changed files with 8712 additions and 16168 deletions

View File

@@ -1,45 +1,22 @@
"use client";
import { useApp } from "@/contexts/AppContext";
import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Button } from "@heroui/react";
export default function LanguageSwitch() {
const { language, setLanguage } = useApp();
const languages = [
{ key: "zh", label: "中文" },
{ key: "en", label: "English" },
];
const handleSelectionChange = (key: React.Key) => {
setLanguage(key as "zh" | "en");
const toggleLanguage = () => {
setLanguage(language === "zh" ? "en" : "zh");
};
return (
<Dropdown>
<DropdownTrigger>
<Button
variant="bordered"
className="bg-bg-surface dark:bg-gray-800 border-border-normal dark:border-gray-700 min-w-10 h-10 px-3 rounded-lg"
>
<span className="text-sm font-medium text-text-primary dark:text-white">
{language === "zh" ? "中" : "EN"}
</span>
</Button>
</DropdownTrigger>
<DropdownMenu
aria-label="Language selection"
selectedKeys={new Set([language])}
selectionMode="single"
onSelectionChange={(keys) => {
const key = Array.from(keys)[0];
if (key) handleSelectionChange(key);
}}
>
{languages.map((lang) => (
<DropdownItem key={lang.key}>{lang.label}</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
<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>
);
}

View File

@@ -3,51 +3,63 @@
import { useState } from "react";
import Image from "next/image";
import { useApp } from "@/contexts/AppContext";
import { Tabs, Tab, Button } from "@heroui/react";
export default function MintSwapPanel() {
const { t } = useApp();
const [activeMode, setActiveMode] = useState<"mint" | "swap">("mint");
const [activeAction, setActiveAction] = useState<"deposit" | "withdraw">("deposit");
const [amount, setAmount] = useState<string>("");
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">
<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 */}
<Tabs
selectedKey={activeMode}
onSelectionChange={(key) => setActiveMode(key as "mint" | "swap")}
variant="underlined"
classNames={{
base: "w-full",
tabList: "w-full gap-0 p-0 rounded-none border-b border-border-gray dark:border-gray-700",
cursor: "bg-text-primary dark:bg-blue-500 h-[2px]",
tab: "h-[53px] flex-1 rounded-none data-[selected=true]:bg-bg-subtle dark:data-[selected=true]:bg-gray-700",
tabContent: "text-body-small font-bold text-text-tertiary dark:text-gray-400 group-data-[selected=true]:text-[#0f172b] dark:group-data-[selected=true]:text-white",
}}
>
<Tab key="mint" title={t("mintSwap.mint")} />
<Tab key="swap" title={t("mintSwap.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 */}
<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>
<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">
@@ -62,13 +74,9 @@ export default function MintSwapPanel() {
<span className="text-caption-tiny font-medium text-[#4b5563] dark:text-gray-400">
{t("mintSwap.balance")}: $12,500.00
</span>
<Button
size="sm"
className="rounded-full px-3 h-[22px] min-w-0 text-[10px] font-medium bg-[#e5e7eb] dark:bg-gray-600 text-[#111827] dark:text-white"
onPress={() => setAmount("12500")}
>
<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>
</button>
</div>
</div>
@@ -83,17 +91,11 @@ export default function MintSwapPanel() {
/>
<span className="text-body-default font-bold text-text-primary dark:text-white">USDC</span>
</button>
<div className="flex flex-col items-end flex-1">
<input
type="number"
placeholder="0.00"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="w-full text-right 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}` : "--"}
<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>
@@ -164,13 +166,15 @@ export default function MintSwapPanel() {
</div>
{/* Submit Button */}
<Button
isDisabled
className="rounded-xl h-12 bg-[#9ca1af] dark:bg-gray-600 text-lg font-bold text-white"
endContent={<Image src="/icon8.svg" alt="" width={20} height={20} />}
<button
className="rounded-xl h-12 flex items-center justify-center gap-2 bg-[#9ca1af] dark:bg-gray-600"
disabled
>
{t("mintSwap.approveDeposit")}
</Button>
<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">

View File

@@ -1,17 +1,16 @@
import Image from "next/image";
import Link from "next/link";
interface NavItemProps {
icon: string;
label: string;
isActive: boolean;
href: string;
onClick: () => void;
}
export default function NavItem({ icon, label, isActive, href }: NavItemProps) {
export default function NavItem({ icon, label, isActive, onClick }: NavItemProps) {
return (
<Link
href={href}
<button
onClick={onClick}
className={`
rounded-xl
pl-4
@@ -23,8 +22,8 @@ export default function NavItem({ icon, label, isActive, href }: NavItemProps) {
overflow-hidden
transition-colors
${isActive
? 'bg-fill-secondary-click dark:bg-gray-700'
: 'hover:bg-gray-50 dark:hover:bg-gray-700'
? 'bg-fill-secondary-click'
: 'hover:bg-gray-50'
}
`}
>
@@ -42,13 +41,13 @@ export default function NavItem({ icon, label, isActive, href }: NavItemProps) {
text-sm
leading-[150%]
${isActive
? 'text-text-primary dark:text-white font-bold'
: 'text-text-tertiary dark:text-gray-400 font-medium'
? 'text-text-primary font-bold'
: 'text-text-tertiary font-medium'
}
`}
>
{label}
</span>
</Link>
</button>
);
}

View File

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

View File

@@ -1,17 +1,16 @@
"use client";
import Image from "next/image";
import { usePathname } from "next/navigation";
import { useState } from "react";
import NavItem from "./NavItem";
import { useApp } from "@/contexts/AppContext";
export default function Sidebar() {
const { t } = useApp();
const pathname = usePathname();
const [activeItem, setActiveItem] = useState("Assets");
const navigationItems = [
{ icon: "/icon-assets.svg", label: t("nav.assets"), key: "Assets", path: "/" },
{ icon: "/icon-lending.svg", label: t("nav.products"), key: "Products", path: "/product" },
{ 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" },
@@ -40,8 +39,8 @@ export default function Sidebar() {
key={item.key}
icon={item.icon}
label={item.label}
isActive={pathname === item.path}
href={item.path}
isActive={activeItem === item.key}
onClick={() => setActiveItem(item.key)}
/>
))}
</nav>

View File

@@ -1,14 +1,14 @@
"use client";
import { Tabs, Tab } from "@heroui/react";
import { useState } from "react";
interface TabItem {
interface Tab {
id: string;
label: string;
}
interface TabNavigationProps {
tabs: TabItem[];
tabs: Tab[];
defaultActiveId?: string;
onTabChange?: (tabId: string) => void;
}
@@ -18,26 +18,38 @@ export default function TabNavigation({
defaultActiveId,
onTabChange,
}: TabNavigationProps) {
const handleSelectionChange = (key: React.Key) => {
onTabChange?.(key.toString());
const [activeTab, setActiveTab] = useState(defaultActiveId || tabs[0]?.id);
const handleTabClick = (tabId: string) => {
setActiveTab(tabId);
onTabChange?.(tabId);
};
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>
<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>
);
}

View File

@@ -1,7 +1,6 @@
"use client";
import { useApp } from "@/contexts/AppContext";
import { Button } from "@heroui/react";
export default function ThemeSwitch() {
const { theme, setTheme } = useApp();
@@ -11,43 +10,37 @@ export default function ThemeSwitch() {
};
return (
<Button
isIconOnly
variant="bordered"
onPress={toggleTheme}
className="bg-bg-surface dark:bg-gray-800 border-border-normal dark:border-gray-700 min-w-10 h-10 rounded-lg"
aria-label={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
<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="currentColor"
stroke="#111827"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className="text-text-primary dark:text-white"
/>
<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="currentColor"
stroke="#111827"
strokeWidth="1.5"
strokeLinecap="round"
className="text-text-primary dark:text-white"
/>
</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="currentColor"
stroke="#111827"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className="text-text-primary dark:text-white"
/>
</svg>
)}
</Button>
</button>
);
}

View File

@@ -1,7 +1,4 @@
"use client";
import Image from "next/image";
import { Button } from "@heroui/react";
import Breadcrumb from "./Breadcrumb";
import LanguageSwitch from "./LanguageSwitch";
import ThemeSwitch from "./ThemeSwitch";
@@ -29,30 +26,24 @@ export default function TopBar() {
<ThemeSwitch />
{/* Wallet Button */}
<Button
isIconOnly
variant="bordered"
className="bg-bg-surface border-border-normal min-w-10 h-10 rounded-lg"
>
<div className="flex items-center justify-center">
<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"
/>
</div>
</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 text-white font-bold font-jetbrains text-sm h-10 px-4 rounded-lg"
startContent={<Image src="/icon-copy.svg" alt="Copy" width={16} height={16} />}
>
0x12...4F82
</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>
);

View File

@@ -0,0 +1,48 @@
"use client";
import { useApp } from "@/contexts/AppContext";
export default function ALPStatsCards() {
const { t } = useApp();
const stats = [
{
label: t("stats.totalValueLocked"),
value: "$465.0M",
isGreen: false,
},
{
label: t("alp.price"),
value: "$1.23",
isGreen: false,
},
{
label: t("alp.poolAPR"),
value: "27.36%",
isGreen: true,
},
{
label: t("alp.rewardAPR"),
value: "8.23%",
isGreen: true,
},
];
return (
<div className="grid grid-cols-[1fr_1fr_1fr_1fr] gap-4">
{stats.map((stat, index) => (
<div
key={index}
className="bg-bg-subtle dark:bg-gray-800 rounded-2xl border border-border-gray dark:border-gray-700 px-6 py-4 flex flex-col gap-2"
>
<div className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400">
{stat.label}
</div>
<div className={`text-[32px] font-bold leading-[130%] tracking-[-0.01em] ${stat.isGreen ? 'text-[#10b981]' : 'text-text-primary dark:text-white'}`}>
{stat.value}
</div>
</div>
))}
</div>
);
}

View File

@@ -0,0 +1,135 @@
"use client";
import { useApp } from "@/contexts/AppContext";
export default function LiquidityAllocationTable() {
const { t } = useApp();
const allocations = [
{
icon: "GY",
iconBg: "linear-gradient(135deg, #FF8904 0%, #F54900 100%)",
name: "YT-GY",
category: t("alp.quantStrategy"),
poolSize: "$25,000",
poolAmount: "24,1938 YTGY",
currentWeight: "47%",
targetWeight: "33%",
currentPrice: "$1.03",
},
{
icon: "LOGO",
iconBg: "linear-gradient(135deg, #00BBA7 0%, #007A55 100%)",
name: "YT-GY",
category: t("alp.quantStrategy"),
poolSize: "$25,000",
poolAmount: "24,1938 YTGY",
currentWeight: "47%",
targetWeight: "33%",
currentPrice: "$1.03",
},
{
icon: "LOGO",
iconBg: "linear-gradient(135deg, #1447E6 0%, #032BBD 100%)",
name: "YT-GY",
category: t("alp.quantStrategy"),
poolSize: "$25,000",
poolAmount: "24,1938 YTGY",
currentWeight: "47%",
targetWeight: "33%",
currentPrice: "$1.03",
},
];
return (
<div className="flex flex-col gap-3">
{/* Title */}
<h2 className="text-heading-h3 font-bold text-text-primary dark:text-white">
{t("alp.liquidityAllocation")}
</h2>
{/* Table */}
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 overflow-hidden">
<div className="flex flex-col">
{/* Header */}
<div className="flex border-b border-border-gray dark:border-gray-700 flex-shrink-0">
<div className="flex-1 px-6 py-4">
<div className="text-caption-tiny font-medium text-[#4b5563] dark:text-gray-400">
{t("alp.token")}
</div>
</div>
<div className="flex-1 px-6 py-4">
<div className="text-caption-tiny font-medium text-[#4b5563] dark:text-gray-400">
{t("alp.poolSize")}
</div>
</div>
<div className="flex-1 px-6 py-4">
<div className="text-caption-tiny font-medium text-[#4b5563] dark:text-gray-400">
{t("alp.currentTargetWeight")}
</div>
</div>
<div className="flex-1 px-6 py-4">
<div className="text-caption-tiny font-medium text-[#4b5563] dark:text-gray-400">
{t("alp.currentPrice")}
</div>
</div>
</div>
{/* Body */}
{allocations.map((item, index) => (
<div
key={index}
className="flex items-center border-b border-border-gray dark:border-gray-700"
>
{/* Token Column */}
<div className="flex-1 px-6 py-4 flex items-center gap-3">
<div
className="w-8 h-8 rounded-full flex items-center justify-center text-white text-[13.57px] font-bold"
style={{ background: item.iconBg }}
>
{item.icon}
</div>
<div className="flex flex-col">
<span className="text-body-small font-bold text-text-primary dark:text-white">
{item.name}
</span>
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400">
{item.category}
</span>
</div>
</div>
{/* Pool Size Column */}
<div className="flex-1 px-6 py-4">
<div className="flex flex-col">
<span className="text-body-small font-bold text-text-primary dark:text-white">
{item.poolSize}
</span>
<span className="text-caption-tiny font-regular text-[#6b7280] dark:text-gray-400">
{item.poolAmount}
</span>
</div>
</div>
{/* Weight Column */}
<div className="flex-1 px-6 py-4">
<span className="text-body-small font-bold">
<span className="text-text-primary dark:text-white">{item.currentWeight}</span>
<span className="text-text-tertiary dark:text-gray-400"> / </span>
<span className="text-text-primary dark:text-white">{item.targetWeight}</span>
</span>
</div>
{/* Price Column */}
<div className="flex-1 px-6 py-4">
<span className="text-body-small font-bold text-text-primary dark:text-white">
{item.currentPrice}
</span>
</div>
</div>
))}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,185 @@
"use client";
import { useState, useEffect, useRef } from "react";
import { useApp } from "@/contexts/AppContext";
import * as echarts from "echarts";
export default function PriceHistoryCard() {
const { t } = useApp();
const chartRef = useRef<HTMLDivElement>(null);
const chartInstance = useRef<echarts.ECharts | null>(null);
// 模拟价格数据
const priceData = [1.01, 1.02, 1.03, 1.02, 1.04, 1.03, 1.05, 1.04, 1.03];
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();
};
}, []);
const updateChart = () => {
if (!chartInstance.current) return;
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];
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} USDC</span>
</div>`;
},
},
xAxis: {
type: "category",
data: priceData.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: [
{
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);
};
// 计算统计数据
const highest = Math.max(...priceData).toFixed(2);
const lowest = Math.min(...priceData).toFixed(2);
const current = priceData[priceData.length - 1].toFixed(2);
const avg = (priceData.reduce((a, b) => a + b, 0) / priceData.length).toFixed(2);
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-6">
{/* Header */}
<div className="text-body-large font-bold text-text-primary dark:text-white">
{t("alp.priceHistory")}
</div>
{/* Content */}
<div className="flex flex-col gap-6">
{/* Info Row */}
<div className="flex items-center justify-between">
<div className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400">
{t("alp.lastDays")}
</div>
<div className="text-caption-tiny font-bold text-[#10b981] dark:text-green-400">
{t("alp.avg")}: {avg} USDC
</div>
</div>
{/* ECharts Chart */}
<div
ref={chartRef}
className="w-full border-b border-border-gray dark:border-gray-600"
style={{ height: "200px" }}
/>
{/* Stats - Vertical Layout */}
<div className="flex flex-col gap-3">
<div className="flex items-start justify-between">
<div className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400">
{t("alp.highest")}
</div>
<div className="text-caption-tiny font-bold text-text-primary dark:text-white tabular-nums">
{highest} USDC
</div>
</div>
<div className="flex items-start justify-between">
<div className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400">
{t("alp.lowest")}
</div>
<div className="text-caption-tiny font-bold text-text-primary dark:text-white tabular-nums">
{lowest} USDC
</div>
</div>
<div className="flex items-start justify-between">
<div className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400">
{t("alp.current")}
</div>
<div className="text-caption-tiny font-bold text-text-primary dark:text-white tabular-nums">
{current} USDC
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,63 @@
"use client";
import { Button, ButtonProps } from "@heroui/react";
import { tv } from "tailwind-variants";
const borderedButtonStyles = tv({
slots: {
base: "rounded-xl border border-[#E5E7EB] dark:border-gray-600 flex items-center justify-center",
button: "w-full h-full rounded-xl px-6 text-body-small font-bold border-none",
},
variants: {
size: {
md: {
base: "h-10",
},
lg: {
base: "h-11",
},
},
fullWidth: {
true: {
base: "w-full",
},
},
isTheme: {
true: {
button: "bg-white dark:bg-gray-800 text-text-primary dark:text-white",
},
},
},
defaultVariants: {
size: "md",
isTheme: false,
},
});
interface BorderedButtonProps extends ButtonProps {
size?: "md" | "lg";
fullWidth?: boolean;
isTheme?: boolean;
}
export default function BorderedButton({
size = "md",
fullWidth = false,
isTheme = false,
className,
...props
}: BorderedButtonProps) {
const { base, button } = borderedButtonStyles({ size, fullWidth, isTheme });
return (
<div className={base({ className })}>
<Button
className={button()}
{...props}
>
{props.children}
</Button>
</div>
);
}

View File

@@ -0,0 +1,155 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import { Modal, ModalContent, Button } from "@heroui/react";
import { buttonStyles } from "@/lib/buttonStyles";
interface ConfirmModalProps {
isOpen: boolean;
onClose: () => void;
}
export default function ConfirmModal({ isOpen, onClose }: ConfirmModalProps) {
const [activeTab, setActiveTab] = useState<"buy" | "sell">("buy");
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size="lg"
classNames={{
base: "bg-transparent shadow-none",
wrapper: "items-center justify-center",
}}
>
<ModalContent className="bg-bg-surface dark:bg-gray-800 rounded-3xl p-6 max-w-[494px]">
<div className="flex flex-col gap-6">
{/* Header - Buy/Sell Tabs */}
<div className="flex items-center gap-8">
<button
onClick={() => setActiveTab("buy")}
className={`text-heading-h4 leading-[140%] ${
activeTab === "buy"
? "font-bold text-text-primary dark:text-white"
: "font-medium text-text-tertiary dark:text-gray-400"
}`}
>
Buy
</button>
<button
onClick={() => setActiveTab("sell")}
className={`text-heading-h4 leading-[140%] ${
activeTab === "sell"
? "font-bold text-text-primary dark:text-white"
: "font-medium text-text-tertiary dark:text-gray-400"
}`}
>
Sell
</button>
</div>
{/* PAY Section */}
<div className="flex flex-col gap-2">
{/* PAY Label and MAX Button */}
<div className="flex items-center justify-between">
<span className="text-body-small font-bold text-text-secondary dark:text-gray-400">
PAY
</span>
<Button size="sm" className="rounded-lg h-7 px-3 text-caption-tiny font-medium bg-content2 text-foreground hover:bg-content3">
MAX
</Button>
</div>
{/* Payment Input Box */}
<div className="bg-bg-subtle dark:bg-gray-700 rounded-2xl p-6">
<div className="flex items-center justify-between">
{/* Left - Amount */}
<div className="flex flex-col gap-3">
<div className="text-[32px] font-bold leading-[130%] tracking-[-0.01em] text-text-primary dark:text-white">
1,000
</div>
<div className="text-body-small font-medium text-text-tertiary dark:text-gray-400">
$10,000.00 USD
</div>
</div>
{/* Right - Token Selector and Balance */}
<div className="flex flex-col gap-3 items-end">
<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>
<Image src="/icon0.svg" alt="" width={20} height={20} />
</button>
<div className="flex items-center gap-1">
<Image src="/icon1.svg" alt="" width={12} height={12} />
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-400">
45,230.00 USDC
</span>
</div>
</div>
</div>
</div>
</div>
{/* Transaction Summary */}
<div className="bg-bg-subtle dark:bg-gray-700 rounded-2xl p-4 flex flex-col gap-2">
<div className="text-body-small font-medium text-text-secondary dark:text-gray-400">
Transaction Summary
</div>
{/* You Get */}
<div className="flex items-center justify-between">
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400">
You Get
</span>
<span className="text-body-small font-bold text-[#ff6900] dark:text-orange-400">
9652.2 GYUS
</span>
</div>
{/* Market Price */}
<div className="flex items-center justify-between">
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400">
Market Price
</span>
<span className="text-body-small font-bold text-text-primary dark:text-white">
$1.03 USDC
</span>
</div>
{/* Price vs Mint */}
<div className="flex items-center justify-between">
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400">
Price vs Mint
</span>
<div className="flex items-center gap-1">
<Image src="/icon2.svg" alt="" width={20} height={20} />
<span className="text-body-small font-bold text-[#10b981] dark:text-green-400">
+2.53%
</span>
</div>
</div>
</div>
{/* Confirm Button */}
<Button
color="default"
variant="solid"
className={buttonStyles({ intent: "theme" })}
onPress={onClose}
>
Buy from SWAP
</Button>
</div>
</ModalContent>
</Modal>
);
}

View File

@@ -0,0 +1,429 @@
"use client";
import Image from "next/image";
import { Modal, ModalContent, Button } from "@heroui/react";
import { buttonStyles } from "@/lib/buttonStyles";
interface ReviewModalProps {
isOpen: boolean;
onClose: () => void;
amount: string;
productName?: string;
}
export default function ReviewModal({
isOpen,
onClose,
amount,
productName = "High-Yield US Equity",
}: ReviewModalProps) {
const gyusAmount = amount ? (parseFloat(amount) * 0.9852).toFixed(0) : "0";
const usdValue = amount ? (parseFloat(amount) * 1.00045).toFixed(2) : "0.00";
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size="lg"
classNames={{
base: "bg-transparent shadow-none",
backdrop: "bg-black/50",
}}
>
<ModalContent>
<div
style={{
background: "#ffffff",
borderRadius: "24px",
padding: "24px",
display: "flex",
flexDirection: "column",
gap: "24px",
position: "relative",
overflow: "hidden",
}}
>
{/* Header */}
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
width: "446px",
}}
>
<div
style={{
color: "#111827",
fontSize: "20px",
fontWeight: 700,
lineHeight: "140%",
fontFamily: "var(--font-inter)",
}}
>
Review
</div>
<button
onClick={onClose}
style={{
width: "24px",
height: "24px",
cursor: "pointer",
background: "none",
border: "none",
padding: 0,
}}
>
<Image
src="/vuesax-linear-close-circle1.svg"
alt="Close"
width={24}
height={24}
/>
</button>
</div>
{/* Product Info */}
<div
style={{
background: "#f9fafb",
borderRadius: "12px",
padding: "16px",
display: "flex",
flexDirection: "row",
gap: "12px",
alignItems: "center",
}}
>
<div
style={{
background: "#fff7ed",
borderRadius: "6px",
border: "0.75px solid rgba(0, 0, 0, 0.1)",
width: "40px",
height: "30px",
boxShadow: "inset 0px 1.5px 3px 0px rgba(0, 0, 0, 0.05)",
overflow: "hidden",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Image
src="/lr0.svg"
alt="Product"
width={43}
height={30}
style={{ width: "107.69%", height: "100%", objectFit: "cover" }}
/>
</div>
<div
style={{
color: "#111827",
fontSize: "18px",
fontWeight: 700,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
{productName}
</div>
</div>
{/* Amount Section */}
<div
style={{
display: "flex",
flexDirection: "column",
gap: "16px",
width: "446px",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "8px",
alignItems: "center",
}}
>
<div
style={{
color: "#4b5563",
fontSize: "14px",
fontWeight: 700,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
Deposit
</div>
<div style={{ textAlign: "center" }}>
<span
style={{
color: "#111827",
fontSize: "48px",
fontWeight: 700,
lineHeight: "120%",
letterSpacing: "-0.01em",
fontFamily: "var(--font-inter)",
}}
>
{amount || "0"}
</span>
<span
style={{
color: "#9ca1af",
fontSize: "20px",
fontWeight: 700,
lineHeight: "140%",
fontFamily: "var(--font-inter)",
marginLeft: "8px",
}}
>
USDC
</span>
</div>
<div
style={{
color: "#4b5563",
fontSize: "18px",
fontWeight: 500,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
${usdValue}
</div>
</div>
</div>
{/* Transaction Details */}
<div
style={{
display: "flex",
flexDirection: "column",
gap: "16px",
width: "446px",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "0px",
}}
>
{/* Deposit Row */}
<div
style={{
borderRadius: "16px",
padding: "16px",
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
gap: "12px",
alignItems: "center",
}}
>
<Image src="/icon0.svg" alt="" width={20} height={20} />
<div
style={{
color: "#4b5563",
fontSize: "14px",
fontWeight: 400,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
Deposit (USDC)
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
gap: "8px",
alignItems: "center",
}}
>
<div
style={{
color: "#111827",
fontSize: "14px",
fontWeight: 500,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
{amount ? parseFloat(amount).toLocaleString() : "0"}
</div>
<Image src="/icon3.svg" alt="" width={14} height={14} />
<div
style={{
color: "#9ca1af",
fontSize: "14px",
fontWeight: 500,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
0
</div>
</div>
</div>
{/* Get GYUS Row */}
<div
style={{
borderRadius: "16px",
padding: "16px",
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
height: "54px",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
gap: "12px",
alignItems: "center",
}}
>
<Image src="/icon2.svg" alt="" width={20} height={20} />
<div
style={{
color: "#4b5563",
fontSize: "14px",
fontWeight: 400,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
GET(GYUS)
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
gap: "8px",
alignItems: "center",
}}
>
<div
style={{
color: "#9ca1af",
fontSize: "14px",
fontWeight: 500,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
0.00
</div>
<Image src="/icon3.svg" alt="" width={14} height={14} />
<div
style={{
color: "#111827",
fontSize: "14px",
fontWeight: 500,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
{gyusAmount}
</div>
</div>
</div>
</div>
{/* APY Info */}
<div
style={{
background: "#f2fcf7",
borderRadius: "16px",
border: "1px solid #cef3e0",
padding: "16px",
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
overflow: "hidden",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
gap: "8px",
alignItems: "center",
}}
>
<Image src="/icon4.svg" alt="" width={20} height={20} />
<div
style={{
color: "#10b981",
fontSize: "14px",
fontWeight: 600,
lineHeight: "20px",
letterSpacing: "-0.15px",
fontFamily: "var(--font-inter)",
}}
>
APY
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
gap: "4px",
alignItems: "center",
}}
>
<div
style={{
color: "#10b981",
fontSize: "18px",
fontWeight: 700,
lineHeight: "28px",
letterSpacing: "-0.44px",
fontFamily: "var(--font-inter)",
}}
>
22.0%
</div>
<Image src="/icon5.svg" alt="" width={16} height={16} />
</div>
</div>
</div>
{/* Confirm Button */}
<Button
color="default"
variant="solid"
className={buttonStyles({ intent: "theme" })}
onPress={() => {
console.log("Transaction confirmed");
onClose();
}}
>
Confirm Transaction
</Button>
</div>
</ModalContent>
</Modal>
);
}

View File

@@ -0,0 +1,192 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import { Button } from "@heroui/react";
import { useApp } from "@/contexts/AppContext";
import ConfirmModal from "@/components/common/ConfirmModal";
import { buttonVariants, disabledButtonClasses } from "@/lib/buttonStyles";
import { cn } from "@/lib/cn";
interface TradePanelProps {
showHeader?: boolean;
title?: string;
subtitle?: string;
}
export default function TradePanel({
showHeader = false,
title = "",
subtitle = "",
}: TradePanelProps) {
const { t } = useApp();
const [sellAmount, setSellAmount] = useState<string>("");
const [buyAmount, setBuyAmount] = useState<string>("");
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false);
return (
<div className={`flex flex-col gap-6 ${showHeader ? "w-full max-w-[600px]" : "w-full"}`}>
{/* Header Section - Optional */}
{showHeader && (
<div className="flex flex-col gap-2">
<h1 className="text-heading-h2 font-bold text-text-primary dark:text-white text-center">
{title}
</h1>
<p className="text-body-default font-medium text-text-secondary dark:text-gray-400 text-center">
{subtitle}
</p>
</div>
)}
{/* Trade Panel */}
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-6">
<div className="flex flex-col gap-4">
{/* SELL and BUY Container with Exchange Icon */}
<div className="flex flex-col gap-2 relative">
{/* SELL Section */}
<div className="bg-bg-subtle dark:bg-gray-700 rounded-2xl p-4 flex flex-col gap-4">
{/* Label and Buttons */}
<div className="flex items-center justify-between">
<span className="text-body-small font-bold text-text-primary dark:text-white">
{t("alp.sell")}
</span>
<div className="flex items-center gap-2">
<Button size="sm" color="default" className="rounded-full px-3 h-[24px] min-w-0 text-[12px] font-bold">
25%
</Button>
<Button size="sm" color="default" className="rounded-full px-3 h-[24px] min-w-0 text-[12px] font-bold">
50%
</Button>
<Button size="sm" color="default" className="rounded-full px-3 h-[24px] min-w-0 text-[12px] font-bold">
75%
</Button>
<Button size="sm" color="default" className={cn(buttonVariants.max)}>
{t("mintSwap.max")}
</Button>
</div>
</div>
{/* Input Row */}
<div className="flex items-center justify-between gap-2 h-[70px]">
<div className="flex flex-col items-start justify-between flex-1 h-full">
<input
type="number"
placeholder="1,000"
value={sellAmount}
onChange={(e) => setSellAmount(e.target.value)}
className="w-full text-left text-[32px] font-bold leading-[130%] tracking-[-0.01em] 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-body-small font-medium text-text-tertiary dark:text-gray-400">
$10,000.00 USD
</span>
</div>
<div className="flex flex-col items-end justify-between gap-1 h-full">
<button className="bg-bg-surface dark:bg-gray-600 rounded-full border border-border-normal dark:border-gray-500 px-3 h-[40px] flex items-center gap-2">
<Image
src="/usd-coin-usdc-logo-10.svg"
alt="USDC"
width={24}
height={24}
/>
<span className="text-body-small font-bold text-text-primary dark:text-white">USDC</span>
<Image src="/icon8.svg" alt="" width={12} height={12} />
</button>
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400">
45,230.00 USDC
</span>
</div>
</div>
</div>
{/* Exchange Icon */}
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-10">
<div className="bg-bg-surface dark:bg-gray-700 rounded-full w-10 h-10 flex items-center justify-center border border-border-gray dark:border-gray-600 shadow-sm">
<Image
src="/icon4.svg"
alt="Exchange"
width={18}
height={18}
/>
</div>
</div>
{/* BUY Section */}
<div className="bg-bg-subtle dark:bg-gray-700 rounded-2xl p-4 flex flex-col gap-4">
{/* Label */}
<div className="flex items-center justify-between">
<span className="text-body-small font-bold text-text-primary dark:text-white">
{t("alp.buy")}
</span>
</div>
{/* Input Row */}
<div className="flex items-center justify-between gap-2 h-[70px]">
<div className="flex flex-col items-start justify-between flex-1 h-full">
<input
type="number"
placeholder="1,000"
value={buyAmount}
onChange={(e) => setBuyAmount(e.target.value)}
className="w-full text-left text-[32px] font-bold leading-[130%] tracking-[-0.01em] 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-body-small font-medium text-text-tertiary dark:text-gray-400">
$10,000.00 USD
</span>
</div>
<div className="flex flex-col items-end justify-between gap-1 h-full">
<button className="bg-bg-surface dark:bg-gray-600 rounded-full border border-border-normal dark:border-gray-500 px-3 h-[40px] flex items-center gap-2">
<Image
src="/component-70.svg"
alt="YTGY"
width={24}
height={24}
/>
<span className="text-body-small font-bold text-text-primary dark:text-white">YTGY</span>
<Image src="/icon8.svg" alt="" width={12} height={12} />
</button>
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400">
45,230.00 USDC
</span>
</div>
</div>
</div>
</div>
{/* Submit Button */}
<Button
isDisabled={!sellAmount && !buyAmount}
color="default"
className="rounded-xl h-12 px-6 text-body-small font-bold"
onPress={() => setIsConfirmModalOpen(true)}
>
{t("alp.buyUsdc")}
</Button>
{/* Rate Info */}
<div className="flex items-center justify-between px-4 py-3 bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600">
<div className="flex items-center gap-2">
<span className="text-body-small font-regular text-black dark:text-white">
1 USDC = 0.99 YTGY
</span>
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400">
($1.00)
</span>
</div>
<div className="flex items-center gap-2">
<Image src="/icon7.svg" alt="" width={16} height={16} />
<span className="text-body-small font-bold text-[#dc2626] dark:text-red-400">
-$5.30
</span>
<Image src="/icon8.svg" alt="" width={12} height={12} />
</div>
</div>
</div>
</div>
{/* Confirm Modal */}
<ConfirmModal
isOpen={isConfirmModalOpen}
onClose={() => setIsConfirmModalOpen(false)}
/>
</div>
);
}

View File

@@ -0,0 +1,576 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import { Modal, ModalContent, Button } from "@heroui/react";
import { buttonStyles } from "@/lib/buttonStyles";
interface WithdrawModalProps {
isOpen: boolean;
onClose: () => void;
amount: string;
}
export default function WithdrawModal({
isOpen,
onClose,
amount,
}: WithdrawModalProps) {
const [showDetails, setShowDetails] = useState(true);
const alpAmount = amount ? (parseFloat(amount) * 0.098).toFixed(0) : "0";
const usdValue = amount ? (parseFloat(amount) * 1.00045).toFixed(2) : "0.00";
const fee = amount ? (parseFloat(amount) * 0.005).toFixed(2) : "0.00";
const minReceive = amount ? (parseFloat(amount) * 0.098 * 0.995).toFixed(0) : "0";
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size="lg"
classNames={{
base: "bg-transparent shadow-none",
backdrop: "bg-black/50",
}}
>
<ModalContent>
<div
style={{
background: "#ffffff",
borderRadius: "24px",
padding: "24px",
display: "flex",
flexDirection: "column",
gap: "24px",
position: "relative",
overflow: "hidden",
alignItems: "center",
}}
>
{/* Header */}
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
width: "446px",
}}
>
<div
style={{
color: "#111827",
fontSize: "20px",
fontWeight: 700,
lineHeight: "140%",
fontFamily: "var(--font-inter)",
}}
>
Review
</div>
<button
onClick={onClose}
style={{
width: "24px",
height: "24px",
cursor: "pointer",
background: "none",
border: "none",
padding: 0,
}}
>
<Image
src="/vuesax-linear-close-circle1.svg"
alt="Close"
width={24}
height={24}
/>
</button>
</div>
{/* Exchange Section */}
<div
style={{
display: "flex",
flexDirection: "column",
gap: "12px",
alignItems: "center",
}}
>
<div
style={{
background: "#f9fafb",
borderRadius: "16px",
padding: "24px 16px",
display: "flex",
flexDirection: "column",
gap: "0px",
alignItems: "center",
width: "446px",
}}
>
{/* From USDC */}
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "4px",
alignItems: "flex-start",
}}
>
<div
style={{
color: "#111827",
fontSize: "24px",
fontWeight: 700,
lineHeight: "130%",
letterSpacing: "-0.005em",
fontFamily: "var(--font-inter)",
}}
>
{amount || "0"} USDC
</div>
<div
style={{
color: "#4b5563",
fontSize: "16px",
fontWeight: 500,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
${usdValue}
</div>
</div>
<Image
src="/usd-coin-usdc-logo-10.svg"
alt="USDC"
width={36}
height={36}
/>
</div>
{/* Exchange Icon */}
<div
style={{
background: "#ffffff",
borderRadius: "999px",
width: "40px",
height: "40px",
display: "flex",
alignItems: "center",
justifyContent: "center",
boxShadow:
"0px 1px 2px -1px rgba(0, 0, 0, 0.1), 0px 1px 3px 0px rgba(0, 0, 0, 0.1)",
overflow: "visible",
}}
>
<div style={{ width: "16px", height: "16px", position: "relative" }}>
<Image
src="/group-9280.svg"
alt="Exchange"
width={16}
height={16}
style={{ width: "100%", height: "100%", objectFit: "contain" }}
/>
</div>
</div>
{/* To ALP */}
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "4px",
alignItems: "flex-start",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
gap: "8px",
alignItems: "center",
}}
>
<div
style={{
color: "#111827",
fontSize: "24px",
fontWeight: 700,
lineHeight: "130%",
letterSpacing: "-0.005em",
fontFamily: "var(--font-inter)",
}}
>
{alpAmount} ALP
</div>
<div
style={{
width: "36px",
height: "36px",
position: "relative",
}}
>
<Image
src="/vector0.svg"
alt=""
width={36}
height={36}
style={{ position: "absolute" }}
/>
<Image
src="/vector1.svg"
alt=""
width={36}
height={36}
style={{ position: "absolute" }}
/>
<Image
src="/vector2.svg"
alt=""
width={36}
height={36}
style={{ position: "absolute" }}
/>
<Image
src="/component-10.svg"
alt=""
width={36}
height={36}
style={{ position: "absolute" }}
/>
</div>
</div>
<div
style={{
color: "#4b5563",
fontSize: "16px",
fontWeight: 500,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
${usdValue}
</div>
</div>
</div>
</div>
{/* Show More/Less Button */}
<button
onClick={() => setShowDetails(!showDetails)}
style={{
background: "transparent",
border: "none",
cursor: "pointer",
display: "flex",
flexDirection: "row",
gap: "8px",
alignItems: "center",
padding: "8px",
}}
>
<div
style={{
color: "#111827",
fontSize: "14px",
fontWeight: 500,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
{showDetails ? "Show less" : "Show more"}
</div>
<Image
src="/icon1.svg"
alt=""
width={20}
height={20}
style={{
transform: showDetails ? "rotate(180deg)" : "rotate(0deg)",
transition: "transform 0.2s ease-in-out",
}}
/>
</button>
{/* Transaction Details */}
{showDetails && (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "12px",
width: "446px",
animation: "fadeIn 0.3s ease-in-out",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
padding: "12px 0",
}}
>
<div
style={{
color: "#9ca1af",
fontSize: "14px",
fontWeight: 400,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
Fee(0.5%)
</div>
<div
style={{
color: "#ef4444",
fontSize: "14px",
fontWeight: 700,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
-${fee}
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
padding: "12px 0",
}}
>
<div
style={{
color: "#9ca1af",
fontSize: "14px",
fontWeight: 400,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
Network cost
</div>
<div
style={{
color: "#ef4444",
fontSize: "14px",
fontWeight: 700,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
-$0.09
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
padding: "12px 0",
}}
>
<div
style={{
color: "#9ca1af",
fontSize: "14px",
fontWeight: 400,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
Rate
</div>
<div
style={{
color: "#111827",
fontSize: "14px",
fontWeight: 700,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
1USDC=0.98ALP ($1.02)
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
padding: "12px 0",
}}
>
<div
style={{
color: "#9ca1af",
fontSize: "14px",
fontWeight: 400,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
Spread
</div>
<div
style={{
color: "#111827",
fontSize: "14px",
fontWeight: 700,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
0
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
padding: "12px 0",
}}
>
<div
style={{
color: "#9ca1af",
fontSize: "14px",
fontWeight: 400,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
Price Impact
</div>
<div
style={{
color: "#10b981",
fontSize: "14px",
fontWeight: 700,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
+$0.60 (+0.065%)
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
padding: "12px 0",
}}
>
<div
style={{
color: "#9ca1af",
fontSize: "14px",
fontWeight: 400,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
Max slippage
</div>
<div
style={{
color: "#111827",
fontSize: "14px",
fontWeight: 700,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
0.5%
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
padding: "12px 0",
}}
>
<div
style={{
color: "#9ca1af",
fontSize: "14px",
fontWeight: 400,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
Min Receive
</div>
<div
style={{
color: "#111827",
fontSize: "14px",
fontWeight: 700,
lineHeight: "150%",
fontFamily: "var(--font-inter)",
}}
>
{minReceive} ALP
</div>
</div>
</div>
)}
</div>
{/* Confirm Button */}
<Button
color="default"
variant="solid"
className={buttonStyles({ intent: "theme" })}
onPress={() => {
console.log("Withdraw confirmed");
onClose();
}}
>
Confirm
</Button>
</div>
</ModalContent>
</Modal>
);
}

View File

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

View File

@@ -0,0 +1,142 @@
"use client";
import Image from "next/image";
import { Button, Card, CardBody } from "@heroui/react";
import { buttonStyles } from "@/lib/buttonStyles";
interface ProductCardListProps {
name: string;
category: string;
categoryColor: "orange" | "green" | "blue" | "purple" | "red";
iconType: "us-flag-1" | "hk-flag" | "us-flag-2" | "sg-flag" | "uk-flag";
poolCap: string;
lockUp: string;
poolCapacityPercent: number;
onInvest?: () => void;
}
export default function ProductCardList({
name,
category,
categoryColor,
iconType,
poolCap,
lockUp,
poolCapacityPercent,
onInvest,
}: ProductCardListProps) {
const getIconSrc = () => {
switch (iconType) {
case "us-flag-1":
case "hk-flag":
case "us-flag-2":
default:
return "/lr0.svg";
}
};
const getIconBg = () => {
switch (categoryColor) {
case "orange":
return "bg-orange-50 dark:bg-orange-900/20";
case "green":
return "bg-green-50 dark:bg-green-900/20";
case "blue":
return "bg-blue-50 dark:bg-blue-900/20";
case "purple":
return "bg-purple-50 dark:bg-purple-900/20";
case "red":
return "bg-red-50 dark:bg-red-900/20";
default:
return "bg-orange-50 dark:bg-orange-900/20";
}
};
return (
<Card className="bg-bg-subtle dark:bg-gray-800 border border-border-gray dark:border-gray-700">
<CardBody className="py-6 px-8">
<div className="flex flex-row items-center justify-between">
{/* Icon and Title - Fixed width */}
<div className="flex flex-row gap-4 items-center justify-start w-[280px] flex-shrink-0">
<div
className={`${getIconBg()} rounded-md border-[0.75px] border-black/10 dark:border-white/10 w-10 h-[30px] relative shadow-[inset_0_1.5px_3px_0_rgba(0,0,0,0.05)] overflow-hidden flex items-center justify-center flex-shrink-0`}
>
<Image
src={getIconSrc()}
alt={name}
width={43}
height={30}
className="w-[107.69%] h-full object-cover"
/>
</div>
<div className="flex flex-col gap-0.5 items-start min-w-0 flex-1">
<div className="text-text-tertiary dark:text-gray-400 text-caption-tiny font-normal font-inter">
{category}
</div>
<div className="text-text-primary dark:text-white text-body-default font-bold font-inter truncate w-full">
{name}
</div>
</div>
</div>
{/* Middle section with stats - evenly distributed */}
<div className="flex flex-row items-center justify-between flex-1 px-12">
{/* Lock-up */}
<div className="flex flex-col gap-1 items-start w-[120px] flex-shrink-0">
<div className="text-text-tertiary dark:text-gray-400 text-caption-tiny font-normal font-inter">
Lock-up
</div>
<div className="text-text-primary dark:text-white text-body-small font-bold font-inter">
{lockUp}
</div>
</div>
{/* Pool Cap */}
<div className="flex flex-col gap-1 items-start w-[100px] flex-shrink-0">
<div className="text-text-tertiary dark:text-gray-400 text-caption-tiny font-normal font-inter">
Pool Cap
</div>
<div className="text-green-500 dark:text-green-400 text-body-small font-bold font-inter">
{poolCap}
</div>
</div>
{/* Pool Capacity */}
<div className="flex flex-col gap-1 items-start w-[260px] flex-shrink-0">
<div className="text-text-tertiary dark:text-gray-400 text-caption-tiny font-normal font-inter">
Pool Capacity
</div>
<div className="flex flex-row gap-3 items-center">
<div className="bg-gray-100 dark:bg-gray-700 rounded-full w-24 h-2 relative overflow-hidden flex-shrink-0">
<div
className="rounded-full h-2"
style={{
background:
"linear-gradient(90deg, rgba(20, 71, 230, 1) 0%, rgba(3, 43, 189, 1) 100%)",
width: `${poolCapacityPercent}%`,
}}
/>
</div>
<div className="text-text-primary dark:text-white text-body-small font-bold font-inter whitespace-nowrap">
{poolCapacityPercent}% Filled
</div>
</div>
</div>
</div>
{/* Invest Button */}
<div className="flex-shrink-0">
<Button
color="default"
variant="solid"
onPress={onInvest}
className={buttonStyles({ intent: "theme" })}
>
Invest
</Button>
</div>
</div>
</CardBody>
</Card>
);
}

View File

@@ -0,0 +1,74 @@
"use client";
import Image from "next/image";
import { Card, CardBody } from "@heroui/react";
interface StatData {
label: string;
value: string;
change: string;
isPositive: boolean;
}
interface StatsCardsProps {
stats?: StatData[];
}
export default function StatsCards({ stats = [] }: StatsCardsProps) {
if (!stats || stats.length === 0) {
return null;
}
return (
<div className="flex flex-row gap-6 items-start justify-start self-stretch">
{stats.map((stat, index) => {
const isLastCard = index === stats.length - 1;
const isFirstCard = index === 0;
const valueColor = isLastCard && stat.value === "--" ? "text-green-500" : "text-text-primary dark:text-white";
return (
<Card
key={index}
className={`flex-1 rounded-[2rem] h-full relative overflow-hidden group bg-bg-subtle dark:bg-gray-800 border border-border-gray dark:border-gray-700 shadow-[0_8px_32px_rgba(0,0,0,0.03)] transition-all duration-500 hover:shadow-[0_16px_48px_rgba(0,0,0,0.06)]
${index === 0 ? 'bg-gradient-to-br from-blue-50/80 to-transparent dark:from-blue-900/20 dark:to-transparent' : ''}
${index === 1 ? 'bg-gradient-to-br from-green-50/80 to-transparent dark:from-green-900/20 dark:to-transparent' : ''}
${index === 2 ? 'bg-gradient-to-br from-purple-50/80 to-transparent dark:from-purple-900/20 dark:to-transparent' : ''}
`}
>
<CardBody className="p-7 flex flex-col gap-3">
{/* Header with label and change badge */}
<div className={`flex flex-row gap-2 items-center ${index === 1 || index === 2 || index === 3 ? 'justify-between' : 'justify-start'} self-stretch`}>
<div className="text-text-tertiary dark:text-gray-400 text-[10px] font-bold leading-[150%] tracking-[0.01em] font-inter">
{stat.label}
</div>
<div
className={`bg-green-50 dark:bg-green-900/30 rounded-full px-1 py-0.5 flex flex-row gap-0 items-center justify-start h-5 ${
isFirstCard ? 'opacity-0' : 'opacity-100'
}`}
>
<Image
src="/stats-icon.svg"
alt=""
width={12}
height={12}
className="flex-shrink-0"
/>
<div className="text-green-500 dark:text-green-400 text-[10px] font-bold leading-[150%] tracking-[0.01em] font-inter">
{stat.change}
</div>
</div>
</div>
{/* Value */}
<div className="flex flex-row gap-4 items-end justify-start">
<div className={`${valueColor} text-heading-h2 font-extrabold font-jetbrains`}>
{stat.value}
</div>
</div>
</CardBody>
</Card>
);
})}
</div>
);
}

View File

@@ -0,0 +1,56 @@
"use client";
import { Button } from "@heroui/react";
import Image from "next/image";
type ViewMode = "grid" | "list";
interface ViewToggleProps {
value: ViewMode;
onChange: (mode: ViewMode) => void;
}
export default function ViewToggle({ value, onChange }: ViewToggleProps) {
return (
<div className="bg-gray-200 dark:bg-gray-700 rounded-lg p-1 flex gap-0">
<Button
isIconOnly
size="sm"
variant="light"
onPress={() => onChange("list")}
className={`w-8 h-8 min-w-8 rounded-lg transition-all ${
value === "list"
? "bg-white dark:bg-gray-600 shadow-sm"
: "bg-transparent hover:bg-gray-300 dark:hover:bg-gray-600"
}`}
style={{ padding: 0 }}
>
<Image
src="/edit-list-unordered0.svg"
alt="List view"
width={24}
height={24}
/>
</Button>
<Button
isIconOnly
size="sm"
variant="light"
onPress={() => onChange("grid")}
className={`w-8 h-8 min-w-8 rounded-lg transition-all ${
value === "grid"
? "bg-white dark:bg-gray-600 shadow-sm"
: "bg-transparent hover:bg-gray-300 dark:hover:bg-gray-600"
}`}
style={{ padding: 0 }}
>
<Image
src="/menu-more-grid-small0.svg"
alt="Grid view"
width={24}
height={24}
/>
</Button>
</div>
);
}

View File

@@ -0,0 +1,53 @@
"use client";
import Image from "next/image";
import Link from "next/link";
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) => {
const isLast = index === items.length - 1;
const content = (
<span
className={`text-sm font-medium leading-[150%] ${
isLast
? "text-text-primary dark:text-white font-bold"
: "text-text-tertiary dark:text-gray-400 hover:text-text-secondary dark:hover:text-gray-300 transition-colors cursor-pointer"
}`}
>
{item.label}
</span>
);
return (
<div key={index} className="flex items-center gap-[3px]">
{!isLast && item.href ? (
<Link href={item.href}>{content}</Link>
) : (
content
)}
{!isLast && (
<Image
src="/icon-chevron-right.svg"
alt=""
width={14}
height={14}
className="flex-shrink-0 dark:invert"
/>
)}
</div>
);
})}
</nav>
);
}

View File

@@ -0,0 +1,45 @@
"use client";
import { useApp } from "@/contexts/AppContext";
import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Button } from "@heroui/react";
export default function LanguageSwitch() {
const { language, setLanguage } = useApp();
const languages = [
{ key: "zh", label: "中文" },
{ key: "en", label: "English" },
];
const handleSelectionChange = (key: React.Key) => {
setLanguage(key as "zh" | "en");
};
return (
<Dropdown>
<DropdownTrigger>
<Button
variant="bordered"
className="bg-bg-surface dark:bg-gray-800 border-border-normal dark:border-gray-700 min-w-10 h-10 px-3 rounded-lg"
>
<span className="text-sm font-medium text-text-primary dark:text-white">
{language === "zh" ? "中" : "EN"}
</span>
</Button>
</DropdownTrigger>
<DropdownMenu
aria-label="Language selection"
selectedKeys={new Set([language])}
selectionMode="single"
onSelectionChange={(keys) => {
const key = Array.from(keys)[0];
if (key) handleSelectionChange(key);
}}
>
{languages.map((lang) => (
<DropdownItem key={lang.key}>{lang.label}</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
);
}

View File

@@ -0,0 +1,59 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
interface NavItemProps {
icon: string;
label: string;
href: string;
}
export default function NavItem({ icon, label, href }: NavItemProps) {
const pathname = usePathname();
const isActive = pathname === href;
return (
<Link
href={href}
className={`
rounded-xl
pl-4
flex
items-center
gap-2
h-[42px]
w-full
overflow-hidden
transition-colors
${isActive
? 'bg-fill-secondary-click dark:bg-gray-700'
: 'hover:bg-gray-50 dark:hover:bg-gray-700'
}
`}
>
<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 dark:text-white font-bold'
: 'text-text-tertiary dark:text-gray-400 font-medium'
}
`}
>
{label}
</span>
</Link>
);
}

View File

@@ -0,0 +1,19 @@
interface PageTitleProps {
title: string;
subtitle?: string;
}
export default function PageTitle({ title, subtitle }: PageTitleProps) {
return (
<div className="mb-8">
<h1 className="text-heading-h2 font-bold text-text-primary dark:text-white">
{title}
</h1>
{subtitle && (
<p className="text-body-large text-text-secondary dark:text-gray-400 mt-2">
{subtitle}
</p>
)}
</div>
);
}

View File

@@ -0,0 +1,17 @@
import { ReactNode } from "react";
interface SectionHeaderProps {
title: string;
children?: ReactNode;
}
export default function SectionHeader({ title, children }: SectionHeaderProps) {
return (
<div className="flex flex-row items-center justify-between">
<h2 className="text-text-primary dark:text-white text-heading-h3 font-bold">
{title}
</h2>
{children && <div>{children}</div>}
</div>
);
}

View File

@@ -0,0 +1,119 @@
"use client";
import { useState, useEffect } from "react";
import Image from "next/image";
import { usePathname, useRouter } from "next/navigation";
import { Listbox, ListboxItem } from "@heroui/react";
import { useApp } from "@/contexts/AppContext";
export default function Sidebar() {
const { t } = useApp();
const pathname = usePathname();
const router = useRouter();
const navigationItems = [
{ icon: "/icon-lending.svg", label: t("nav.fundMarket"), key: "FundMarket", 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" },
];
const [currentKey, setCurrentKey] = useState("FundMarket");
useEffect(() => {
const matched = navigationItems.find(item => item.path === pathname)?.key;
if (matched) {
setCurrentKey(matched);
}
}, [pathname]);
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 dark:invert"
/>
</div>
{/* Navigation */}
<Listbox
aria-label="Navigation"
selectedKeys={[currentKey]}
onAction={(key) => {
const item = navigationItems.find(i => i.key === key);
if (item) router.push(item.path);
}}
classNames={{
base: "w-full p-0",
list: "gap-1",
}}
>
{navigationItems.map((item) => (
<ListboxItem
key={item.key}
classNames={{
base: [
"h-11 px-3 rounded-xl gap-3",
"data-[selected=true]:bg-fill-tertiary-normal dark:data-[selected=true]:bg-gray-700",
"data-[selected=true]:text-text-primary dark:data-[selected=true]:text-white",
"hover:bg-fill-quaternary dark:hover:bg-gray-700",
].join(" "),
}}
startContent={
<div className="w-[22px] h-[22px] flex-shrink-0 relative">
<Image
src={item.icon}
alt={item.label}
width={22}
height={22}
className="w-full h-full"
/>
</div>
}
>
{item.label}
</ListboxItem>
))}
</Listbox>
{/* 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>
);
}

View File

@@ -0,0 +1,53 @@
"use client";
import { useApp } from "@/contexts/AppContext";
import { Button } from "@heroui/react";
export default function ThemeSwitch() {
const { theme, setTheme } = useApp();
const toggleTheme = () => {
setTheme(theme === "light" ? "dark" : "light");
};
return (
<Button
isIconOnly
variant="bordered"
onPress={toggleTheme}
className="bg-bg-surface dark:bg-gray-800 border-border-normal dark:border-gray-700 min-w-10 h-10 rounded-lg"
aria-label={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
>
{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="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className="text-text-primary dark:text-white"
/>
<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="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
className="text-text-primary dark:text-white"
/>
</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="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className="text-text-primary dark:text-white"
/>
</svg>
)}
</Button>
);
}

View File

@@ -0,0 +1,62 @@
"use client";
import Image from "next/image";
import { Button } from "@heroui/react";
import Breadcrumb from "./Breadcrumb";
import LanguageSwitch from "./LanguageSwitch";
import ThemeSwitch from "./ThemeSwitch";
interface BreadcrumbItem {
label: string;
href?: string;
}
interface TopBarProps {
breadcrumbItems?: BreadcrumbItem[];
}
export default function TopBar({ breadcrumbItems }: TopBarProps) {
return (
<div className="flex items-center justify-between w-full">
{/* Left: Breadcrumb */}
<div className="flex items-center gap-2">
{breadcrumbItems && <Breadcrumb items={breadcrumbItems} />}
</div>
{/* Right: Actions */}
<div className="flex items-center gap-4">
{/* Language Switch */}
<LanguageSwitch />
{/* Theme Switch */}
<ThemeSwitch />
{/* Wallet Button */}
<Button
isIconOnly
variant="bordered"
className="bg-bg-surface border-border-normal min-w-10 h-10 rounded-lg"
>
<div className="flex items-center justify-center">
<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"
/>
</div>
</Button>
{/* Address Button */}
<Button
className="bg-text-primary text-white font-bold font-jetbrains text-sm h-10 px-4 rounded-lg"
startContent={<Image src="/icon-copy.svg" alt="Copy" width={16} height={16} />}
>
0x12...4F82
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,186 @@
"use client";
import { Button } from "@heroui/react";
import { useRouter } from "next/navigation";
import { useApp } from "@/contexts/AppContext";
import BorderedButton from "@/components/common/BorderedButton";
interface BorrowMarketItem {
icon: string;
iconBg: string;
name: string;
nameEn?: string;
category: string;
badge?: string;
yourBalance: string;
yourInterest: string;
borrowed: string;
ltv: string;
ltvColor: string;
ltvProgress: number;
}
export default function BorrowMarket() {
const { t } = useApp();
const router = useRouter();
const items: BorrowMarketItem[] = [
{
icon: "GY",
iconBg: "linear-gradient(135deg, #FF8904 0%, #F54900 100%)",
name: "高盈美股量化策略",
category: "Quant Strategy • RWA-042",
yourBalance: "$25,000",
yourInterest: "+$1,250",
borrowed: "$12,500",
ltv: "50%",
ltvColor: "text-[#ff6900]",
ltvProgress: 50,
},
{
icon: "LOGO",
iconBg: "linear-gradient(135deg, #00BBA7 0%, #007A55 100%)",
name: "HK Commercial RE",
nameEn: "PI Only",
category: "Real Estate • RWA-109",
yourBalance: "$25,000",
yourInterest: "+$1,250",
borrowed: "$12,500",
ltv: "40%",
ltvColor: "text-[#ff6900]",
ltvProgress: 40,
},
{
icon: "LOGO",
iconBg: "linear-gradient(135deg, #00BBA7 0%, #007A55 100%)",
name: "HK Commercial RE",
category: "Real Estate • RWA-109",
yourBalance: "$25,000",
yourInterest: "+$1,250",
borrowed: "$12,500",
ltv: "0%",
ltvColor: "text-text-tertiary",
ltvProgress: 0,
},
];
return (
<div className="flex flex-col gap-3">
{/* Title */}
<h2 className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em]">
{t("lending.borrowMarket")}
</h2>
{/* Cards Grid */}
<div className="flex flex-col gap-3">
{items.map((item, index) => (
<div
key={index}
className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-6 flex items-center gap-8"
>
{/* Left Section - Token Info */}
<div className="flex items-center gap-4 w-[280px]">
{/* Token Icon */}
<div
className="w-10 h-10 rounded-full flex items-center justify-center text-white text-sm font-bold shadow-md"
style={{ background: item.iconBg }}
>
{item.icon}
</div>
{/* Token Name */}
<div className="flex flex-col h-10">
<div className="flex items-center gap-2">
<span className="text-body-default font-bold text-text-primary dark:text-white leading-[150%]">
{item.name}
</span>
{item.nameEn && (
<span className="bg-bg-subtle dark:bg-gray-700 rounded px-1.5 py-0.5 text-[10px] font-medium text-text-tertiary dark:text-gray-400">
{item.nameEn}
</span>
)}
</div>
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{item.category}
</span>
</div>
</div>
{/* Middle Section - Stats */}
<div className="flex items-center justify-between flex-1">
{/* Your Balance */}
<div className="flex flex-col w-[112.9px]">
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("lending.yourBalance")}
</span>
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
{item.yourBalance}
</span>
</div>
{/* Your Interest */}
<div className="flex flex-col w-[112.9px]">
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("lending.yourInterest")}
</span>
<span className="text-body-small font-bold text-[#10b981] dark:text-green-400 leading-[150%]">
{item.yourInterest}
</span>
</div>
{/* Borrowed */}
<div className="flex flex-col w-[112.9px]">
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("lending.borrowed")}
</span>
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
{item.borrowed}
</span>
</div>
{/* LTV */}
<div className="flex flex-col gap-2 w-[112.9px]">
<div className="flex items-center justify-between">
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("lending.ltv")}
</span>
<span className={`text-caption-tiny font-bold leading-[150%] tracking-[0.01em] ${item.ltvColor} dark:text-orange-400`}>
{item.ltv}
</span>
</div>
{/* Progress Bar */}
{item.ltvProgress > 0 && (
<div className="w-full h-2 bg-fill-secondary-click dark:bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full bg-[#ff6900] dark:bg-orange-500 rounded-full"
style={{ width: `${item.ltvProgress}%` }}
/>
</div>
)}
</div>
</div>
{/* Right Section - Buttons */}
<div className="flex items-center gap-3">
<Button
className="rounded-xl h-10 px-6 text-body-small font-bold bg-foreground text-background"
isDisabled={index === 2}
onPress={() => router.push("/repay")}
>
{t("lending.repay")}
</Button>
<BorderedButton
size="md"
isTheme
className="whitespace-nowrap"
>
{t("lending.borrow")}
</BorderedButton>
</div>
</div>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,56 @@
"use client";
import { useApp } from "@/contexts/AppContext";
export default function LendingHeader() {
const { t } = useApp();
const stats = [
{
label: t("lending.totalUsdcSupply"),
value: "$200.4M",
valueColor: "text-text-primary dark:text-white",
},
{
label: t("lending.utilization"),
value: "65%",
valueColor: "text-[#10b981] dark:text-green-400",
},
{
label: t("lending.activeLoans"),
value: "3",
valueColor: "text-text-primary dark:text-white",
},
];
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">
{/* Title Section */}
<div className="flex flex-col gap-0">
<h1 className="text-heading-h2 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.01em]">
{t("lending.title")}
</h1>
<p className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("lending.subtitle")}
</p>
</div>
{/* Stats Cards */}
<div className="flex gap-4 w-full">
{stats.map((stat, index) => (
<div
key={index}
className="flex-1 bg-bg-subtle dark:bg-gray-700 rounded-2xl border border-border-gray dark:border-gray-600 p-4 flex flex-col gap-2"
>
<span className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{stat.label}
</span>
<span className={`text-heading-h2 font-bold leading-[130%] tracking-[-0.01em] ${stat.valueColor}`}>
{stat.value}
</span>
</div>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,62 @@
"use client";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { Button } from "@heroui/react";
import { useApp } from "@/contexts/AppContext";
import { buttonStyles } from "@/lib/buttonStyles";
export default function LendingPlaceholder() {
const { t } = useApp();
const router = useRouter();
return (
<div className="relative rounded-2xl h-[180px] overflow-hidden">
{/* Dark Background */}
<div className="absolute inset-0 bg-[#0f172b]" />
{/* Green Glow Effect */}
<div
className="absolute w-32 h-32 rounded-full right-0 top-[-64px]"
style={{
background: "rgba(0, 188, 125, 0.2)",
filter: "blur(40px)",
}}
/>
{/* Content - Centered */}
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col gap-4 w-[90%]">
{/* Your Portfolio Section */}
<div className="flex flex-col gap-2">
{/* Label Row */}
<div className="flex items-center justify-between h-[21px]">
<span className="text-[10px] font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("lending.yourPortfolio")}
</span>
<span className="text-[10px] font-bold text-[#10b981] dark:text-green-400 leading-[150%] tracking-[0.01em]">
+$1,240 {t("lending.earned")}
</span>
</div>
{/* Amount */}
<div className="flex items-center gap-2">
<span className="text-heading-h2 font-bold text-white leading-[130%] tracking-[-0.01em]">
$15,500
</span>
</div>
</div>
{/* Supply USDC Button */}
<Button
color="default"
variant="solid"
className="h-11 rounded-xl px-6 text-body-small font-bold bg-[#282E3F] dark:bg-white dark:text-[#282E3F] text-background"
onPress={() => router.push("/supply")}
endContent={<Image src="/arrow-right-icon.svg" alt="" width={20} height={20} />}
>
{t("lending.supplyUsdc")}
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,73 @@
"use client";
import Image from "next/image";
import { useApp } from "@/contexts/AppContext";
export default function LendingStats() {
const { t } = useApp();
// Mini bar chart data (heights in percentage)
const chartBars = [
{ height: 40, color: "#f2fcf7" },
{ height: 55, color: "#e1f8ec" },
{ height: 45, color: "#cef3e0" },
{ height: 65, color: "#b8ecd2" },
{ height: 80, color: "#00ad76" },
{ height: 95, color: "#10b981" },
];
return (
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-8 flex items-center gap-6 h-[180px]">
{/* Left Section - USDC Borrowed and Avg. APY */}
<div className="flex items-center gap-12 flex-1">
{/* USDC Borrowed */}
<div className="flex flex-col gap-1">
<span className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("lending.usdcBorrowed")}
</span>
<span className="text-heading-h2 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.01em]">
$125.2M
</span>
<div className="flex items-center gap-1">
<Image src="/icon0.svg" alt="" width={14} height={14} />
<span className="text-[12px] font-medium text-[#10b981] dark:text-green-400 leading-[16px]">
+12% {t("lending.vsLastMonth")}
</span>
</div>
</div>
{/* Divider */}
<div className="w-px h-12 bg-border-gray dark:bg-gray-600" />
{/* Avg. APY */}
<div className="flex flex-col gap-1">
<span className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("lending.avgApy")}
</span>
<span className="text-heading-h2 font-bold text-[#10b981] dark:text-green-400 leading-[130%] tracking-[-0.01em]">
20.5%
</span>
<div className="flex items-center h-4">
<span className="text-[12px] font-regular text-text-tertiary dark:text-gray-400 leading-[16px]">
{t("lending.stableYield")}
</span>
</div>
</div>
</div>
{/* Right Section - Mini Chart */}
<div className="flex items-end gap-1 w-48 h-16">
{chartBars.map((bar, index) => (
<div
key={index}
className="flex-1 rounded-t-md transition-all"
style={{
backgroundColor: bar.color,
height: `${bar.height}%`,
}}
/>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,62 @@
"use client";
import { Button } from "@heroui/react";
import { useApp } from "@/contexts/AppContext";
import { buttonStyles } from "@/lib/buttonStyles";
export default function RepayBorrowDebt() {
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-8 flex-1 shadow-md">
{/* Title */}
<h3 className="text-body-large font-bold text-text-primary dark:text-white leading-[150%]">
{t("repay.borrowDebt")}
</h3>
{/* Token Info and APR */}
<div className="flex items-center justify-between h-[50px]">
{/* Left - Token Info */}
<div className="flex items-center gap-4">
{/* Token Icon */}
<div
className="w-10 h-10 rounded-full flex items-center justify-center text-white text-[10px] font-bold shadow-md"
style={{ background: "linear-gradient(135deg, #00BBA7 0%, #007A55 100%)" }}
>
LOGO
</div>
{/* Amount */}
<div className="flex flex-col">
<span className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em]">
1,000 USDC
</span>
<span className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
$1,000.00
</span>
</div>
</div>
{/* Right - APR */}
<div className="flex flex-col items-end">
<span className="text-heading-h3 font-bold text-[#10b981] dark:text-green-400 leading-[130%] tracking-[-0.005em]">
6.1%
</span>
<span className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("repay.apr")}
</span>
</div>
</div>
{/* Buttons */}
<div className="grid grid-cols-2 gap-3">
<Button className={buttonStyles({ intent: "theme" })}>
{t("repay.borrow")}
</Button>
<Button className="rounded-xl h-11 w-full px-6 text-body-small font-bold bg-content2 text-foreground">
{t("repay.repay")}
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,66 @@
"use client";
import Image from "next/image";
import { useApp } from "@/contexts/AppContext";
export default function RepayHeader() {
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 items-center justify-between">
{/* Left Section - Token Icons and Title */}
<div className="flex items-center gap-2">
{/* Overlapping Token Icons */}
<div className="flex items-center relative">
<Image
src="/component-70.svg"
alt="YTGY"
width={52}
height={52}
className="relative z-10"
/>
<Image
src="/usd-coin-usdc-logo-10.svg"
alt="USDC"
width={52}
height={52}
className="relative -ml-3"
/>
</div>
{/* Title and Subtitle */}
<div className="flex flex-col gap-1">
<h1 className="text-heading-h2 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.01em]">
GY / USDC
</h1>
<p className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
{t("repay.supplyToBorrow")}
</p>
</div>
</div>
{/* Right Section - Stats */}
<div className="flex items-center justify-between w-[262px]">
{/* Price */}
<div className="flex flex-col gap-1">
<span className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("repay.price")}
</span>
<span className="text-heading-h3 font-bold text-[#10b981] dark:text-green-400 leading-[130%] tracking-[-0.005em]">
$1.29
</span>
</div>
{/* Available */}
<div className="flex flex-col gap-1">
<span className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("repay.available")}
</span>
<span className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em]">
14.2M
</span>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,63 @@
"use client";
import { useApp } from "@/contexts/AppContext";
export default function RepayPoolStats() {
const { t } = useApp();
return (
<div className="grid grid-cols-2 gap-6">
{/* Left Card - Total Value Locked and Utilization */}
<div className="bg-white/50 dark:bg-gray-800/50 rounded-2xl border border-border-normal dark:border-gray-700 px-6 flex items-center justify-between h-[98px]">
{/* Total Value Locked */}
<div className="flex flex-col gap-1">
<span className="text-[12px] font-bold text-[#90a1b9] dark:text-gray-400 leading-4 uppercase">
{t("repay.totalValueLocked")}
</span>
<span className="text-[20px] font-bold text-[#0f172b] dark:text-white leading-7 tracking-[-0.45px]">
$124,592,102
</span>
</div>
{/* Utilization */}
<div className="flex flex-col gap-1">
<span className="text-[12px] font-bold text-[#90a1b9] dark:text-gray-400 leading-4 uppercase text-right">
{t("repay.utilization")}
</span>
<span className="text-[20px] font-bold text-[#0f172b] dark:text-white leading-7 tracking-[-0.45px] text-right">
42.8%
</span>
</div>
</div>
{/* Right Card - Reward Multiplier */}
<div className="bg-white/50 dark:bg-gray-800/50 rounded-2xl border border-border-normal dark:border-gray-700 px-6 flex items-center justify-between h-[98px]">
{/* Reward Multiplier */}
<div className="flex flex-col gap-1">
<span className="text-[12px] font-bold text-[#90a1b9] dark:text-gray-400 leading-4 uppercase">
{t("repay.rewardMultiplier")}
</span>
<span className="text-[20px] font-bold text-[#0f172b] dark:text-white leading-7 tracking-[-0.45px]">
2.5x
</span>
</div>
{/* Overlapping Circles */}
<div className="relative w-20 h-8">
{/* Green Circle */}
<div className="absolute left-0 top-0 w-8 h-8 rounded-full bg-[#00bc7d] border-2 border-white dark:border-gray-900" />
{/* Blue Circle */}
<div className="absolute left-6 top-0 w-8 h-8 rounded-full bg-[#2b7fff] border-2 border-white dark:border-gray-900" />
{/* Gray Circle with +3 */}
<div className="absolute left-12 top-0 w-8 h-8 rounded-full bg-[#e2e8f0] dark:bg-gray-600 border-2 border-white dark:border-gray-900 flex items-center justify-center">
<span className="text-[10px] font-bold text-[#45556c] dark:text-gray-300 leading-[15px] tracking-[0.12px]">
+3
</span>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,69 @@
"use client";
import Image from "next/image";
import { useApp } from "@/contexts/AppContext";
export default function RepayStats() {
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-8 flex-1 h-[249px] shadow-md">
{/* Stats Info */}
<div className="flex flex-col gap-6 h-[120px]">
{/* NET APR */}
<div className="flex items-center justify-between h-7">
<div className="flex items-center gap-2">
<Image src="/icon0.svg" alt="" width={18} height={18} />
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
{t("repay.netApr")}
</span>
</div>
<span className="text-body-small font-bold text-[#10b981] dark:text-green-400 leading-[150%]">
3%
</span>
</div>
{/* Liq.price/offset */}
<div className="flex items-center justify-between h-7">
<div className="flex items-center gap-2">
<Image src="/icon1.svg" alt="" width={18} height={18} />
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
{t("repay.liqPriceOffset")}
</span>
</div>
<div className="flex items-center gap-1">
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
$0.938
</span>
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
/ 20%
</span>
</div>
</div>
{/* Position Health */}
<div className="flex items-center justify-between h-7">
<div className="flex items-center gap-2">
<Image src="/icon2.svg" alt="" width={18} height={18} />
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
{t("repay.positionHealth")}
</span>
</div>
<span className="text-body-small font-bold text-[#10b981] dark:text-green-400 leading-[150%]">
Safe 50%
</span>
</div>
</div>
{/* Progress Bar */}
<div className="w-full">
<div className="w-full h-2 bg-fill-secondary-click dark:bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full bg-[#10b981] dark:bg-green-500 rounded-full"
style={{ width: "50%" }}
/>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,62 @@
"use client";
import { Button } from "@heroui/react";
import { useApp } from "@/contexts/AppContext";
import { buttonStyles } from "@/lib/buttonStyles";
export default function RepaySupplyCollateral() {
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-8 flex-1 shadow-md">
{/* Title */}
<h3 className="text-body-large font-bold text-text-primary dark:text-white leading-[150%]">
{t("repay.supplyCollateral")}
</h3>
{/* Token Info and APR */}
<div className="flex items-center justify-between h-[50px]">
{/* Left - Token Info */}
<div className="flex items-center gap-4">
{/* Token Icon */}
<div
className="w-10 h-10 rounded-full flex items-center justify-center text-white text-sm font-bold shadow-md"
style={{ background: "linear-gradient(135deg, #FF8904 0%, #F54900 100%)" }}
>
GY
</div>
{/* Amount */}
<div className="flex flex-col">
<span className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em]">
1,000 YTGY
</span>
<span className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
$10,000.00
</span>
</div>
</div>
{/* Right - APR */}
<div className="flex flex-col items-end">
<span className="text-heading-h3 font-bold text-[#10b981] dark:text-green-400 leading-[130%] tracking-[-0.005em]">
9.1%
</span>
<span className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("repay.apr")}
</span>
</div>
</div>
{/* Buttons */}
<div className="grid grid-cols-2 gap-3">
<Button className={buttonStyles({ intent: "theme" })}>
{t("repay.deposit")}
</Button>
<Button className="rounded-xl h-11 w-full px-6 text-body-small font-bold bg-content2 text-foreground">
{t("repay.withdraw")}
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,226 @@
"use client";
import { useState, useEffect, useRef } from "react";
import Image from "next/image";
import { Button } from "@heroui/react";
import { useApp } from "@/contexts/AppContext";
import * as echarts from "echarts";
export default function SupplyContent() {
const { t } = useApp();
const [selectedPeriod, setSelectedPeriod] = useState<"1W" | "1M" | "1Y">("1W");
const chartRef = useRef<HTMLDivElement>(null);
const chartInstance = useRef<echarts.ECharts | null>(null);
// 模拟 APY 数据
const generateChartData = (period: "1W" | "1M" | "1Y") => {
const dataPoints = period === "1W" ? 7 : period === "1M" ? 30 : 365;
const data = [];
let baseValue = 18 + Math.random() * 4;
for (let i = 0; i < dataPoints; i++) {
const change = (Math.random() - 0.45) * 2;
baseValue = Math.max(15, Math.min(25, baseValue + change));
data.push(parseFloat(baseValue.toFixed(2)));
}
return data;
};
const chartData = generateChartData(selectedPeriod);
// 根据周期生成 X 轴标签
const getXAxisLabels = (period: "1W" | "1M" | "1Y") => {
if (period === "1W") {
return ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
} else if (period === "1M") {
return Array.from({ length: 7 }, (_, i) => `${i * 4 + 1}d`);
} else {
return Array.from({ length: 7 }, (_, i) => `${i * 50 + 1}d`);
}
};
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();
};
}, [selectedPeriod]);
const updateChart = () => {
if (!chartInstance.current) return;
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];
return `<div style="padding: 4px 8px;">
<span style="color: #9ca3af; font-size: 11px;">${data.axisValueLabel}</span><br/>
<span style="color: #10b981; font-weight: 600; font-size: 14px;">${data.value}%</span>
</div>`;
},
},
xAxis: {
type: "category",
data: getXAxisLabels(selectedPeriod),
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
show: selectedPeriod === "1W",
color: "#9ca3af",
fontSize: 10,
fontWeight: 500,
},
},
yAxis: {
type: "value",
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
show: false,
},
splitLine: {
show: false,
},
},
series: [
{
data: chartData,
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-8 flex flex-col h-full w-full shadow-md">
{/* Header - USDC Lend Pool */}
<div className="flex items-center gap-3 flex-shrink-0">
<Image
src="/usd-coin-usdc-logo-10.svg"
alt="USDC"
width={32}
height={32}
/>
<h1 className="text-heading-h2 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.01em]">
{t("supply.usdcLendPool")}
</h1>
</div>
{/* Historical APY Section */}
<div className="flex items-start justify-between flex-shrink-0">
{/* Left - APY Display */}
<div className="flex flex-col gap-1">
<span className="text-[12px] font-bold text-text-tertiary dark:text-gray-400 leading-[16px] tracking-[1.2px] uppercase">
{t("supply.historicalApy")}
</span>
<div className="flex items-center gap-2">
<span className="text-heading-h2 font-bold text-[#10b981] dark:text-green-400 leading-[130%] tracking-[-0.01em]">
20.5%
</span>
<div className="bg-[#e1f8ec] dark:bg-green-900/30 rounded-full px-2 py-0.5 flex items-center justify-center">
<span className="text-caption-tiny font-bold text-[#10b981] dark:text-green-400 leading-[150%] tracking-[0.01em]">
+2.4%
</span>
</div>
</div>
</div>
{/* Right - Period Selector */}
<div className="bg-[#F9FAFB] dark:bg-gray-700 rounded-xl p-1 flex items-center gap-1">
<Button
size="sm"
variant={selectedPeriod === "1W" ? "solid" : "light"}
onPress={() => setSelectedPeriod("1W")}
>
1W
</Button>
<Button
size="sm"
variant={selectedPeriod === "1M" ? "solid" : "light"}
onPress={() => setSelectedPeriod("1M")}
>
1M
</Button>
<Button
size="sm"
variant={selectedPeriod === "1Y" ? "solid" : "light"}
onPress={() => setSelectedPeriod("1Y")}
>
1Y
</Button>
</div>
</div>
{/* Chart Section */}
<div className="flex-1 min-h-0 w-full">
{/* ECharts Chart */}
<div
ref={chartRef}
className="w-full h-full"
style={{
background: "linear-gradient(0deg, rgba(16, 185, 129, 0.1) 0%, transparent 100%)",
}}
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,159 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import { Button } from "@heroui/react";
import { useApp } from "@/contexts/AppContext";
import { buttonStyles } from "@/lib/buttonStyles";
export default function SupplyPanel() {
const { t } = useApp();
const [amount, setAmount] = useState("");
return (
<div className="p-6 flex flex-col gap-6 flex-1">
{/* Token Balance & Supplied */}
<div className="flex flex-col gap-4">
{/* Token Balance */}
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 p-3 flex flex-col gap-1">
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("supply.tokenBalance")}
</span>
<div className="flex items-center justify-between">
<span className="text-body-large font-bold text-text-primary dark:text-white leading-[150%]">
45,230.00
</span>
<span className="text-caption-tiny font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
JLP
</span>
</div>
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
$45,230.00
</span>
</div>
{/* Supplied */}
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 p-3 flex flex-col gap-1">
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("supply.supplied")}
</span>
<div className="flex items-center justify-between">
<span className="text-body-large font-bold text-text-primary dark:text-white leading-[150%]">
0.00
</span>
<span className="text-caption-tiny font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
USDC
</span>
</div>
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
$0.00
</span>
</div>
</div>
{/* Deposit Section */}
<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-4">
{/* Deposit Label and Available */}
<div className="flex items-center gap-2">
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
{t("supply.deposit")}
</span>
<div className="flex items-center gap-2 ml-auto">
<Image src="/wallet-icon.svg" alt="" width={12} height={12} />
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
45,230.00 USDC
</span>
</div>
<Button size="sm" color="default" variant="solid" className={buttonStyles({ intent: "max" })}>
{t("supply.max")}
</Button>
</div>
{/* Input Row */}
<div className="flex items-center justify-between h-12">
{/* USDC Token Button */}
<div className="bg-bg-surface dark:bg-gray-800 rounded-full border border-border-normal dark:border-gray-600 p-2 flex items-center gap-2 h-12">
<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 leading-[150%]">
USDC
</span>
</div>
{/* Amount Input */}
<div className="flex flex-col items-end">
<input
type="text"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="0.00"
className="text-heading-h3 font-bold text-text-input-box dark:text-gray-500 leading-[130%] tracking-[-0.005em] text-right bg-transparent outline-none w-24"
/>
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
--
</span>
</div>
</div>
</div>
{/* Health Factor */}
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<div className="flex flex-col gap-1">
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("supply.healthFactor")}
</span>
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
0% {t("supply.utilization")}
</span>
</div>
<span className="text-caption-tiny font-bold text-[#10b981] dark:text-green-400 leading-[150%] tracking-[0.01em]">
{t("supply.safe")}
</span>
</div>
{/* Progress Bar */}
<div className="w-full h-2 bg-fill-secondary-click dark:bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full rounded-full"
style={{
background: "linear-gradient(90deg, #10b981 0%, #ffb933 100%)",
width: "0%",
}}
/>
</div>
</div>
{/* Estimated Returns */}
<div className="bg-bg-subtle dark:bg-gray-700 rounded-2xl p-4 flex flex-col gap-2">
<span className="text-body-small font-medium text-text-secondary dark:text-gray-300 leading-[150%]">
{t("supply.estimatedReturns")}
</span>
<div className="flex items-center justify-between">
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
{t("supply.estApy")}
</span>
<span className="text-body-small font-bold text-[#ff6900] dark:text-orange-400 leading-[150%]">
22%
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
{t("supply.estReturnsYear")}
</span>
<span className="text-body-small font-bold text-[#10b981] dark:text-green-400 leading-[150%]">
~ $0.50
</span>
</div>
</div>
{/* Supply Button */}
<Button color="default" variant="solid" className={`${buttonStyles({ intent: "theme" })} mt-auto`} endContent={<Image src="/arrow-right-icon.svg" alt="" width={20} height={20} />}>
{t("supply.supply")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,159 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import { Button } from "@heroui/react";
import { useApp } from "@/contexts/AppContext";
import { buttonStyles } from "@/lib/buttonStyles";
export default function WithdrawPanel() {
const { t } = useApp();
const [amount, setAmount] = useState("45230");
return (
<div className="p-6 flex flex-col gap-6 flex-1">
{/* Token Balance & Supplied */}
<div className="flex flex-col gap-4">
{/* Token Balance */}
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 p-3 flex flex-col gap-1">
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("supply.tokenBalance")}
</span>
<div className="flex items-center justify-between">
<span className="text-body-large font-bold text-text-primary dark:text-white leading-[150%]">
45,230.00
</span>
<span className="text-caption-tiny font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
JLP
</span>
</div>
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
$45,230.00
</span>
</div>
{/* Supplied */}
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 p-3 flex flex-col gap-1">
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("supply.supplied")}
</span>
<div className="flex items-center justify-between">
<span className="text-body-large font-bold text-text-primary dark:text-white leading-[150%]">
0.00
</span>
<span className="text-caption-tiny font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
USDC
</span>
</div>
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
$0.00
</span>
</div>
</div>
{/* Withdraw Section */}
<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-4">
{/* Withdraw Label and Available */}
<div className="flex items-center gap-2">
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
{t("supply.withdraw")}
</span>
<div className="flex items-center gap-2 ml-auto">
<Image src="/wallet-icon.svg" alt="" width={12} height={12} />
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
45,230.00 USDC
</span>
</div>
<Button size="sm" className={buttonStyles({ intent: "max" })}>
{t("supply.max")}
</Button>
</div>
{/* Input Row */}
<div className="flex items-center justify-between h-12">
{/* USDC Token Button */}
<div className="bg-bg-surface dark:bg-gray-800 rounded-full border border-border-normal dark:border-gray-600 p-2 flex items-center gap-2 h-12">
<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 leading-[150%]">
USDC
</span>
</div>
{/* Amount Input */}
<div className="flex flex-col items-end">
<input
type="text"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="0"
className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em] text-right bg-transparent outline-none w-24"
/>
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
$1233.00 USD
</span>
</div>
</div>
</div>
{/* Health Factor */}
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<div className="flex flex-col gap-1">
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("supply.healthFactor")}
</span>
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
0% {t("supply.utilization")}
</span>
</div>
<span className="text-caption-tiny font-bold text-[#10b981] dark:text-green-400 leading-[150%] tracking-[0.01em]">
{t("supply.safe")}
</span>
</div>
{/* Progress Bar */}
<div className="w-full h-2 bg-fill-secondary-click dark:bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full rounded-full"
style={{
background: "linear-gradient(90deg, #10b981 0%, #ffb933 100%)",
width: "0%",
}}
/>
</div>
</div>
{/* Estimated Returns */}
<div className="bg-bg-subtle dark:bg-gray-700 rounded-2xl p-4 flex flex-col gap-2">
<span className="text-body-small font-medium text-text-secondary dark:text-gray-300 leading-[150%]">
{t("supply.estimatedReturns")}
</span>
<div className="flex items-center justify-between">
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
{t("supply.estApy")}
</span>
<span className="text-body-small font-bold text-[#ff6900] dark:text-orange-400 leading-[150%]">
22%
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
{t("supply.estReturnsYear")}
</span>
<span className="text-body-small font-bold text-[#10b981] dark:text-green-400 leading-[150%]">
~ $0.50
</span>
</div>
</div>
{/* Withdraw Button */}
<Button className={`${buttonStyles({ intent: "theme" })} mt-auto`} endContent={<Image src="/arrow-right-icon.svg" alt="" width={20} height={20} />}>
{t("supply.withdraw")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,190 @@
"use client";
import { useState } from "react";
import Image from "next/image";
type FilterTab = "All" | "Referrals" | "Deposits";
interface ActivityRow {
user: string;
friend: string;
code: string;
points: string;
}
const mockData: ActivityRow[] = [
{ user: "0x44...9A21", friend: "0x88...1B2C", code: "REF-001", points: "+120" },
{ user: "0x44...9A21", friend: "0x88...1B2C", code: "REF-001", points: "+120" },
{ user: "0x44...9A21", friend: "0x88...1B2C", code: "REF-001", points: "+120" },
{ user: "0x44...9A21", friend: "0x88...1B2C", code: "REF-001", points: "+120" },
{ user: "0x44...9A21", friend: "0x88...1B2C", code: "REF-001", points: "+120" },
];
export default function ActivityHistory() {
const [activeTab, setActiveTab] = useState<FilterTab>("All");
const [currentPage, setCurrentPage] = useState(1);
const totalPages = 4;
return (
<div className="w-full bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-8 flex flex-col gap-6">
{/* Top Section - Title and Filter Tabs */}
<div className="flex items-center justify-between">
<h3 className="text-heading-h4 font-bold text-text-primary dark:text-white leading-[140%]">
Activity History
</h3>
{/* Filter Tabs */}
<div className="bg-[#f9fafb] dark:bg-gray-700 rounded-xl p-1 flex items-center gap-0 h-9">
<button
onClick={() => setActiveTab("All")}
className={`px-4 h-full rounded-lg text-body-small font-bold transition-all min-w-[60px] ${
activeTab === "All"
? "bg-white dark:bg-gray-600 text-text-primary dark:text-white shadow-sm"
: "text-text-tertiary dark:text-gray-400"
}`}
>
All
</button>
<button
onClick={() => setActiveTab("Referrals")}
className={`px-4 h-full rounded-lg text-body-small font-bold transition-all min-w-[90px] ${
activeTab === "Referrals"
? "bg-white dark:bg-gray-600 text-text-primary dark:text-white shadow-sm"
: "text-text-tertiary dark:text-gray-400"
}`}
>
Referrals
</button>
<button
onClick={() => setActiveTab("Deposits")}
className={`px-4 h-full rounded-lg text-body-small font-bold transition-all min-w-[85px] ${
activeTab === "Deposits"
? "bg-white dark:bg-gray-600 text-text-primary dark:text-white shadow-sm"
: "text-text-tertiary dark:text-gray-400"
}`}
>
Deposits
</button>
</div>
</div>
{/* Table Card */}
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 overflow-hidden">
{/* Table Header Section */}
<div className="bg-bg-surface dark:bg-gray-800 border-b border-border-gray dark:border-gray-700 p-6 flex items-center justify-between">
<div className="flex flex-col gap-1">
<h4 className="text-heading-h4 font-bold text-text-primary dark:text-white leading-[140%]">
Activity History
</h4>
<p className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
Track all your points-earning activities
</p>
</div>
<div className="flex items-center gap-2">
<Image src="/icon-refresh.svg" alt="Refresh" width={16} height={16} />
<span className="text-caption-tiny font-regular text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
Last updated: 2 minutes ago
</span>
</div>
</div>
{/* Table */}
<div className="overflow-auto">
{/* Table Header */}
<div className="bg-bg-subtle dark:bg-gray-700/30 border-b border-border-gray dark:border-gray-700 flex">
<div className="flex-1 px-6 py-4">
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
User
</span>
</div>
<div className="flex-1 px-6 py-4">
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
Friends
</span>
</div>
<div className="flex-1 px-6 py-4">
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
Code
</span>
</div>
<div className="flex-1 px-6 py-4">
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
Points
</span>
</div>
</div>
{/* Table Body */}
{mockData.map((row, index) => (
<div
key={index}
className={`flex ${
index !== mockData.length - 1 ? "border-b border-border-gray dark:border-gray-700" : ""
}`}
>
<div className="flex-1 px-6 py-4">
<span className="text-body-small font-bold text-text-primary dark:text-white font-jetbrains">
{row.user}
</span>
</div>
<div className="flex-1 px-6 py-4">
<span className="text-body-small font-bold text-text-primary dark:text-white font-jetbrains">
{row.friend}
</span>
</div>
<div className="flex-1 px-6 py-4">
<span className="text-body-small font-bold text-text-primary dark:text-white font-jetbrains">
{row.code}
</span>
</div>
<div className="flex-1 px-6 py-4">
<span className="text-body-small font-bold leading-[150%]" style={{ color: "#10b981" }}>
{row.points}
</span>
</div>
</div>
))}
</div>
</div>
{/* Pagination */}
<div className="flex items-center justify-center gap-4">
{/* Previous Button */}
<button
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
className="w-10 h-10 flex items-center justify-center disabled:opacity-50"
>
<Image src="/icon-chevron-left.svg" alt="Previous" width={10} height={10} />
</button>
{/* Page Numbers */}
<div className="flex items-center gap-3">
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
<button
key={page}
onClick={() => setCurrentPage(page)}
className={`px-[10px] py-[3px] rounded-lg text-sm leading-[22px] transition-all ${
currentPage === page
? "bg-bg-subtle dark:bg-gray-700 text-text-primary dark:text-white"
: "text-text-tertiary dark:text-gray-400"
}`}
>
{page}
</button>
))}
</div>
{/* Next Button */}
<button
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
className="w-10 h-10 flex items-center justify-center disabled:opacity-50"
>
<Image src="/icon-chevron-right.svg" alt="Next" width={10} height={10} />
</button>
</div>
</div>
);
}

View File

@@ -0,0 +1,49 @@
import Image from "next/image";
import { Button } from "@heroui/react";
interface BindInviteCardProps {
placeholder?: string;
onApply?: (code: string) => void;
}
export default function BindInviteCard({
placeholder = "ENTER CODE",
onApply,
}: BindInviteCardProps) {
return (
<div className="flex-[32] 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 gap-3 h-6">
<Image src="/icon0.svg" alt="" width={24} height={24} />
<span className="text-body-default font-bold text-text-primary dark:text-white leading-[150%]">
BIND INVITE
</span>
</div>
{/* Description */}
<p className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
Were you invited? Enter the code here to boost your base yield by +0.5%.
</p>
{/* Input and Button */}
<div className="flex flex-col gap-4">
{/* Input Field */}
<div className="flex items-center gap-2">
<div className="flex-1 bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 px-4 py-3 h-[46px] flex items-center">
<span className="text-body-default font-bold text-[#d1d5db] dark:text-gray-500 leading-[150%] font-jetbrains">
{placeholder}
</span>
</div>
</div>
{/* Apply Button */}
<Button
onClick={() => onApply?.("")}
className="bg-text-primary dark:bg-white rounded-xl h-11 text-body-small font-bold text-white dark:text-gray-900"
>
Apply
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,117 @@
import Image from "next/image";
import { Button } from "@heroui/react";
interface DepositCardProps {
logo?: string;
title: string;
subtitle: string;
badge: string;
lockPeriod: string;
progressPercent: number;
pointsBoost: string;
onDeposit?: () => void;
}
export default function DepositCard({
logo,
title,
subtitle,
badge,
lockPeriod,
progressPercent,
pointsBoost,
onDeposit,
}: DepositCardProps) {
return (
<div className="w-full bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-8 flex flex-col gap-6">
{/* Top Section */}
<div className="flex items-center justify-between">
{/* Left - Logo and Title */}
<div className="flex items-center gap-6">
{/* Logo */}
{logo ? (
<Image src={logo} alt={title} width={40} height={40} className="rounded-full" />
) : (
<div
className="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0"
style={{
background: "linear-gradient(135deg, rgba(0, 187, 167, 1) 0%, rgba(0, 122, 85, 1) 100%)"
}}
>
<span className="text-[10px] font-bold text-white leading-[125%] tracking-[-0.11px]">
LOGO
</span>
</div>
)}
{/* Title and Subtitle */}
<div className="flex flex-col gap-1">
<div className="flex items-center gap-3">
<h3 className="text-heading-h4 font-bold text-text-primary dark:text-white leading-[140%]">
{title}
</h3>
<div
className="rounded-full px-3 py-1 flex items-center justify-center"
style={{
background: "#ff6900",
boxShadow: "0px 4px 6px -4px rgba(255, 105, 0, 0.2), 0px 10px 15px -3px rgba(255, 105, 0, 0.2)"
}}
>
<span className="text-[10px] font-bold text-white leading-[150%] tracking-[0.01em]">
{badge}
</span>
</div>
</div>
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[133%]">
{subtitle}
</span>
</div>
</div>
{/* Right - Lock Period and Button */}
<div className="flex items-center gap-12">
<div className="flex flex-col gap-1">
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
LOCK PERIOD
</span>
<span className="text-body-default font-bold text-text-primary dark:text-white leading-[150%]">
{lockPeriod}
</span>
</div>
<Button
onClick={onDeposit}
className="bg-text-primary dark:bg-white rounded-xl h-11 px-4 text-body-small font-bold text-white dark:text-gray-900 flex items-center gap-1"
style={{ width: '163px' }}
>
Start LOOP
<Image src="/icon-arrow-right.svg" alt="" width={16} height={16} />
</Button>
</div>
</div>
{/* Bottom Section - Progress Bar */}
<div className="flex items-center justify-between border-t border-[#f1f5f9] dark:border-gray-600 pt-6">
{/* Progress Bar */}
<div className="relative flex-1 h-2 bg-[#f1f5f9] dark:bg-gray-700 rounded-full overflow-hidden max-w-[447px]">
<div
className="absolute left-0 top-0 h-full rounded-full"
style={{
width: `${progressPercent}%`,
background: "#00bc7d",
boxShadow: "0px 0px 15px 0px rgba(16, 185, 129, 0.4)"
}}
/>
</div>
{/* Points Badge */}
<span
className="text-[10px] font-black leading-[150%] tracking-[1.12px] ml-4"
style={{ color: "#00bc7d" }}
>
{pointsBoost}
</span>
</div>
</div>
);
}

View File

@@ -0,0 +1,86 @@
import { Button } from "@heroui/react";
import BorderedButton from "@/components/common/BorderedButton";
interface EarnOpportunityCardProps {
pointsLabel: string;
title: string;
subtitle: string;
metricLabel: string;
metricValue: string;
buttonText: string;
onButtonClick?: () => void;
}
export default function EarnOpportunityCard({
pointsLabel,
title,
subtitle,
metricLabel,
metricValue,
buttonText,
onButtonClick,
}: EarnOpportunityCardProps) {
return (
<div className="flex-1 bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-8 flex flex-col gap-6">
{/* Top Section - Logo and Points Badge */}
<div className="flex items-start justify-between">
{/* Logo */}
<div
className="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0"
style={{
background: "linear-gradient(135deg, rgba(0, 187, 167, 1) 0%, rgba(0, 122, 85, 1) 100%)"
}}
>
<span className="text-[10px] font-bold text-white leading-[125%] tracking-[-0.11px]">
LOGO
</span>
</div>
{/* Points Badge */}
<div
className="rounded-full px-3 py-1 flex items-center justify-center"
style={{
background: "#fff5ef",
border: "1px solid #ffc9ad"
}}
>
<span className="text-[10px] font-bold leading-[150%] tracking-[0.01em]" style={{ color: "#ff6900" }}>
{pointsLabel}
</span>
</div>
</div>
{/* Middle Section - Title and Subtitle */}
<div className="flex flex-col gap-1">
<h3 className="text-heading-h4 font-bold text-text-primary dark:text-white leading-[140%]">
{title}
</h3>
<p className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{subtitle}
</p>
</div>
{/* Bottom Section - Metric and Button */}
<div className="flex items-center gap-6">
{/* Metric */}
<div className="flex-1 flex flex-col gap-1">
<span className="text-[10px] font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{metricLabel}
</span>
<span className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em]">
{metricValue}
</span>
</div>
{/* Button */}
<BorderedButton
size="lg"
onClick={onButtonClick}
className="bg-[#f3f4f6] dark:bg-gray-700 text-text-primary dark:text-white"
>
{buttonText}
</BorderedButton>
</div>
</div>
);
}

View File

@@ -0,0 +1,91 @@
"use client";
import EarnOpportunityCard from "./EarnOpportunityCard";
import ActivityHistory from "./ActivityHistory";
import TopPerformers from "./TopPerformers";
import ReferralCodeCard from "./ReferralCodeCard";
import BindInviteCard from "./BindInviteCard";
import TeamTVLCard from "./TeamTVLCard";
import DepositCard from "./DepositCard";
export default function PointsCards() {
return (
<div className="flex flex-col gap-6">
{/* First Row - Cards 1, 2, 3 */}
<div className="flex gap-6">
<ReferralCodeCard
code="PR0T0-8821"
onCopy={() => console.log("Copy clicked")}
onShare={() => console.log("Share clicked")}
/>
<BindInviteCard
placeholder="ENTER CODE"
onApply={(code) => console.log("Apply clicked", code)}
/>
<TeamTVLCard
currentTVL="$8.5M"
totalTVL="$10M"
progressPercent={85}
members={12}
roles={[
{ icon: "/icon-whale.svg", label: "Whales", current: 3, total: 3 },
{ icon: "/icon-trader.svg", label: "Traders", current: 0, total: 3 },
{ icon: "/icon-user.svg", label: "Users", current: 5, total: 3 },
]}
/>
</div>
{/* Second Row - Card 4 */}
<DepositCard
title="Deposit USDC to ALP"
subtitle="Native Staking"
badge="UP TO 3X"
lockPeriod="30 DAYS"
progressPercent={65}
pointsBoost="+10% POINTS"
onDeposit={() => console.log("Deposit clicked")}
/>
{/* Third Row - Earn Opportunities */}
<div className="flex gap-6">
<EarnOpportunityCard
pointsLabel="7X Points"
title="Pendle YT"
subtitle="Yield Trading optimization"
metricLabel="EST. APY"
metricValue="50%-300%"
buttonText="ZAP IN"
onButtonClick={() => console.log("ZAP IN clicked")}
/>
<EarnOpportunityCard
pointsLabel="4X Points"
title="Curve LP"
subtitle="Liquidity Pool provision"
metricLabel="CURRENT APY"
metricValue="15%-35%"
buttonText="Add Liquidity"
onButtonClick={() => console.log("Add Liquidity clicked")}
/>
<EarnOpportunityCard
pointsLabel="2X-8X Points"
title="Morpho"
subtitle="Lending Loop strategy"
metricLabel="MAX LTV"
metricValue="85%"
buttonText="Start LOOP"
onButtonClick={() => console.log("Start LOOP clicked")}
/>
</div>
{/* Fourth Row - Activity History and Top Performers */}
<div className="flex gap-6">
<div className="flex-[2]">
<ActivityHistory />
</div>
<div className="flex-1">
<TopPerformers />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,115 @@
"use client";
import Image from "next/image";
import VipCard from "./VipCard";
import { useApp } from "@/contexts/AppContext";
export default function PointsDashboard() {
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 Row */}
<div className="flex items-center justify-between">
{/* Left - Tags */}
<div className="flex items-center gap-3">
{/* Season 1 Tag */}
<div className="bg-[#f9fafb] dark:bg-gray-700 border border-border-normal dark:border-gray-600 rounded-full px-3 py-1 flex items-center justify-center">
<span className="text-caption-tiny font-bold text-text-primary dark:text-white leading-[150%] tracking-[0.01em]">
{t("points.season1")}
</span>
</div>
{/* Live Tag */}
<div className="bg-[#e1f8ec] dark:bg-green-900/30 border border-[#b8ecd2] dark:border-green-700 rounded-full px-3 py-1 flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-[#10b981] opacity-55" />
<span className="text-caption-tiny font-bold text-[#10b981] dark:text-green-400 leading-[150%] tracking-[0.01em]">
{t("points.live")}
</span>
</div>
</div>
{/* Right - 7x Points Button */}
<div className="rounded-[14px] px-4 py-2 flex items-center gap-2 h-[34.78px] shadow-lg bg-gradient-to-r from-[#fee685] to-[#fdc700] rotate-1">
<Image src="/icon0.svg" alt="" width={16} height={16} />
<span className="text-caption-tiny font-bold text-text-primary leading-[150%] tracking-[0.01em]">
{t("points.xPoints")}
</span>
</div>
</div>
{/* Main Content */}
<div className="flex gap-6">
{/* Left Section - Points Info */}
<div className="flex-1 flex flex-col gap-6">
{/* Title */}
<div className="flex flex-col gap-1">
<h2 className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em]">
{t("points.pointsDashboard")}
</h2>
<p className="text-body-small font-regular text-text-tertiary dark:text-gray-400 leading-[150%]">
<span>{t("points.unlockUpTo")} </span>
<span className="font-bold text-[#fdc700]">{t("points.xPoints")}</span>
<span> {t("points.withEcosystemMultipliers")}</span>
</p>
</div>
{/* Stats Grid */}
<div className="flex items-center justify-between gap-6">
{/* Total Points */}
<div className="flex flex-col gap-1">
<span className="text-body-small font-bold text-text-secondary dark:text-gray-300 leading-[150%]">
{t("points.yourTotalPoints")}
</span>
<span className="text-[48px] font-extrabold leading-[110%] tracking-[-0.01em] font-jetbrains bg-gradient-to-b from-[#111827] to-[#4b5563] bg-clip-text text-transparent">
2,450
</span>
</div>
{/* Rank and Timer Container */}
<div className="bg-bg-subtle dark:bg-gray-700 rounded-2xl border border-white/5 dark:border-gray-600 px-6 h-28 flex items-center gap-12">
{/* Global Rank */}
<div className="flex flex-col gap-1">
<span className="text-[10px] font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("points.globalRank")}
</span>
<span className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em]">
#8,204
</span>
<span className="text-[10px] font-regular text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
{t("points.topOfUsers")}
</span>
</div>
{/* Divider */}
<div className="w-px h-8 bg-border-normal dark:bg-gray-600" />
{/* Ends In */}
<div className="flex flex-col gap-1">
<span className="text-[10px] font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("points.endsIn")}
</span>
<span className="text-heading-h3 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em]">
12d 4h
</span>
<span className="text-[10px] font-regular text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
Feb 28
</span>
</div>
</div>
</div>
</div>
{/* Right Section - VIP Card */}
<VipCard
memberTier="Silver Member"
level={2}
currentPoints={2450}
totalPoints={3000}
nextTier="Gold"
pointsToNextTier={550}
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,64 @@
import Image from "next/image";
import { Button } from "@heroui/react";
import BorderedButton from "@/components/common/BorderedButton";
interface ReferralCodeCardProps {
code: string;
onCopy?: () => void;
onShare?: () => void;
}
export default function ReferralCodeCard({
code = "PR0T0-8821",
onCopy,
onShare,
}: ReferralCodeCardProps) {
return (
<div className="flex-[32] 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 gap-3 h-6">
<Image src="/icon0.svg" alt="" width={24} height={24} />
<span className="text-body-default font-bold text-text-primary dark:text-white leading-[150%]">
REFERRAL CODE
</span>
</div>
{/* Description */}
<p className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
Share your unique code to earn 10% commission on all friend activities.
</p>
{/* Code Display and Buttons */}
<div className="flex flex-col gap-4">
{/* Code Display Row */}
<div className="flex items-center gap-2">
{/* Code Container */}
<div className="flex-1 bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 px-4 py-3 h-[46px] flex items-center">
<span className="text-body-default font-bold text-text-primary dark:text-white leading-[150%] font-jetbrains">
{code}
</span>
</div>
{/* Copy Button */}
<Button
isIconOnly
onClick={onCopy}
className="bg-text-primary dark:bg-white rounded-xl w-[46px] h-[46px] min-w-[46px]"
>
<Image src="/icon1.svg" alt="Copy" width={20} height={20} />
</Button>
</div>
{/* Share Button */}
<BorderedButton
size="lg"
fullWidth
onClick={onShare}
isTheme
>
Share
</BorderedButton>
</div>
</div>
);
}

View File

@@ -0,0 +1,98 @@
import Image from "next/image";
interface RoleIndicator {
icon: string;
label: string;
current: number;
total: number;
}
interface TeamTVLCardProps {
currentTVL: string;
totalTVL: string;
progressPercent: number;
members: number;
roles: RoleIndicator[];
}
export default function TeamTVLCard({
currentTVL = "$8.5M",
totalTVL = "$10M",
progressPercent = 85,
members = 12,
roles = [
{ icon: "/icon-whale.svg", label: "Whales", current: 3, total: 3 },
{ icon: "/icon-trader.svg", label: "Traders", current: 0, total: 3 },
{ icon: "/icon-user.svg", label: "Users", current: 5, total: 3 },
],
}: TeamTVLCardProps) {
return (
<div className="flex-[32] bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-8 flex flex-col gap-6 relative">
{/* Header */}
<div className="flex items-center gap-3">
<Image src="/icon0.svg" alt="" width={24} height={24} />
<span className="text-body-default font-bold text-text-primary dark:text-white leading-[150%]">
Team TVL Reward
</span>
</div>
{/* Description */}
<p className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
Were you invited? Enter the code here to boost your base yield by +0.5%.
</p>
{/* Progress Section */}
<div className="flex flex-col gap-4">
{/* TVL Info */}
<div className="flex flex-col gap-1">
<div className="flex items-start justify-between">
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
CURRENT TEAM TVL
</span>
<span className="text-caption-tiny font-bold text-text-primary dark:text-white leading-[150%] tracking-[0.01em]">
{currentTVL} / {totalTVL}
</span>
</div>
{/* Progress Bar */}
<div className="relative w-full h-2 bg-[#f3f4f6] dark:bg-gray-700 rounded-full overflow-hidden">
<div
className="absolute left-0 top-0 h-full rounded-full"
style={{
width: `${progressPercent}%`,
background: "linear-gradient(90deg, rgba(20, 71, 230, 1) 0%, rgba(3, 43, 189, 1) 100%)"
}}
/>
</div>
</div>
{/* Role Indicators */}
<div className="flex items-center gap-2">
{roles.map((role, index) => (
<div
key={index}
className="flex-1 bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 px-4 py-2 flex flex-col items-center justify-center gap-0"
>
<div className="flex items-center justify-center h-4">
<Image src={role.icon} alt={role.label} width={16} height={16} />
</div>
<span className="text-[10px] font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{role.label}
</span>
<span className="text-caption-tiny font-bold text-text-primary dark:text-white leading-[150%] tracking-[0.01em]">
{role.current}/{role.total}
</span>
</div>
))}
</div>
</div>
{/* Members Badge */}
<div className="absolute right-6 top-8 bg-[#e1f8ec] dark:bg-green-900/30 border border-[#b8ecd2] dark:border-green-700 rounded-full px-3 py-1">
<span className="text-[10px] font-bold text-[#10b981] dark:text-green-400 leading-[150%] tracking-[0.01em]">
{members} Members
</span>
</div>
</div>
);
}

View File

@@ -0,0 +1,129 @@
import Image from "next/image";
interface Performer {
rank: number;
address: string;
points: string;
}
const mockPerformers: Performer[] = [
{ rank: 1, address: "0x8A...2291", points: "52k" },
{ rank: 2, address: "0x11...F92A", points: "48k" },
{ rank: 3, address: "0x9C...3310", points: "41k" },
{ rank: 4, address: "0x42...119A", points: "32k" },
{ rank: 5, address: "0x77...882B", points: "28k" },
];
export default function TopPerformers() {
const getRowStyle = (rank: number) => {
switch (rank) {
case 1:
return {
bg: "#fff5ef",
borderColor: "#ff6900",
rankColor: "#ff6900",
textColor: "#111827",
pointsSize: "text-body-large",
};
case 2:
return {
bg: "#fffbf5",
borderColor: "#ffb933",
rankColor: "#ffb933",
textColor: "#111827",
pointsSize: "text-body-large",
};
case 3:
return {
bg: "#f3f4f6",
borderColor: "#6b7280",
rankColor: "#6b7280",
textColor: "#111827",
pointsSize: "text-body-large",
};
default:
return {
bg: "transparent",
borderColor: "transparent",
rankColor: "#d1d5db",
textColor: "#9ca1af",
pointsSize: "text-body-small",
};
}
};
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 h-full">
{/* Header */}
<div className="border-b border-[#f1f5f9] dark:border-gray-700 px-6 pt-8 pb-8">
<h3 className="text-heading-h4 font-bold text-text-primary dark:text-white leading-[140%]">
Top Performers
</h3>
</div>
{/* Performers List */}
<div className="flex-1 flex flex-col">
{mockPerformers.map((performer) => {
const style = getRowStyle(performer.rank);
const isTopThree = performer.rank <= 3;
const height = performer.rank <= 3 ? "h-[72px]" : "h-[68px]";
return (
<div
key={performer.rank}
className={`flex items-center justify-between px-6 ${height} border-l-4`}
style={{
backgroundColor: style.bg,
borderLeftColor: style.borderColor,
}}
>
{/* Left - Rank and Address */}
<div className="flex items-center gap-5">
{/* Rank Number */}
<span
className={`font-black italic leading-[133%] ${
isTopThree ? "text-2xl tracking-[0.07px]" : "text-lg tracking-[-0.44px]"
}`}
style={{ color: style.rankColor }}
>
{performer.rank}
</span>
{/* Address */}
<span
className="text-body-small font-bold font-jetbrains leading-[150%]"
style={{ color: style.textColor }}
>
{performer.address}
</span>
</div>
{/* Right - Points */}
<span
className={`${style.pointsSize} font-bold leading-[150%]`}
style={{
color: isTopThree ? "#111827" : "#4b5563",
}}
>
{performer.points}
</span>
</div>
);
})}
</div>
{/* Footer - My Rank */}
<div className="border-t border-border-gray dark:border-gray-700 px-6 py-6 flex items-center justify-between">
<div className="flex items-center gap-1">
<Image src="/icon-chart.svg" alt="Chart" width={16} height={16} />
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
MY RANK
</span>
</div>
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
2,450.00
</span>
</div>
</div>
);
}

View File

@@ -0,0 +1,109 @@
import Image from "next/image";
interface VipCardProps {
memberTier: string;
level: number;
currentPoints: number;
totalPoints: number;
nextTier: string;
pointsToNextTier: number;
}
export default function VipCard({
memberTier = "Silver Member",
level = 2,
currentPoints = 2450,
totalPoints = 3000,
nextTier = "Gold",
pointsToNextTier = 550,
}: VipCardProps) {
const progressPercent = (currentPoints / totalPoints) * 100;
const progressBarPercent = Math.min(progressPercent * 0.47, 38.4); // 根据原型调整比例
return (
<div className="w-[340px] h-[198px] relative rounded-xl overflow-hidden" style={{ background: "linear-gradient(180deg, #484848 0%, #1e1e1e 100%)" }}>
{/* Top Left - Member Info */}
<div className="absolute left-6 top-[33px]">
<div className="flex flex-col gap-1">
<span className="text-body-default font-bold text-white leading-[150%]">
{memberTier}
</span>
<span className="text-body-small font-bold text-text-tertiary dark:text-gray-400 leading-[150%]">
Current Tier
</span>
</div>
</div>
{/* Top Right - VIP Badge */}
<div className="absolute right-6 top-[26px]">
<div className="relative w-[55px] h-[28px]">
{/* Badge Layers */}
<div className="absolute left-[0.45px] top-[6px]">
<Image src="/polygon-30.svg" alt="" width={24} height={15} />
</div>
<div className="absolute left-[28.55px] top-[6px]">
<Image src="/polygon-40.svg" alt="" width={24} height={15} />
</div>
<div className="absolute left-[4px] top-[3px]">
<Image src="/polygon-20.svg" alt="" width={21} height={25} />
</div>
<div className="absolute left-[4px] top-[3px]">
<Image src="/mask-group1.svg" alt="" width={21} height={25} />
</div>
{/* Decorative circles */}
<div className="absolute left-[12.84px] top-0 w-[3.32px] h-[3.32px] rounded-full" style={{ background: "linear-gradient(180deg, #ffcd1d 0%, #ff971d 100%)" }} />
<div className="absolute left-0 top-[5.14px] w-[2.72px] h-[2.72px] rounded-full" style={{ background: "linear-gradient(180deg, #ffcd1d 0%, #ff971d 100%)" }} />
<div className="absolute left-[26.2px] top-[5.14px] w-[2.72px] h-[2.72px] rounded-full" style={{ background: "linear-gradient(180deg, #ffcd1d 0%, #ff971d 100%)" }} />
{/* Level Badge */}
<div className="absolute left-[24px] top-[9.62px]">
<Image src="/rectangle-420.svg" alt="" width={31} height={12} />
<span className="absolute left-[9.24px] top-0 text-[8.5px] font-semibold leading-[120%] uppercase" style={{ color: "#a07400" }}>
LV{level}
</span>
</div>
</div>
</div>
{/* Bottom - Progress Section */}
<div className="absolute left-1/2 -translate-x-1/2 top-[107px] w-[280px] flex flex-col gap-1">
{/* Progress Label */}
<div className="flex items-center justify-between">
<span className="text-caption-tiny font-regular leading-[150%] tracking-[0.01em]" style={{ color: "#d1d5db" }}>
Progress
</span>
<span className="text-caption-tiny font-bold text-white leading-[150%] tracking-[0.01em]">
{currentPoints} / {totalPoints}
</span>
</div>
{/* Progress Bar */}
<div className="relative w-full h-1 rounded-[5px]" style={{ background: "#343434" }}>
<div
className="absolute left-0 top-0 h-full rounded-[5px]"
style={{
width: `${progressBarPercent}%`,
background: "linear-gradient(90deg, rgba(255, 255, 255, 1) 95.15%, rgba(255, 255, 255, 0) 100%)"
}}
/>
<div
className="absolute w-[10px] h-[10px] rounded-full bg-white -top-[3px]"
style={{
left: `${Math.max(0, progressBarPercent - 3)}%`,
filter: "blur(3.42px)"
}}
/>
</div>
</div>
{/* Bottom - Next Tier */}
<div className="absolute left-6 top-[163px] flex items-center gap-1">
<span className="text-caption-tiny font-regular text-white leading-[150%] tracking-[0.01em]">
{pointsToNextTier} points to <span className="font-bold">{nextTier}</span>
</span>
<Image src="/icon-arrow-gold.svg" alt="" width={16} height={16} />
</div>
</div>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View File

@@ -0,0 +1,192 @@
"use client";
import { useApp } from "@/contexts/AppContext";
import { useEffect, useRef } from "react";
import * as echarts from "echarts";
export default function AssetDistribution() {
const { t } = useApp();
const chartRef = useRef<HTMLDivElement>(null);
const legends = [
{ nameKey: "transparency.fixedIncome", color: "bg-[#1447e6]" },
{ nameKey: "transparency.alternativeAssets", color: "bg-[#10b981]" },
{ nameKey: "transparency.alternativeCredit", color: "bg-[#ff6900]" },
];
useEffect(() => {
if (!chartRef.current) return;
const chart = echarts.init(chartRef.current, null, {
renderer: "svg",
});
const option = {
series: [
{
type: "pie",
radius: ["45%", "70%"],
center: ["50%", "50%"],
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 0,
borderColor: "transparent",
borderWidth: 0,
},
label: {
show: true,
position: "outside",
alignTo: "none",
formatter: (params: any) => {
return `{name|${params.name}}\n{line|}\n{value|${params.data.displayValue}}`;
},
rich: {
name: {
fontSize: 14,
fontWeight: "bold",
color: "#0f172b",
lineHeight: 20,
},
line: {
height: 1,
width: 100,
backgroundColor: "transparent",
},
value: {
fontSize: 12,
fontWeight: "500",
lineHeight: 18,
},
},
},
labelLine: {
show: true,
length: 20,
length2: 60,
lineStyle: {
width: 1,
},
},
emphasis: {
disabled: true,
},
data: [
{
value: 52.7,
name: t("transparency.usTreasuryBills"),
displayValue: "$140M (30.1%)",
itemStyle: {
color: "#1447e6",
},
label: {
rich: {
value: {
color: "#1447e6",
},
},
},
labelLine: {
lineStyle: {
color: "#1447e6",
},
},
},
{
value: 30.1,
name: t("transparency.realEstate"),
displayValue: "$140M (30.1%)",
itemStyle: {
color: "#10b981",
},
label: {
rich: {
value: {
color: "#10b981",
},
},
},
labelLine: {
lineStyle: {
color: "#10b981",
},
},
},
{
value: 17.2,
name: t("transparency.privateCredit"),
displayValue: "$80M (17.2%)",
itemStyle: {
color: "#ff6900",
},
label: {
rich: {
value: {
color: "#ff6900",
},
},
},
labelLine: {
lineStyle: {
color: "#ff6900",
},
},
},
],
animationType: "expansion",
animationEasing: "cubicOut",
animationDuration: 1000,
},
],
};
chart.setOption(option);
const handleResize = () => {
chart.resize();
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
chart.dispose();
};
}, [t]);
return (
<div className="flex-1 bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-6 flex flex-col gap-6">
{/* Header */}
<div className="flex flex-col gap-0">
<h3 className="text-[20px] font-bold text-text-primary dark:text-white leading-[140%]">
{t("transparency.assetDistribution")}
</h3>
<p className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("transparency.distributionSubtitle")}
</p>
</div>
{/* Pie Chart Section */}
<div className="flex flex-col gap-3">
{/* ECharts Pie Chart */}
<div className="relative h-[210px] w-full">
<div ref={chartRef} className="w-full h-full" />
{/* Center 100 */}
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-[40px] font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.01em] pointer-events-none">
100
</div>
</div>
{/* Legends */}
<div className="flex items-center justify-center gap-6">
{legends.map((legend, index) => (
<div key={index} className="flex items-center gap-2">
<div className={`w-3 h-3 rounded-full ${legend.color}`} />
<span className="text-body-small font-regular text-text-primary dark:text-white leading-[150%]">
{t(legend.nameKey)}
</span>
</div>
))}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,80 @@
"use client";
import Image from "next/image";
import { useApp } from "@/contexts/AppContext";
export default function GeographicAllocation() {
const { t } = useApp();
const regions = [
{
countryKey: "transparency.unitedStates",
regionKey: "transparency.northAmerica",
value: "$305,000,000",
percentage: "65.6%",
flag: "/lr0.svg",
},
{
countryKey: "transparency.hongKong",
regionKey: "transparency.asiaPacific",
value: "$160,000,000",
percentage: "34.4%",
flag: "/container14.svg",
},
];
return (
<div className="flex-1 bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-6 flex flex-col gap-6">
{/* Header */}
<div className="flex flex-col gap-0">
<h3 className="text-[20px] font-bold text-text-primary dark:text-white leading-[140%]">
{t("transparency.geographicAllocation")}
</h3>
<p className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("transparency.geographicSubtitle")}
</p>
</div>
{/* Region Cards */}
<div className="flex flex-col gap-4">
{regions.map((region, index) => (
<div
key={index}
className="bg-bg-subtle dark:bg-gray-700 rounded-2xl border border-border-gray dark:border-gray-600 p-4 flex items-center justify-between"
>
{/* Left - Flag and Location */}
<div className="flex items-center gap-3">
<div className="w-12 h-12 flex-shrink-0 flex items-center justify-center">
<Image
src={region.flag}
alt={t(region.countryKey)}
width={48}
height={48}
className="w-full h-full object-contain"
/>
</div>
<div className="flex flex-col">
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
{t(region.countryKey)}
</span>
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t(region.regionKey)}
</span>
</div>
</div>
{/* Right - Value and Percentage */}
<div className="flex flex-col items-end">
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
{region.value}
</span>
<span className="text-caption-tiny font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{region.percentage}
</span>
</div>
</div>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,170 @@
"use client";
import Image from "next/image";
import { useApp } from "@/contexts/AppContext";
export default function HoldingsTable() {
const { t } = useApp();
const holdings = [
{
custodian: t("transparency.morganStanley"),
custodianType: t("transparency.primeBroker"),
assetType: t("transparency.usEquityPortfolio"),
maturityDate: "05 Feb 2026",
daysRemaining: `77 ${t("transparency.days")}`,
value: "$12,500,000.00",
status: t("transparency.verified"),
},
{
custodian: t("transparency.morganStanley"),
custodianType: t("transparency.primeBroker"),
assetType: t("transparency.usEquityPortfolio"),
maturityDate: "05 Feb 2026",
daysRemaining: `77 ${t("transparency.days")}`,
value: "$12,500,000.00",
status: t("transparency.verified"),
},
{
custodian: t("transparency.morganStanley"),
custodianType: t("transparency.primeBroker"),
assetType: t("transparency.usEquityPortfolio"),
maturityDate: "05 Feb 2026",
daysRemaining: `77 ${t("transparency.days")}`,
value: "$12,500,000.00",
status: t("transparency.verified"),
},
{
custodian: t("transparency.morganStanley"),
custodianType: t("transparency.primeBroker"),
assetType: t("transparency.usEquityPortfolio"),
maturityDate: "05 Feb 2026",
daysRemaining: `77 ${t("transparency.days")}`,
value: "$12,500,000.00",
status: t("transparency.verified"),
},
];
return (
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 overflow-hidden">
{/* Header */}
<div className="bg-bg-surface dark:bg-gray-800 border-b border-border-gray dark:border-gray-700 px-6 py-6 flex items-center justify-between">
<div className="flex flex-col gap-1">
<h2 className="text-[20px] font-bold text-text-primary dark:text-white leading-[140%]">
{t("transparency.rwaHoldings")}
</h2>
<p className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("transparency.rwaSubtitle")}
</p>
</div>
<div className="flex items-center gap-2 rounded-full">
<Image src="/component-10.svg" alt="" width={16} height={16} />
<span className="text-caption-tiny font-regular text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
{t("transparency.lastUpdated")}: 2 {t("transparency.minutesAgo")}
</span>
</div>
</div>
{/* Table */}
<div className="overflow-auto">
{/* Table Header */}
<div className="flex bg-bg-subtle dark:bg-gray-700 border-b border-border-gray dark:border-gray-600">
<div className="flex-1 px-6 py-4">
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
{t("transparency.custodian")}
</span>
</div>
<div className="flex-1 px-6 py-4">
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
{t("transparency.assetType")}
</span>
</div>
<div className="flex-1 px-6 py-4">
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
{t("transparency.maturity")}
</span>
</div>
<div className="flex-1 px-6 py-4">
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
{t("transparency.valueUsd")}
</span>
</div>
<div className="flex-1 px-6 py-4">
<span className="text-caption-tiny font-medium text-text-secondary dark:text-gray-300 leading-[150%] tracking-[0.01em]">
{t("transparency.status")}
</span>
</div>
</div>
{/* Table Body */}
{holdings.map((holding, index) => (
<div
key={index}
className={`flex items-center ${
index !== holdings.length - 1
? "border-b border-border-gray dark:border-gray-600"
: ""
}`}
>
{/* Custodian */}
<div className="flex-1 px-6 py-6 flex items-center gap-3">
<div className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 bg-gradient-to-br from-[#ff8904] to-[#f54900] shadow-[0px_2.01px_3.02px_-2.01px_rgba(255,105,0,0.2)]">
<span className="text-[10px] font-bold text-white leading-[14px] tracking-[-0.23px]">
GY
</span>
</div>
<div className="flex flex-col">
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
{holding.custodian}
</span>
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{holding.custodianType}
</span>
</div>
</div>
{/* Asset Type */}
<div className="flex-1 px-6 py-6">
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
{holding.assetType}
</span>
</div>
{/* Maturity */}
<div className="flex-1 px-6 py-6 flex flex-col">
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
{holding.maturityDate}
</span>
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
({holding.daysRemaining})
</span>
</div>
{/* Value */}
<div className="flex-1 px-6 py-6">
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
{holding.value}
</span>
</div>
{/* Status */}
<div className="flex-1 px-6 py-6">
<div className="inline-flex items-center gap-0.5 bg-[#e1f8ec] dark:bg-green-900/30 border border-[#b8ecd2] dark:border-green-700 rounded-full px-3 py-1">
<Image
src="/component-11.svg"
alt=""
width={14}
height={14}
/>
<span className="text-[10px] font-bold text-[#10b981] dark:text-green-400 leading-[15px]">
{holding.status}
</span>
</div>
</div>
</div>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,137 @@
"use client";
import { useApp } from "@/contexts/AppContext";
interface CircleProgressProps {
percentage: number;
color: string;
percentageText: string;
}
function CircleProgress({ percentage, color, percentageText }: CircleProgressProps) {
const size = 50;
const strokeWidth = 4;
const radius = (size - strokeWidth) / 2;
const circumference = radius * 2 * Math.PI;
const offset = circumference - (percentage / 100) * circumference;
return (
<div className="relative flex items-center justify-center flex-shrink-0" style={{ width: size, height: size }}>
<svg width={size} height={size} className="transform -rotate-90">
{/* 背景圆环 - 浅色轨道 */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="transparent"
stroke="currentColor"
strokeWidth={strokeWidth}
className="text-slate-200/30 dark:text-gray-600"
/>
{/* 进度圆环 - 填充部分 */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="transparent"
stroke={color}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
style={{
strokeDashoffset: offset,
transition: 'stroke-dashoffset 1s ease-in-out',
strokeLinecap: 'round'
}}
/>
</svg>
{/* 中间的百分比文字 */}
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-[10px] font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{percentageText}
</span>
</div>
</div>
);
}
export default function TransparencyStats() {
const { t } = useApp();
const stats = [
{
labelKey: "transparency.totalUsdcSupply",
value: "$200.4M",
percentage: 52.7,
percentageText: "52.7%",
percentageColor: "text-[#1447e6]",
circleColor: "#1447e6",
},
{
labelKey: "transparency.utilization",
value: "$140.0M",
percentage: 30.1,
percentageText: "30.1%",
percentageColor: "text-[#ff6900]",
circleColor: "#ff6900",
},
{
labelKey: "transparency.activeLoans",
value: "$80.0M",
percentage: 17.2,
percentageText: "17.2%",
percentageColor: "text-[#10b981]",
circleColor: "#10b981",
},
];
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">
{/* Total Reserves */}
<div className="flex flex-col gap-2">
<span className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t("transparency.totalReserves")}
</span>
<span className="text-heading-h2 font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.01em]">
$465,000,000
</span>
</div>
{/* Stats Cards */}
<div className="flex gap-4">
{stats.map((stat, index) => (
<div
key={index}
className="flex-1 bg-bg-subtle dark:bg-gray-700 rounded-2xl border border-border-gray dark:border-gray-600 p-4 flex items-center justify-between"
>
{/* Left - Text Info */}
<div className="flex flex-col gap-2">
{/* Label */}
<span className="text-[10px] font-bold text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
{t(stat.labelKey)}
</span>
{/* Value */}
<span className="text-[20px] font-bold text-text-primary dark:text-white leading-[130%] tracking-[-0.005em]">
{stat.value}
</span>
{/* Percentage */}
<span className={`text-[12px] font-medium leading-[150%] tracking-[0.01em] ${stat.percentageColor}`}>
{stat.percentageText}
</span>
</div>
{/* Right - Circle Chart */}
<CircleProgress
percentage={stat.percentage}
color={stat.circleColor}
percentageText={stat.percentageText}
/>
</div>
))}
</div>
</div>
);
}