diff --git a/packages/common/src/types/game/status.ts b/packages/common/src/types/game/status.ts index 3a73868..ef1ba17 100644 --- a/packages/common/src/types/game/status.ts +++ b/packages/common/src/types/game/status.ts @@ -47,7 +47,7 @@ type ManagerExtraStatus = { answers: string[] image?: string } - SHOW_LEADERBOARD: { leaderboard: Player[] } + SHOW_LEADERBOARD: { oldLeaderboard: Player[]; leaderboard: Player[] } } export type PlayerStatusDataMap = CommonStatusDataMap diff --git a/packages/socket/src/services/game.ts b/packages/socket/src/services/game.ts index 6713be3..8fae6c2 100644 --- a/packages/socket/src/services/game.ts +++ b/packages/socket/src/services/game.ts @@ -25,6 +25,7 @@ class Game { managerStatus: { name: Status; data: StatusDataMap[Status] } | null = null playerStatus: Map = new Map() + leaderboard: Player[] quizz: Quizz players: Player[] @@ -58,6 +59,7 @@ class Game { this.lastBroadcastStatus = null this.managerStatus = null this.playerStatus = new Map() + this.leaderboard = [] this.players = [] @@ -490,20 +492,24 @@ class Game { this.round.currentQuestion + 1 === this.quizz.questions.length const sortedPlayers = this.players.sort((a, b) => b.points - a.points) + const oldLeaderboard = + this.leaderboard.length === 0 ? sortedPlayers : this.leaderboard + this.leaderboard = this.players.sort((a, b) => b.points - a.points) if (isLastRound) { this.started = false this.broadcastStatus(STATUS.FINISHED, { subject: this.quizz.subject, - top: sortedPlayers.slice(0, 3), + top: this.leaderboard.slice(0, 3), }) return } this.sendStatus(this.manager.id, STATUS.SHOW_LEADERBOARD, { - leaderboard: sortedPlayers.slice(0, 5), + oldLeaderboard: oldLeaderboard.slice(0, 5), + leaderboard: this.leaderboard.slice(0, 5), }) } } diff --git a/packages/web/package.json b/packages/web/package.json index 6e5f41d..882ff70 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -13,6 +13,7 @@ "@t3-oss/env-nextjs": "^0.13.8", "clsx": "^2.1.1", "ky": "^1.13.0", + "motion": "^12.23.24", "next": "15.5.4", "react": "19.1.0", "react-confetti": "^6.4.0", diff --git a/packages/web/src/components/game/states/Leaderboard.tsx b/packages/web/src/components/game/states/Leaderboard.tsx index 58a1a16..4054fcb 100644 --- a/packages/web/src/components/game/states/Leaderboard.tsx +++ b/packages/web/src/components/game/states/Leaderboard.tsx @@ -1,26 +1,63 @@ import { ManagerStatusDataMap } from "@rahoot/common/types/game/status" +import { AnimatePresence, motion } from "motion/react" +import { useEffect, useState } from "react" type Props = { data: ManagerStatusDataMap["SHOW_LEADERBOARD"] } -const Leaderboard = ({ data: { leaderboard } }: Props) => ( -
-

- Leaderboard -

-
- {leaderboard.map(({ username, points }, key) => ( -
- {username} - {points} -
- ))} -
-
-) +const Leaderboard = ({ data: { oldLeaderboard, leaderboard } }: Props) => { + const [displayedLeaderboard, setDisplayedLeaderboard] = + useState(oldLeaderboard) + + useEffect(() => { + setDisplayedLeaderboard(oldLeaderboard) + + const timer = setTimeout(() => { + setDisplayedLeaderboard(leaderboard) + }, 2000) + + return () => clearTimeout(timer) + }, [oldLeaderboard, leaderboard]) + + return ( +
+

+ Leaderboard +

+
+ + {displayedLeaderboard.map(({ username, points }) => ( + + {username} + {points} + + ))} + +
+
+ ) +} export default Leaderboard diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a13227c..b586a6b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,6 +103,9 @@ importers: ky: specifier: ^1.13.0 version: 1.13.0 + motion: + specifier: ^12.23.24 + version: 12.23.24(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next: specifier: 15.5.4 version: 15.5.4(@babel/core@7.28.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -1493,6 +1496,20 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + framer-motion@12.23.24: + resolution: {integrity: sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1902,6 +1919,26 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + motion-dom@12.23.23: + resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==} + + motion-utils@12.23.6: + resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} + + motion@12.23.24: + resolution: {integrity: sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3889,6 +3926,15 @@ snapshots: dependencies: is-callable: 1.2.7 + framer-motion@12.23.24(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + motion-dom: 12.23.23 + motion-utils: 12.23.6 + tslib: 2.8.1 + optionalDependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + fsevents@2.3.3: optional: true @@ -4268,6 +4314,20 @@ snapshots: minimist@1.2.8: {} + motion-dom@12.23.23: + dependencies: + motion-utils: 12.23.6 + + motion-utils@12.23.6: {} + + motion@12.23.24(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + framer-motion: 12.23.24(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + tslib: 2.8.1 + optionalDependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + ms@2.1.3: {} nanoid@3.3.11: {}