大更新

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