From ba290fd520ba6c1561bf8b8f4f6dfbfcf79a73c7 Mon Sep 17 00:00:00 2001
From: Kevin Thomas
Date: Wed, 11 Dec 2024 19:12:17 -0800
Subject: [PATCH] feat: clean up leaderboard+profile page page & add dropdowns
to leaderboard page
---
src/api/profile/profile.ts | 3 +-
.../Leaderboard/LeaderboardColumn.tsx | 29 ++-
.../Leaderboard/LeaderboardEntry.tsx | 169 ++++++++++++++++++
src/components/Leaderboard/index.ts | 1 +
src/components/Profile/UserProfile.tsx | 5 +-
src/pages/leaderboard.tsx | 10 +-
src/pages/profile/[name].tsx | 7 +-
src/types/auth/index.ts | 40 +++++
8 files changed, 235 insertions(+), 29 deletions(-)
create mode 100644 src/components/Leaderboard/LeaderboardEntry.tsx
diff --git a/src/api/profile/profile.ts b/src/api/profile/profile.ts
index 39f1f1d..9425d01 100644
--- a/src/api/profile/profile.ts
+++ b/src/api/profile/profile.ts
@@ -1,6 +1,7 @@
import { buildUrl } from '../utils'
+import { PlayerStats } from 'src/types'
-export const getPlayerStats = async (name?: string) => {
+export const getPlayerStats = async (name?: string): Promise => {
const res = await fetch(
buildUrl(`auth/get_player_stats${name ? `/${name}` : ''}`),
)
diff --git a/src/components/Leaderboard/LeaderboardColumn.tsx b/src/components/Leaderboard/LeaderboardColumn.tsx
index 185d473..50e4117 100644
--- a/src/components/Leaderboard/LeaderboardColumn.tsx
+++ b/src/components/Leaderboard/LeaderboardColumn.tsx
@@ -1,8 +1,9 @@
-import Link from 'next/link'
+import { LeaderboardEntry } from 'src/components'
interface Props {
+ id: 'regular' | 'train' | 'turing' | 'hand' | 'brain'
+ name: 'Regular' | 'Train' | 'Bot/Not' | 'Hand' | 'Brain'
icon: JSX.Element
- name: string
ranking: {
display_name: string
elo: number
@@ -10,6 +11,7 @@ interface Props {
}
export const LeaderboardColumn: React.FC = ({
+ id,
icon,
name,
ranking,
@@ -22,24 +24,13 @@ export const LeaderboardColumn: React.FC = ({
{ranking.map((player, index) => (
-
-
-
{index + 1}
-
-
- {player.display_name} {index == 0 && '👑'}
-
-
-
-
-
{player.elo}
-
+ typeId={id}
+ type={name}
+ index={index}
+ {...player}
+ />
))}
diff --git a/src/components/Leaderboard/LeaderboardEntry.tsx b/src/components/Leaderboard/LeaderboardEntry.tsx
new file mode 100644
index 0000000..e5eee92
--- /dev/null
+++ b/src/components/Leaderboard/LeaderboardEntry.tsx
@@ -0,0 +1,169 @@
+import Link from 'next/link'
+import { useEffect, useState } from 'react'
+
+import { PlayerStats } from 'src/types'
+import { getPlayerStats } from 'src/api'
+
+interface Props {
+ index: number
+ typeId: 'regular' | 'train' | 'turing' | 'hand' | 'brain'
+ type: 'Regular' | 'Train' | 'Bot/Not' | 'Hand' | 'Brain'
+ display_name: string
+ elo: number
+}
+
+export const LeaderboardEntry = ({
+ typeId,
+ type,
+ index,
+ display_name,
+ elo,
+}: Props) => {
+ const [hover, setHover] = useState(false)
+ const [popup, setPopup] = useState(false)
+ const [stats, setStats] = useState(null)
+
+ let ratingKey:
+ | 'regularRating'
+ | 'trainRating'
+ | 'botNotRating'
+ | 'handRating'
+ | 'brainRating'
+ let highestRatingKey:
+ | 'regularMax'
+ | 'trainMax'
+ | 'botNotMax'
+ | 'handMax'
+ | 'brainMax'
+ let gamesKey:
+ | 'regularGames'
+ | 'trainGames'
+ | 'botNotCorrect'
+ | 'handGames'
+ | 'brainGames'
+
+ let gamesWonKey:
+ | 'regularWins'
+ | 'trainCorrect'
+ | 'botNotCorrect'
+ | 'handWins'
+ | 'brainWins'
+
+ switch (typeId) {
+ case 'regular':
+ ratingKey = 'regularRating'
+ highestRatingKey = 'regularMax'
+ gamesKey = 'regularGames'
+ gamesWonKey = 'regularWins'
+ break
+ case 'train':
+ ratingKey = 'trainRating'
+ highestRatingKey = 'trainMax'
+ gamesKey = 'trainGames'
+ gamesWonKey = 'trainCorrect'
+ break
+ case 'turing':
+ ratingKey = 'botNotRating'
+ highestRatingKey = 'botNotMax'
+ gamesKey = 'botNotCorrect'
+ gamesWonKey = 'botNotCorrect'
+ break
+ case 'hand':
+ ratingKey = 'handRating'
+ highestRatingKey = 'handMax'
+ gamesKey = 'handGames'
+ gamesWonKey = 'handWins'
+ break
+ case 'brain':
+ ratingKey = 'brainRating'
+ highestRatingKey = 'brainMax'
+ gamesKey = 'brainGames'
+ gamesWonKey = 'brainWins'
+ break
+ default:
+ ratingKey = 'regularRating'
+ highestRatingKey = 'regularMax'
+ gamesKey = 'regularGames'
+ gamesWonKey = 'regularWins'
+ break
+ }
+
+ useEffect(() => {
+ let timer: NodeJS.Timeout
+ if (hover) {
+ timer = setTimeout(() => {
+ fetchStats()
+ }, 300)
+ } else {
+ setPopup(false)
+ }
+
+ return () => clearTimeout(timer)
+ }, [hover])
+
+ const fetchStats = async () => {
+ try {
+ const playerStats = await getPlayerStats(display_name)
+ setStats(playerStats)
+ setPopup(true)
+ } catch (error) {
+ console.error(error)
+ }
+ }
+
+ return (
+ setHover(true)}
+ onMouseLeave={() => setHover(false)}
+ >
+
+
{index + 1}
+
+
+ {display_name} {index == 0 && '👑'}
+
+
+
+
{elo}
+ {popup && stats && (
+
+
+
+ {display_name}'s {type}{' '}
+ Statistics
+
+
+
+ open_in_new
+
+
+
+
+
+
Rating
+
{stats[ratingKey]}
+
+
+
Highest
+
{stats[highestRatingKey]}
+
+
+
Games
+
{stats[gamesKey]}
+
+
+
Win %
+
+ {((stats[gamesWonKey] / stats[gamesKey]) * 100).toFixed(0)}%
+
+
+
+
+ )}
+
+ )
+}
diff --git a/src/components/Leaderboard/index.ts b/src/components/Leaderboard/index.ts
index 0d02c41..af5523e 100644
--- a/src/components/Leaderboard/index.ts
+++ b/src/components/Leaderboard/index.ts
@@ -1 +1,2 @@
+export * from './LeaderboardEntry'
export * from './LeaderboardColumn'
diff --git a/src/components/Profile/UserProfile.tsx b/src/components/Profile/UserProfile.tsx
index fac60c6..b3f7890 100644
--- a/src/components/Profile/UserProfile.tsx
+++ b/src/components/Profile/UserProfile.tsx
@@ -8,12 +8,11 @@ import {
RegularPlayIcon,
} from '../Icons/icons'
import { ProfileColumn } from 'src/components'
+import { PlayerStats } from 'src/types'
interface Props {
wide?: boolean
- stats: {
- [key: string]: number
- }
+ stats: PlayerStats
}
export const UserProfile = ({ wide, stats }: Props) => {
diff --git a/src/pages/leaderboard.tsx b/src/pages/leaderboard.tsx
index c6995a7..b0bbc12 100644
--- a/src/pages/leaderboard.tsx
+++ b/src/pages/leaderboard.tsx
@@ -16,8 +16,9 @@ const Leaderboard: React.FC = () => {
const [leaderboard, setLeaderboard] = useState<
{
icon: JSX.Element
- name: string
ranking: { display_name: string; elo: number }[]
+ name: 'Regular' | 'Train' | 'Bot/Not' | 'Hand' | 'Brain'
+ id: 'regular' | 'train' | 'turing' | 'hand' | 'brain'
}[]
>()
const fetchLeaderboard = useCallback(async () => {
@@ -25,26 +26,31 @@ const Leaderboard: React.FC = () => {
setLastUpdated(new Date(lb.last_updated + 'Z'))
setLeaderboard([
{
+ id: 'regular',
icon: ,
name: 'Regular',
ranking: lb.play_leaders,
},
{
+ id: 'train',
icon: ,
name: 'Train',
ranking: lb.puzzles_leaders,
},
{
+ id: 'turing',
icon: ,
name: 'Bot/Not',
ranking: lb.turing_leaders,
},
{
+ id: 'hand',
icon: ,
name: 'Hand',
ranking: lb.hand_leaders,
},
{
+ id: 'brain',
icon: ,
name: 'Brain',
ranking: lb.brain_leaders,
@@ -81,7 +87,7 @@ const Leaderboard: React.FC = () => {
: '...'}
-
+
{leaderboard?.map((column, index) => (
))}
diff --git a/src/pages/profile/[name].tsx b/src/pages/profile/[name].tsx
index 9ba1f24..d2d1524 100644
--- a/src/pages/profile/[name].tsx
+++ b/src/pages/profile/[name].tsx
@@ -3,6 +3,7 @@ import { NextPage } from 'next'
import { useRouter } from 'next/router'
import { useContext, useState, useEffect } from 'react'
+import { PlayerStats } from 'src/types'
import { getPlayerStats } from 'src/api'
import { WindowSizeContext } from 'src/contexts'
import { UserIcon } from 'src/components/Icons/icons'
@@ -12,7 +13,7 @@ const ProfilePage: NextPage = () => {
const router = useRouter()
const [name, setName] = useState('')
- const [stats, setStats] = useState({
+ const [stats, setStats] = useState
({
regularRating: 0,
regularWins: 0,
regularDraws: 0,
@@ -77,9 +78,7 @@ const ProfilePage: NextPage = () => {
interface Props {
name: string
- stats: {
- [key: string]: number
- }
+ stats: PlayerStats
}
const Profile: React.FC = (props: Props) => {
const { isMobile } = useContext(WindowSizeContext)
diff --git a/src/types/auth/index.ts b/src/types/auth/index.ts
index 5398d42..8e4ced7 100644
--- a/src/types/auth/index.ts
+++ b/src/types/auth/index.ts
@@ -3,3 +3,43 @@ export interface User {
displayName: string
lichessId?: string
}
+
+export interface PlayerStats {
+ regularRating: number
+ regularWins: number
+ regularDraws: number
+ regularGames: number
+ regularMax: number
+ regularMin: number
+ regularHours: number
+
+ handRating: number
+ handWins: number
+ handDraws: number
+ handGames: number
+ handMax: number
+ handMin: number
+ handHours: number
+
+ brainRating: number
+ brainWins: number
+ brainDraws: number
+ brainGames: number
+ brainMax: number
+ brainMin: number
+ brainHours: number
+
+ trainRating: number
+ trainCorrect: number
+ trainGames: number
+ trainMax: number
+ trainMin: number
+ trainHours: number
+
+ botNotRating: number
+ botNotCorrect: number
+ botNotWrong: number
+ botNotMax: number
+ botNotMin: number
+ botNotHours: number
+}