From b387038c8c4a0e59e2560a309d4436bbda47276b Mon Sep 17 00:00:00 2001 From: Harshal Dulera Date: Sat, 7 Dec 2024 17:54:23 +0530 Subject: [PATCH] feat: implement leaderboard with dynamic data and custom styling --- packages/nextjs/.gitignore | 3 +- packages/nextjs/app/leaderboard/page.tsx | 182 +++++++++++++-------- packages/nextjs/app/leaderboard/queries.ts | 12 +- packages/nextjs/tailwind.config.js | 96 +++++------ 4 files changed, 169 insertions(+), 124 deletions(-) diff --git a/packages/nextjs/.gitignore b/packages/nextjs/.gitignore index 394202c..cf8851b 100644 --- a/packages/nextjs/.gitignore +++ b/packages/nextjs/.gitignore @@ -34,4 +34,5 @@ yarn-error.log* .env.production.local # typescript -*.tsbuildinfo \ No newline at end of file +*.tsbuildinfo + diff --git a/packages/nextjs/app/leaderboard/page.tsx b/packages/nextjs/app/leaderboard/page.tsx index 79a29f2..496d3a6 100644 --- a/packages/nextjs/app/leaderboard/page.tsx +++ b/packages/nextjs/app/leaderboard/page.tsx @@ -5,106 +5,134 @@ import { LEADERBOARD } from "./queries"; import { useQuery } from "@apollo/client"; import { motion } from "framer-motion"; -interface LeaderboardEntry { - rank: number; +interface Transfer { + blockTimestamp: string; + blockNumber: string; + from: string; + id: string; + tokenId: string; + transactionHash: string; + to: string; +} + +interface TransferCounts { address: string; - score: number; - eth: number; + count: number; + tokenIds: string[]; } export default function Leaderboard() { const { data, loading, error } = useQuery(LEADERBOARD); - if (loading) return

Loading...

; - if (error) return

Error: {error.message}

; - console.log(data); - const leaderboardData: LeaderboardEntry[] = [ - { rank: 1, address: "0x1234...5678", score: 1500, eth: 2.5 }, - { rank: 2, address: "0x8765...4321", score: 1450, eth: 2.45 }, - { rank: 3, address: "0x9876...1234", score: 1400, eth: 2.4 }, - { rank: 4, address: "0x5432...8765", score: 1350, eth: 2.35 }, - { rank: 5, address: "0x2468...1357", score: 1300, eth: 2.3 }, - ]; + if (loading) { + return ( +
+
Loading Leaderboard...
+
+ ); + } - // Generate additional entries with predictable values - const extendedData = [...leaderboardData]; - for (let i = leaderboardData.length + 1; i <= 50; i++) { - extendedData.push({ - rank: i, - address: `0x${i.toString().padStart(4, "0")}...${(1000 - i).toString().padStart(4, "0")}`, - score: Math.max(0, 1500 - i * 20), - eth: Number((2.5 - i * 0.05).toFixed(2)), - }); + if (error) { + return ( +
+
Error: {error.message}
+
+ ); } + // Process transfers data to create leaderboard + const addressCounts = data.transfers.reduce((acc: { [key: string]: TransferCounts }, transfer: Transfer) => { + // Count both sending and receiving + [transfer.from, transfer.to].forEach(address => { + if (address !== "0x0000000000000000000000000000000000000000") { // Exclude zero address + if (!acc[address]) { + acc[address] = { + address, + count: 0, + tokenIds: [] + }; + } + acc[address].count += 1; + if (!acc[address].tokenIds.includes(transfer.tokenId)) { + acc[address].tokenIds.push(transfer.tokenId); + } + } + }); + return acc; + }, {}); + + // Convert to array and sort by count + const leaderboardData = Object.values(addressCounts) + .sort((a, b) => b.count - a.count) + .map((item, index) => ({ + ...item, + rank: index + 1 + })); + return (
- {/* Fixed Hero Section */} -
+ {/* Hero Section */} +
-

Top Stakers

-

- The highest stakers on our platform are showcased here. Join them by staking your ETH! +

Top NFT Traders

+

+ Most active addresses in NFT transfers on our platform

