Skip to content

Commit

Permalink
feat: add play, hand, brain games to profile page
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinjosethomas committed Nov 19, 2024
1 parent 800fcb1 commit 45fa9d2
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 28 deletions.
12 changes: 12 additions & 0 deletions src/api/analysis/analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ export const getAnalysisList = async (): Promise<
return data
}

export const getAnalysisGameList = async (type = 'play', page = 1) => {
const res = await fetch(buildUrl(`analysis/user/list/${type}/${page}`))

if (res.status === 401) {
throw new Error('Unauthorized')
}

const data = await res.json()

return data
}

export const getLichessGames = async (
username: string,
onMessage: (data: any) => void,
Expand Down
154 changes: 127 additions & 27 deletions src/components/UserProfile/GameList.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,64 @@
import { motion } from 'framer-motion'
import { AuthContext } from 'src/contexts'
import React, { useState, useEffect, useContext } from 'react'

import { getLichessGames } from 'src/api'
import { AnalysisLichessGame } from 'src/types'
import { getLichessGames, getAnalysisGameList } from 'src/api'
import { AnalysisLichessGame, AnalysisWebGame } from 'src/types'

export default function GameList() {
const { user } = useContext(AuthContext)
const [games, setGames] = useState<AnalysisLichessGame[]>([])
const [selected, setSelected] = useState<
'play' | 'hand' | 'brain' | 'lichess'
>('play')
const [games, setGames] = useState<AnalysisWebGame[]>([])
const [playGames, setPlayGames] = useState<AnalysisWebGame[]>([])
const [handGames, setHandGames] = useState<AnalysisWebGame[]>([])
const [brainGames, setBrainGames] = useState<AnalysisWebGame[]>([])

useEffect(() => {
if (user?.lichessId) {
const playRequest = getAnalysisGameList('play', 1)
const handRequest = getAnalysisGameList('hand', 1)
const brainRequest = getAnalysisGameList('brain', 1)

Promise.all([playRequest, handRequest, brainRequest]).then((data) => {
const [play, hand, brain] = data

const parse = (game: {
game_id: string
maia_name: string
result: string
player_color: 'white' | 'black'
}) => {
const raw = game.maia_name.replace('_kdd_', ' ')
const maia = raw.charAt(0).toUpperCase() + raw.slice(1)

return {
id: game.game_id,
label:
game.player_color === 'white'
? `You vs. ${maia}`
: `${maia} vs. You`,
result: game.result,
}
}

setPlayGames(play.games.map(parse))
setHandGames(hand.games.map(parse))
setBrainGames(brain.games.map(parse))
})
}
}, [user?.lichessId])

useEffect(() => {
if (user?.lichessId) {
getLichessGames(user?.lichessId, (data) => {
const playerColor =
data.players.white.user?.id == user?.lichessId ? 'white' : 'black'
const result = data.pgn.match(/\[Result\s+"(.+?)"\]/)[1] || '?'

const game = {
id: data.id,
white:
playerColor == 'white'
? 'You'
: data.players.white.user?.id || 'Anonymous',
black:
playerColor == 'black'
? 'You'
: data.players.black.user?.id || 'Anonymous',
label: `${data.players.white.user?.id || 'Unknown'} vs. ${data.players.black.user?.id || 'Unknown'}`,
result: result,
pgn: data.pgn,
}

setGames((x) => [...x, game])
Expand All @@ -35,30 +67,98 @@ export default function GameList() {
}, [user?.lichessId])

return (
<div className="flex w-full flex-col md:w-[500px]">
<div className="flex flex-row items-center justify-start gap-4 bg-background-2 px-6 py-5 md:px-8">
<p className="text-2xl font-bold md:text-3xl">YOUR GAMES</p>
<div className="flex w-full flex-col overflow-hidden rounded border border-white border-opacity-10 md:w-[600px]">
<div className="flex flex-row items-center justify-start gap-4 border-b border-white border-opacity-10 bg-background-1 px-4 py-4 md:px-6">
<p className="text-2xl font-bold md:text-2xl">Your Games</p>
</div>

<div className="flex max-h-96 flex-col overflow-y-scroll bg-background-1">
{games.map((game, index) => (
<div className="grid select-none grid-cols-4 border-b-2 border-white border-opacity-10">
<Header
label="Play"
name="play"
selected={selected}
setSelected={setSelected}
/>
<Header
label="Hand"
name="hand"
selected={selected}
setSelected={setSelected}
/>
<Header
label="Brain"
name="brain"
selected={selected}
setSelected={setSelected}
/>
<Header
label="Lichess"
name="lichess"
selected={selected}
setSelected={setSelected}
/>
</div>
<div className="red-scrollbar flex max-h-[60vh] flex-col overflow-y-scroll">
{(selected === 'play'
? playGames
: selected === 'hand'
? handGames
: selected === 'brain'
? brainGames
: games
).map((game, index) => (
<a
key={index}
href={`/analysis/${game.id}/lichess`}
className="group flex w-full cursor-pointer items-center gap-2 pr-2 hover:bg-background-2"
className={`group flex w-full cursor-pointer items-center gap-2 pr-2 ${index % 2 === 0 ? 'bg-background-1/40' : 'bg-background-1/20'} hover:bg-background-1/80`}
>
<div className="flex h-full w-10 items-center justify-center bg-background-2 group-hover:bg-background-3">
<p className="text-muted">{index + 1}</p>
<div className="flex h-full w-10 items-center justify-center bg-background-1 py-1 group-hover:bg-white/5">
<p className="text-secondary">{index + 1}</p>
</div>
<div className="flex flex-1 items-center justify-between py-1">
<p className="text-primary">
{game.white} vs. {game.black}
</p>
<p className="text-muted">{game.result}</p>
<p className="text-secondary">{game.label}</p>
<p className="text-secondary">{game.result}</p>
</div>
</a>
))}
</div>
</div>
)
}

function Header({
name,
label,
selected,
setSelected,
}: {
label: string
name: 'play' | 'hand' | 'brain' | 'lichess'
selected: 'play' | 'hand' | 'brain' | 'lichess'
setSelected: (name: 'play' | 'hand' | 'brain' | 'lichess') => void
}) {
return (
<button
onClick={() => setSelected(name)}
className={`relative flex items-center justify-center py-1 ${selected === name ? 'bg-human-4/20' : 'bg-background-1/60 hover:bg-background-1'} transition duration-200`}
>
<div className="flex items-center justify-start gap-1">
<p
className={`transition duration-200 ${selected === name ? 'text-human-2/80' : 'text-primary/80'} `}
>
{label}
</p>
<i
className={`material-symbols-outlined text-base transition duration-200 ${selected === name ? 'text-human-2/80' : 'text-primary/80'}`}
>
keyboard_arrow_down
</i>
</div>
{selected === name && (
<motion.div
layoutId="underline"
className="absolute -bottom-0.5 h-0.5 w-full bg-human-2/80"
></motion.div>
)}
</button>
)
}
2 changes: 1 addition & 1 deletion src/components/UserProfile/UserProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const UserProfile: React.FC = () => {
<div className="*:w-16 *:fill-primary">{UserIcon}</div>
<h1 className="text-3xl font-semibold">{user?.displayName}</h1>
</div>
<div className="flex flex-col gap-6 md:flex-row">
<div className="flex flex-col items-start gap-6 md:flex-row">
<GameList />
<div className="grid h-full w-full grid-cols-2 gap-6">
<ProfileColumn
Expand Down
6 changes: 6 additions & 0 deletions src/types/analysis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ export interface AnalysisLichessGame {
result?: string
}

export interface AnalysisWebGame {
id: string
label: string
result: string
}

export interface AnalyzedGame extends Game {
maiaEvaluations: { [model: string]: MoveMap[] }
stockfishEvaluations: StockfishEvaluations<EvaluationType>
Expand Down

0 comments on commit 45fa9d2

Please sign in to comment.