From 989749fb2c04258c1252eb9ca9c905549b4835a2 Mon Sep 17 00:00:00 2001 From: Paul Queruel Date: Tue, 11 Aug 2026 19:38:46 +0200 Subject: [PATCH 1/5] feat(ranking): add public leaderboard --- .../GameContext/useGameSetup.test.tsx | 2 - .../components/GameContext/useGameSetup.ts | 23 +- .../front/src/components/GameOutcome.test.tsx | 1 + apps/front/src/components/HomePage.tsx | 11 +- .../front/src/components/Leaderboard.test.tsx | 70 ++++++ apps/front/src/components/Leaderboard.tsx | 194 +++++++++++++++++ .../src/components/PlayerBoard/Board.tsx | 10 +- .../src/components/PlayerBoard/Name.test.tsx | 40 ---- .../front/src/components/PlayerBoard/Name.tsx | 125 +---------- .../components/PlayerBoard/PlayerOneBoard.tsx | 16 +- apps/front/src/components/Profile.test.tsx | 15 +- apps/front/src/components/Profile.tsx | 44 ++-- .../src/components/RankedMatchmaking.test.tsx | 1 + apps/front/src/components/Router.tsx | 3 + apps/front/src/translations/resources/en.json | 17 +- apps/front/src/translations/resources/fr.json | 17 +- .../src/translations/resources/zh-tw.json | 17 +- apps/front/src/utils/api.test.ts | 14 +- apps/front/src/utils/api.ts | 56 ++--- apps/front/src/utils/identityStorage.ts | 13 -- apps/front/src/utils/playerIdentity.test.ts | 23 +- apps/front/src/utils/playerIdentity.ts | 12 +- .../0009_add_player_display_names.sql | 5 + .../durable-objects/GameStateDurableObject.ts | 45 +--- apps/worker/src/endpoints/createPlayer.ts | 24 ++- .../worker/src/endpoints/deleteDisplayName.ts | 32 --- apps/worker/src/endpoints/displayName.ts | 114 ---------- .../src/endpoints/getRankedLeaderboard.ts | 87 ++++++++ apps/worker/src/endpoints/getRankedProfile.ts | 11 +- apps/worker/src/endpoints/index.ts | 4 +- apps/worker/src/endpoints/init.ts | 39 +++- .../src/endpoints/updateRankedProfile.ts | 34 +++ apps/worker/src/utils/validation.ts | 9 - apps/worker/src/workers/index.ts | 26 +-- apps/worker/test/observability.test.ts | 6 +- apps/worker/test/worker.integration.test.ts | 201 +++++++++++++++--- packages/common/src/schemas/api.ts | 11 +- packages/common/src/schemas/durableObject.ts | 18 +- packages/common/src/schemas/playerIdentity.ts | 6 + packages/common/src/schemas/ranking.ts | 31 ++- packages/common/src/types/durableObject.ts | 15 +- packages/common/src/types/playerIdentity.ts | 4 + packages/common/src/types/ranking.ts | 20 ++ 43 files changed, 850 insertions(+), 616 deletions(-) create mode 100644 apps/front/src/components/Leaderboard.test.tsx create mode 100644 apps/front/src/components/Leaderboard.tsx delete mode 100644 apps/front/src/components/PlayerBoard/Name.test.tsx create mode 100644 apps/worker/migrations/0009_add_player_display_names.sql delete mode 100644 apps/worker/src/endpoints/deleteDisplayName.ts delete mode 100644 apps/worker/src/endpoints/displayName.ts create mode 100644 apps/worker/src/endpoints/getRankedLeaderboard.ts create mode 100644 apps/worker/src/endpoints/updateRankedProfile.ts diff --git a/apps/front/src/components/GameContext/useGameSetup.test.tsx b/apps/front/src/components/GameContext/useGameSetup.test.tsx index 5ce51a30..762200ca 100644 --- a/apps/front/src/components/GameContext/useGameSetup.test.tsx +++ b/apps/front/src/components/GameContext/useGameSetup.test.tsx @@ -75,11 +75,9 @@ vi.mock('../../utils/api', () => ({ } }, createWebSocketTicket: vi.fn(), - deleteDisplayName: vi.fn(), initGame: vi.fn(), play: vi.fn(), reportClientProtocolDiagnostic: vi.fn(), - updateDisplayName: vi.fn(), voteRematch: vi.fn() })) vi.mock('../../utils/playerIdentity', async (importOriginal) => ({ diff --git a/apps/front/src/components/GameContext/useGameSetup.ts b/apps/front/src/components/GameContext/useGameSetup.ts index bc533f32..73d0c5eb 100644 --- a/apps/front/src/components/GameContext/useGameSetup.ts +++ b/apps/front/src/components/GameContext/useGameSetup.ts @@ -9,7 +9,6 @@ import { GameState, getGameStateMessagePayload, type IGameState, - isEmptyOrBlank, PROTOCOL_VERSION, type GameSettings } from '@knucklebones/common' @@ -18,8 +17,6 @@ import { useRoomKey } from '../../hooks/useRoomKey' import { ApiRequestError, createWebSocketTicket, - deleteDisplayName, - updateDisplayName, initGame, play, reportClientProtocolDiagnostic, @@ -354,23 +351,6 @@ export function useGameSetup() { }) } - async function _updateDisplayName(newDisplayName: string) { - if (isEmptyOrBlank(newDisplayName)) { - await deleteDisplayName({ roomKey, playerId: playerId! }).catch( - (error) => { - setErrorMessage(error.message) - } - ) - } else { - await updateDisplayName( - { roomKey, playerId: playerId! }, - { displayName: newDisplayName } - ).catch((error) => { - setErrorMessage(error.message) - }) - } - } - // Easy way to do a type guard if (identityError !== null) { return { @@ -401,7 +381,6 @@ export function useGameSetup() { voteContinueBo, voteContinueIndefinitely, voteRematch: _voteRematch, - resign, - updateDisplayName: _updateDisplayName + resign } } diff --git a/apps/front/src/components/GameOutcome.test.tsx b/apps/front/src/components/GameOutcome.test.tsx index 2eefa0f0..8cd5add7 100644 --- a/apps/front/src/components/GameOutcome.test.tsx +++ b/apps/front/src/components/GameOutcome.test.tsx @@ -93,6 +93,7 @@ describe('GameOutcome ranked rating', () => { vi.mocked(getRankedProfile).mockReset() vi.mocked(getRankedProfile).mockResolvedValue({ playerId: '11111111-1111-4111-8111-111111111111', + displayName: 'Player One', ratingPool: 'classic', rating: 1184, gamesPlayed: 1, diff --git a/apps/front/src/components/HomePage.tsx b/apps/front/src/components/HomePage.tsx index d54a7354..53f1185e 100644 --- a/apps/front/src/components/HomePage.tsx +++ b/apps/front/src/components/HomePage.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' -import { UserCircleIcon } from '@heroicons/react/24/outline' +import { TrophyIcon, UserCircleIcon } from '@heroicons/react/24/outline' import { type PlayerType } from '@knucklebones/common' import { useLocalizedPath } from '../hooks/useLocalizedPath' import KnucklebonesLogo from '../svgs/logo.svg' @@ -70,6 +70,15 @@ export function HomePage() { > +