- {/* Scrollable Leaderboard Section */} + {/* Leaderboard Section */}
{/* Sticky Header */} -
-
+
+
Rank & Address -
- Score - Staked +
+ Transfers + NFTs
{/* Scrollable Content */}
- {extendedData.map((entry, index) => ( + {leaderboardData.map((entry, index) => (
-
- {entry.rank} +
+ {entry.rank}
- {entry.address} + + {entry.address.slice(0, 6)}...{entry.address.slice(-4)} +
-
-
- {entry.score} -
-
- {entry.eth} ETH -
+
+ + {entry.count} + + + {entry.tokenIds.length} +
@@ -112,6 +140,30 @@ export default function Leaderboard() {
+ + {/* Stats Section */} +
+
+
+

+ {leaderboardData.length} +

+

Total Traders

+
+
+

+ {data.transfers.length} +

+

Total Transfers

+
+
+

+ {new Set(data.transfers.map((t: Transfer) => t.tokenId)).size} +

+

Unique NFTs

+
+
+
); -} +} \ No newline at end of file diff --git a/packages/nextjs/app/leaderboard/queries.ts b/packages/nextjs/app/leaderboard/queries.ts index 9585d19..a959ffb 100644 --- a/packages/nextjs/app/leaderboard/queries.ts +++ b/packages/nextjs/app/leaderboard/queries.ts @@ -2,10 +2,14 @@ import { gql } from "@apollo/client"; export const LEADERBOARD = gql` query LeaderBoard { - userStakes(orderBy: totalStaked, orderDirection: desc) { - totalStaked - user + transfers { + blockTimestamp + blockNumber + from id + tokenId + transactionHash + to } } -`; +`; \ No newline at end of file diff --git a/packages/nextjs/tailwind.config.js b/packages/nextjs/tailwind.config.js index 8be8fb1..d091dca 100644 --- a/packages/nextjs/tailwind.config.js +++ b/packages/nextjs/tailwind.config.js @@ -6,56 +6,40 @@ module.exports = { "./utils/**/*.{js,ts,jsx,tsx}", ], plugins: [require("daisyui")], - darkTheme: "dark", - darkMode: ["selector", "[data-theme='dark']"], + darkTheme: "fitpal", + darkMode: ["selector", "[data-theme='fitpal']"], daisyui: { themes: [ { - light: { - primary: '#000001', - 'primary-content': '#fbf8fe', - secondary: '#2d2c2e', - 'secondary-content': '#fbf8fe', - accent: '#b6b7bb', - 'accent-content': '#000001', - neutral: '#a3a2a7', - 'neutral-content': '#fbf8fe', - 'base-100': '#fbf8fe', - 'base-200': '#2d2c2e', - 'base-300': '#000001', - 'base-content': '#000001', - info: '#b6b7bb', - success: '#34EEB6', - warning: '#FFCF72', - error: '#FF8863', - '--rounded-btn': '9999rem', - '.tooltip': { '--tooltip-tail': '6px' }, - '.link': { textUnderlineOffset: '2px' }, - '.link:hover': { opacity: '80%' } - }, - }, - { - dark: { - primary: '#000001', // Full Black - 'primary-content': '#fbf8fe', // White - secondary: '#2d2c2e', // Med Grey - 'secondary-content': '#fbf8fe', // White - accent: '#b6b7bb', // Inner Grey - 'accent-content': '#fbf8fe', // White - neutral: '#a3a2a7', // Mid Grey - 'neutral-content': '#fbf8fe', // White - 'base-100': '#2d2c2e', // Med Grey - 'base-200': '#000001', // Full Black - 'base-300': '#2d2c2e', // Med Grey - 'base-content': '#fbf8fe', // White - info: '#b6b7bb', // Inner Grey - success: '#34EEB6', - warning: '#FFCF72', - error: '#FF8863', - '--rounded-btn': '9999rem', - '.tooltip': { '--tooltip-tail': '6px', '--tooltip-color': 'oklch(var(--p))' }, - '.link': { textUnderlineOffset: '2px' }, - '.link:hover': { opacity: '80%' } + fitpal: { + primary: '#9FFF5B', // Neon Green + 'primary-content': '#000000', + secondary: '#1C1C1E', // Dark Grey + 'secondary-content': '#ffffff', + accent: '#9FFF5B', // Neon Green + 'accent-content': '#000000', + neutral: '#2C2C2E', // Medium Grey + 'neutral-content': '#ffffff', + 'base-100': '#000000', // Pure Black + 'base-200': '#1C1C1E', // Dark Grey + 'base-300': '#2C2C2E', // Medium Grey + 'base-content': '#ffffff', + info: '#9FFF5B', // Neon Green + success: '#9FFF5B', // Neon Green + warning: '#FFD426', // Warning Yellow + error: '#FF4545', // Error Red + + // Custom properties + '--rounded-btn': '0.75rem', + '--border-btn': '1px', + '.glass': { + 'background': 'rgba(28,28,30,0.8)', + 'backdrop-filter': 'blur(8px)', + }, + '.chart-line': { + 'stroke': '#9FFF5B', + 'filter': 'drop-shadow(0 0 2px #9FFF5B)', + }, }, }, ], @@ -63,14 +47,18 @@ module.exports = { theme: { extend: { colors: { - 'full-black': '#000001', - 'med-grey': '#2d2c2e', - 'inner-grey': '#b6b7bb', - 'mid-grey': '#a3a2a7', - 'pure-white': '#fbf8fe', + 'neon-green': '#9FFF5B', + 'dark-surface': '#1C1C1E', + 'darker-surface': '#000000', + 'medium-surface': '#2C2C2E', + }, + boxShadow: { + 'neon-glow': '0 0 10px rgba(159, 255, 91, 0.3)', + 'neon-glow-strong': '0 0 15px rgba(159, 255, 91, 0.5)', + }, + backgroundImage: { + 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', }, - boxShadow: { center: '0 0 12px -2px rgb(0 0 0 / 0.05)' }, - animation: { 'pulse-fast': 'pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite' } }, }, }; \ No newline at end of file