initial commit
This commit is contained in:
190
components/points/ActivityHistory.tsx
Normal file
190
components/points/ActivityHistory.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
49
components/points/BindInviteCard.tsx
Normal file
49
components/points/BindInviteCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
117
components/points/DepositCard.tsx
Normal file
117
components/points/DepositCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
86
components/points/EarnOpportunityCard.tsx
Normal file
86
components/points/EarnOpportunityCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
91
components/points/PointsCards.tsx
Normal file
91
components/points/PointsCards.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
115
components/points/PointsDashboard.tsx
Normal file
115
components/points/PointsDashboard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
64
components/points/ReferralCodeCard.tsx
Normal file
64
components/points/ReferralCodeCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
98
components/points/TeamTVLCard.tsx
Normal file
98
components/points/TeamTVLCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
129
components/points/TopPerformers.tsx
Normal file
129
components/points/TopPerformers.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
109
components/points/VipCard.tsx
Normal file
109
components/points/VipCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user