大更新

This commit is contained in:
YoRHa
2026-02-04 12:56:06 +08:00
parent 098a91f2ac
commit 4f2ff3f3ba
535 changed files with 11908 additions and 15540 deletions

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