Skip to content

Commit ba290fd

Browse files
feat: clean up leaderboard+profile page page & add dropdowns to leaderboard page
1 parent 56eb733 commit ba290fd

File tree

8 files changed

+235
-29
lines changed

8 files changed

+235
-29
lines changed

src/api/profile/profile.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { buildUrl } from '../utils'
2+
import { PlayerStats } from 'src/types'
23

3-
export const getPlayerStats = async (name?: string) => {
4+
export const getPlayerStats = async (name?: string): Promise<PlayerStats> => {
45
const res = await fetch(
56
buildUrl(`auth/get_player_stats${name ? `/${name}` : ''}`),
67
)

src/components/Leaderboard/LeaderboardColumn.tsx

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import Link from 'next/link'
1+
import { LeaderboardEntry } from 'src/components'
22

33
interface Props {
4+
id: 'regular' | 'train' | 'turing' | 'hand' | 'brain'
5+
name: 'Regular' | 'Train' | 'Bot/Not' | 'Hand' | 'Brain'
46
icon: JSX.Element
5-
name: string
67
ranking: {
78
display_name: string
89
elo: number
910
}[]
1011
}
1112

1213
export const LeaderboardColumn: React.FC<Props> = ({
14+
id,
1315
icon,
1416
name,
1517
ranking,
@@ -22,24 +24,13 @@ export const LeaderboardColumn: React.FC<Props> = ({
2224
</div>
2325
<div className="flex w-full flex-col">
2426
{ranking.map((player, index) => (
25-
<div
27+
<LeaderboardEntry
2628
key={index}
27-
className={`flex w-full items-center justify-between px-6 py-2 ${index % 2 === 0 ? 'bg-background-1/90' : 'bg-background-1/50'}`}
28-
>
29-
<div className="flex items-center gap-2">
30-
<p className="w-5">{index + 1}</p>
31-
<Link
32-
href={`/profile/${player.display_name}`}
33-
className="flex items-center gap-2 hover:underline"
34-
>
35-
<p>
36-
{player.display_name} {index == 0 && '👑'}
37-
</p>
38-
</Link>
39-
</div>
40-
41-
<p>{player.elo}</p>
42-
</div>
29+
typeId={id}
30+
type={name}
31+
index={index}
32+
{...player}
33+
/>
4334
))}
4435
</div>
4536
</div>
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import Link from 'next/link'
2+
import { useEffect, useState } from 'react'
3+
4+
import { PlayerStats } from 'src/types'
5+
import { getPlayerStats } from 'src/api'
6+
7+
interface Props {
8+
index: number
9+
typeId: 'regular' | 'train' | 'turing' | 'hand' | 'brain'
10+
type: 'Regular' | 'Train' | 'Bot/Not' | 'Hand' | 'Brain'
11+
display_name: string
12+
elo: number
13+
}
14+
15+
export const LeaderboardEntry = ({
16+
typeId,
17+
type,
18+
index,
19+
display_name,
20+
elo,
21+
}: Props) => {
22+
const [hover, setHover] = useState(false)
23+
const [popup, setPopup] = useState(false)
24+
const [stats, setStats] = useState<PlayerStats | null>(null)
25+
26+
let ratingKey:
27+
| 'regularRating'
28+
| 'trainRating'
29+
| 'botNotRating'
30+
| 'handRating'
31+
| 'brainRating'
32+
let highestRatingKey:
33+
| 'regularMax'
34+
| 'trainMax'
35+
| 'botNotMax'
36+
| 'handMax'
37+
| 'brainMax'
38+
let gamesKey:
39+
| 'regularGames'
40+
| 'trainGames'
41+
| 'botNotCorrect'
42+
| 'handGames'
43+
| 'brainGames'
44+
45+
let gamesWonKey:
46+
| 'regularWins'
47+
| 'trainCorrect'
48+
| 'botNotCorrect'
49+
| 'handWins'
50+
| 'brainWins'
51+
52+
switch (typeId) {
53+
case 'regular':
54+
ratingKey = 'regularRating'
55+
highestRatingKey = 'regularMax'
56+
gamesKey = 'regularGames'
57+
gamesWonKey = 'regularWins'
58+
break
59+
case 'train':
60+
ratingKey = 'trainRating'
61+
highestRatingKey = 'trainMax'
62+
gamesKey = 'trainGames'
63+
gamesWonKey = 'trainCorrect'
64+
break
65+
case 'turing':
66+
ratingKey = 'botNotRating'
67+
highestRatingKey = 'botNotMax'
68+
gamesKey = 'botNotCorrect'
69+
gamesWonKey = 'botNotCorrect'
70+
break
71+
case 'hand':
72+
ratingKey = 'handRating'
73+
highestRatingKey = 'handMax'
74+
gamesKey = 'handGames'
75+
gamesWonKey = 'handWins'
76+
break
77+
case 'brain':
78+
ratingKey = 'brainRating'
79+
highestRatingKey = 'brainMax'
80+
gamesKey = 'brainGames'
81+
gamesWonKey = 'brainWins'
82+
break
83+
default:
84+
ratingKey = 'regularRating'
85+
highestRatingKey = 'regularMax'
86+
gamesKey = 'regularGames'
87+
gamesWonKey = 'regularWins'
88+
break
89+
}
90+
91+
useEffect(() => {
92+
let timer: NodeJS.Timeout
93+
if (hover) {
94+
timer = setTimeout(() => {
95+
fetchStats()
96+
}, 300)
97+
} else {
98+
setPopup(false)
99+
}
100+
101+
return () => clearTimeout(timer)
102+
}, [hover])
103+
104+
const fetchStats = async () => {
105+
try {
106+
const playerStats = await getPlayerStats(display_name)
107+
setStats(playerStats)
108+
setPopup(true)
109+
} catch (error) {
110+
console.error(error)
111+
}
112+
}
113+
114+
return (
115+
<div
116+
className={`relative flex w-full items-center justify-between px-6 py-2 ${index % 2 === 0 ? 'bg-background-1/90' : 'bg-background-1/50'}`}
117+
onMouseEnter={() => setHover(true)}
118+
onMouseLeave={() => setHover(false)}
119+
>
120+
<div className="flex items-center gap-2">
121+
<p className="w-5">{index + 1}</p>
122+
<Link
123+
href={`/profile/${display_name}`}
124+
className="flex items-center gap-2 hover:underline"
125+
>
126+
<p>
127+
{display_name} {index == 0 && '👑'}
128+
</p>
129+
</Link>
130+
</div>
131+
<p>{elo}</p>
132+
{popup && stats && (
133+
<div className="absolute left-0 top-[100%] z-20 flex w-full max-w-[26rem] flex-col overflow-hidden rounded border border-white/10 bg-background-1">
134+
<div className="flex w-full justify-between bg-backdrop/50 px-4 py-2">
135+
<p>
136+
<span className="font-bold">{display_name}</span>&apos;s {type}{' '}
137+
Statistics
138+
</p>
139+
<Link href={`/profile/${display_name}`}>
140+
<i className="material-symbols-outlined select-none text-lg text-primary hover:text-human-1">
141+
open_in_new
142+
</i>
143+
</Link>
144+
</div>
145+
<div className="flex items-center justify-between px-4 py-2">
146+
<div className="flex flex-col items-center justify-center gap-0.5 text-human-1">
147+
<p className="text-sm xl:text-base">Rating</p>
148+
<b className="text-3xl xl:text-3xl">{stats[ratingKey]}</b>
149+
</div>
150+
<div className="flex flex-col items-center justify-center gap-0.5">
151+
<p className="text-sm xl:text-base">Highest</p>
152+
<b className="text-3xl xl:text-3xl">{stats[highestRatingKey]}</b>
153+
</div>
154+
<div className="flex flex-col items-center justify-center gap-0.5">
155+
<p className="text-sm xl:text-base">Games</p>
156+
<b className="text-3xl xl:text-3xl">{stats[gamesKey]}</b>
157+
</div>
158+
<div className="flex flex-col items-center justify-center gap-0.5">
159+
<p className="text-sm xl:text-base">Win %</p>
160+
<b className="text-3xl xl:text-3xl">
161+
{((stats[gamesWonKey] / stats[gamesKey]) * 100).toFixed(0)}%
162+
</b>
163+
</div>
164+
</div>
165+
</div>
166+
)}
167+
</div>
168+
)
169+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export * from './LeaderboardEntry'
12
export * from './LeaderboardColumn'

src/components/Profile/UserProfile.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import {
88
RegularPlayIcon,
99
} from '../Icons/icons'
1010
import { ProfileColumn } from 'src/components'
11+
import { PlayerStats } from 'src/types'
1112

1213
interface Props {
1314
wide?: boolean
14-
stats: {
15-
[key: string]: number
16-
}
15+
stats: PlayerStats
1716
}
1817

1918
export const UserProfile = ({ wide, stats }: Props) => {

src/pages/leaderboard.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,35 +16,41 @@ const Leaderboard: React.FC = () => {
1616
const [leaderboard, setLeaderboard] = useState<
1717
{
1818
icon: JSX.Element
19-
name: string
2019
ranking: { display_name: string; elo: number }[]
20+
name: 'Regular' | 'Train' | 'Bot/Not' | 'Hand' | 'Brain'
21+
id: 'regular' | 'train' | 'turing' | 'hand' | 'brain'
2122
}[]
2223
>()
2324
const fetchLeaderboard = useCallback(async () => {
2425
const lb = await getLeaderboard()
2526
setLastUpdated(new Date(lb.last_updated + 'Z'))
2627
setLeaderboard([
2728
{
29+
id: 'regular',
2830
icon: <RegularPlayIcon />,
2931
name: 'Regular',
3032
ranking: lb.play_leaders,
3133
},
3234
{
35+
id: 'train',
3336
icon: <TrainIcon />,
3437
name: 'Train',
3538
ranking: lb.puzzles_leaders,
3639
},
3740
{
41+
id: 'turing',
3842
icon: <TuringIcon />,
3943
name: 'Bot/Not',
4044
ranking: lb.turing_leaders,
4145
},
4246
{
47+
id: 'hand',
4348
icon: <HandIcon />,
4449
name: 'Hand',
4550
ranking: lb.hand_leaders,
4651
},
4752
{
53+
id: 'brain',
4854
icon: <BrainIcon />,
4955
name: 'Brain',
5056
ranking: lb.brain_leaders,
@@ -81,7 +87,7 @@ const Leaderboard: React.FC = () => {
8187
: '...'}
8288
</p>
8389
</div>
84-
<div className="grid h-full w-full grid-cols-1 justify-start gap-4 md:grid-cols-3">
90+
<div className="grid h-full w-full grid-cols-1 justify-start gap-4 md:grid-cols-2 lg:grid-cols-3">
8591
{leaderboard?.map((column, index) => (
8692
<LeaderboardColumn key={index} {...column} />
8793
))}

src/pages/profile/[name].tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { NextPage } from 'next'
33
import { useRouter } from 'next/router'
44
import { useContext, useState, useEffect } from 'react'
55

6+
import { PlayerStats } from 'src/types'
67
import { getPlayerStats } from 'src/api'
78
import { WindowSizeContext } from 'src/contexts'
89
import { UserIcon } from 'src/components/Icons/icons'
@@ -12,7 +13,7 @@ const ProfilePage: NextPage = () => {
1213
const router = useRouter()
1314

1415
const [name, setName] = useState('')
15-
const [stats, setStats] = useState({
16+
const [stats, setStats] = useState<PlayerStats>({
1617
regularRating: 0,
1718
regularWins: 0,
1819
regularDraws: 0,
@@ -77,9 +78,7 @@ const ProfilePage: NextPage = () => {
7778

7879
interface Props {
7980
name: string
80-
stats: {
81-
[key: string]: number
82-
}
81+
stats: PlayerStats
8382
}
8483
const Profile: React.FC<Props> = (props: Props) => {
8584
const { isMobile } = useContext(WindowSizeContext)

src/types/auth/index.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,43 @@ export interface User {
33
displayName: string
44
lichessId?: string
55
}
6+
7+
export interface PlayerStats {
8+
regularRating: number
9+
regularWins: number
10+
regularDraws: number
11+
regularGames: number
12+
regularMax: number
13+
regularMin: number
14+
regularHours: number
15+
16+
handRating: number
17+
handWins: number
18+
handDraws: number
19+
handGames: number
20+
handMax: number
21+
handMin: number
22+
handHours: number
23+
24+
brainRating: number
25+
brainWins: number
26+
brainDraws: number
27+
brainGames: number
28+
brainMax: number
29+
brainMin: number
30+
brainHours: number
31+
32+
trainRating: number
33+
trainCorrect: number
34+
trainGames: number
35+
trainMax: number
36+
trainMin: number
37+
trainHours: number
38+
39+
botNotRating: number
40+
botNotCorrect: number
41+
botNotWrong: number
42+
botNotMax: number
43+
botNotMin: number
44+
botNotHours: number
45+
}

0 commit comments

Comments
 (0)