1+ "use client" ;
2+ import { useState } from "react" ;
3+ import { api } from "~/trpc/react" ;
4+ import { useSession } from "next-auth/react" ;
5+ type Week = {
6+ id ?: number | string ;
7+ number ?: number ;
8+ title ?: string ;
9+ } ;
110
11+ type LeaderboardEntry = {
12+ username : string ;
13+ completedWarmup : number ;
14+ completedMedium : number ;
15+ completedHarder : number ;
16+ completedInsane : number ;
17+ total : number ;
18+ } ;
219
320const Leaderboard = ( ) => {
21+ // Get current username
22+ const { data : session } = useSession ( ) ;
23+ const currentUsername = session ?. user ?. name ?? "" ;
24+
25+ const [ selected , setSelected ] = useState ( "all" ) ;
26+
27+ // Fetch weeks for the slider
28+ const { data : weeksData , isLoading : weeksLoading } =
29+ api . week . getWeeks . useQuery ( ) ;
30+
31+ // Fetch leaderboard data
32+ const {
33+ data : leaderboardData ,
34+ isLoading : leaderboardLoading ,
35+ error : leaderboardError ,
36+ } = selected === "all"
37+ ? api . leaderboard . getAll . useQuery ( )
38+ : api . leaderboard . getByWeek . useQuery ( selected ) ;
39+
40+ if ( weeksLoading || leaderboardLoading ) {
441 return (
5- < div >
6- Hello
42+ < div className = "mx-auto mt-12 max-w-3xl" >
43+ < h1 className = "mb-8 text-center text-3xl font-extrabold tracking-tight text-white drop-shadow" >
44+ Leaderboard
45+ </ h1 >
46+ < div className = "mb-6 flex justify-center" >
47+ < div className = "inline-flex rounded-full bg-neutral-800 p-1" >
48+ < span className = "animate-pulse rounded-full bg-neutral-700 px-5 py-2 font-semibold text-gray-100" >
49+ Loading...
50+ </ span >
51+ </ div >
52+ </ div >
53+ < div className = "mb-2 flex w-full flex-row justify-between rounded-t-2xl bg-neutral-800 px-8 py-3 text-lg font-semibold text-white" >
54+ < span className = "w-1/4 text-left" > User</ span >
55+ < span className = "w-1/8 text-center" > Warmup</ span >
56+ < span className = "w-1/8 text-center" > Medium</ span >
57+ < span className = "w-1/8 text-center" > Harder</ span >
58+ < span className = "w-1/8 text-center" > Insane</ span >
59+ < span className = "w-1/8 text-center" > Total</ span >
60+ </ div >
61+ </ div >
62+ ) ;
63+ }
64+
65+ if ( leaderboardError ) return < div > Error loading leaderboard</ div > ;
66+
67+ const weekOptions = [
68+ { id : "all" , label : "Global" } ,
69+ ...( weeksData
70+ ? weeksData . map ( ( w : Week ) => ( {
71+ id : ( w . id ?? w . number ?? "" ) . toString ( ) ,
72+ label : w . title ?? `Week ${ w . number } ` ,
73+ } ) )
74+ : [ ] ) ,
75+ ] ;
76+
77+ const sorted = [ ...( ( leaderboardData as LeaderboardEntry [ ] ) ?? [ ] ) ] . sort (
78+ ( a , b ) => b . total - a . total ,
79+ ) ;
80+
81+ let lastTotal : number | null = null ;
82+ let lastPlace = 0 ;
83+
84+ const sortedWithPlace = sorted . map ( ( row : LeaderboardEntry , i ) => {
85+ if ( row . total !== lastTotal ) {
86+ lastPlace = i + 1 ;
87+ lastTotal = row . total ;
88+ }
89+ return { ...row , place : lastPlace } ;
90+ } ) ;
91+
92+ return (
93+ < div className = "mx-auto mt-12 max-w-3xl" >
94+ < h1 className = "mb-8 text-center text-3xl font-extrabold tracking-tight text-white drop-shadow" >
95+ Leaderboard
96+ </ h1 >
97+ < div className = "mb-6 flex justify-center" >
98+ < div className = "inline-flex rounded-full bg-neutral-800 p-1" >
99+ { weekOptions . map ( ( w ) => (
100+ < button
101+ key = { w . id }
102+ onClick = { ( ) => setSelected ( w . id ) }
103+ className = { `rounded-full px-5 py-2 font-semibold transition-all ${
104+ selected === w . id
105+ ? "bg-white text-neutral-900 shadow"
106+ : "text-white hover:bg-neutral-700"
107+ } `}
108+ >
109+ { w . label }
110+ </ button >
111+ ) ) }
7112 </ div >
8- )
9- }
113+ </ div >
114+ < div className = "mb-2 flex w-full flex-row justify-between rounded-t-2xl bg-neutral-800 px-8 py-3 text-lg font-semibold text-white" >
115+ < span className = "w-1/4 text-left" > User</ span >
116+ < span className = "w-1/8 text-center" > Warmup</ span >
117+ < span className = "w-1/8 text-center" > Medium</ span >
118+ < span className = "w-1/8 text-center" > Harder</ span >
119+ < span className = "w-1/8 text-center" > Insane</ span >
120+ < span className = "w-1/8 text-center" > Total</ span >
121+ </ div >
122+ < ol className = "flex flex-col gap-4" >
123+ { sortedWithPlace . map ( ( row , i ) => (
124+ < li
125+ key = { i }
126+ className = { `flex w-full flex-row items-center justify-between rounded-2xl px-8 py-4 text-white shadow-lg transition-all ${ row . place !== 1 && row . username !== currentUsername ? "bg-neutral-700" : "" } ${ row . place === 1 ? "scale-[1.03] bg-gray-600 font-bold" : "" } ${ row . username === currentUsername ? "bg-green-600 font-bold" : "" } ` }
127+ >
128+ < div className = "flex w-1/4 items-center gap-3" >
129+ < span className = "w-8 text-center text-xl font-semibold text-gray-400" >
130+ { row . place }
131+ </ span >
132+ < span className = "break-words text-base font-medium leading-tight" >
133+ { row . username }
134+ </ span >
135+ </ div >
136+ < span className = "w-1/8 text-center text-lg font-bold" >
137+ { row . completedWarmup }
138+ </ span >
139+ < span className = "w-1/8 text-center text-lg font-bold" >
140+ { row . completedMedium }
141+ </ span >
142+ < span className = "w-1/8 text-center text-lg font-bold" >
143+ { row . completedHarder }
144+ </ span >
145+ < span className = "w-1/8 text-center text-lg font-bold" >
146+ { row . completedInsane }
147+ </ span >
148+ < span className = "w-1/8 text-center text-lg font-bold" >
149+ { row . total }
150+ </ span >
151+ </ li >
152+ ) ) }
153+ </ ol >
154+ </ div >
155+ ) ;
156+ } ;
10157
11- export default Leaderboard ;
158+ export default Leaderboard ;
0 commit comments