diff --git a/package.json b/package.json index a9e632c..bae3cf4 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "clsx": "^2.1.1", "dexie": "^4.0.8", "dexie-react-hooks": "^1.1.7", + "framer-motion": "^11.5.4", "ky": "^1.7.2", "lucide-react": "^0.439.0", "next": "14.2.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 120f515..0564f34 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ dependencies: dexie-react-hooks: specifier: ^1.1.7 version: 1.1.7(@types/react@18.3.5)(dexie@4.0.8)(react@18.3.1) + framer-motion: + specifier: ^11.5.4 + version: 11.5.4(react-dom@18.3.1)(react@18.3.1) ky: specifier: ^1.7.2 version: 1.7.2 @@ -2038,6 +2041,25 @@ packages: cross-spawn: 7.0.3 signal-exit: 4.1.0 + /framer-motion@11.5.4(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-E+tb3/G6SO69POkdJT+3EpdMuhmtCh9EWuK4I1DnIC23L7tFPrl8vxP+LSovwaw6uUr73rUbpb4FgK011wbRJQ==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.7.0 + dev: false + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true diff --git a/src/components/player/player-data-point.tsx b/src/components/player/player-data-point.tsx deleted file mode 100644 index 82ebde2..0000000 --- a/src/components/player/player-data-point.tsx +++ /dev/null @@ -1,13 +0,0 @@ -type Props = { - icon?: React.ReactNode; - children: React.ReactNode; -}; - -export default function PlayerDataPoint({ icon, children }: Props) { - return ( -
- {icon} - {children} -
- ); -} diff --git a/src/components/player/player-data.tsx b/src/components/player/player-data.tsx index 638a69f..01effd3 100644 --- a/src/components/player/player-data.tsx +++ b/src/components/player/player-data.tsx @@ -31,7 +31,11 @@ export default function PlayerData({ initalPlayerData, sort, page }: Props) { return (
- + {!player.inactive && ( + <> + + + )}
); diff --git a/src/components/player/player-header.tsx b/src/components/player/player-header.tsx index 7beddb6..440f92c 100644 --- a/src/components/player/player-header.tsx +++ b/src/components/player/player-header.tsx @@ -5,10 +5,10 @@ import Card from "../card"; import CountryFlag from "../country-flag"; import { Avatar, AvatarImage } from "../ui/avatar"; import ClaimProfile from "./claim-profile"; -import PlayerDataPoint from "./player-data-point"; -const playerSubNames = [ +const playerData = [ { + showWhenInactiveOrBanned: false, icon: () => { return ; }, @@ -17,6 +17,7 @@ const playerSubNames = [ }, }, { + showWhenInactiveOrBanned: false, icon: (player: ScoreSaberPlayer) => { return ; }, @@ -25,6 +26,7 @@ const playerSubNames = [ }, }, { + showWhenInactiveOrBanned: true, render: (player: ScoreSaberPlayer) => { return

{formatNumberWithCommas(player.pp)}pp

; }, @@ -44,14 +46,26 @@ export default function PlayerHeader({ player }: Props) {

{player.name}

-
- {playerSubNames.map((subName, index) => { - return ( - - {subName.render(player)} - - ); - })} +
+
+ {player.inactive &&

Inactive Account

} + {player.banned &&

Banned Account

} +
+
+ {playerData.map((subName, index) => { + // Check if the player is inactive or banned and if the data should be shown + if (!subName.showWhenInactiveOrBanned && (player.inactive || player.banned)) { + return null; + } + + return ( +
+ {subName.icon && subName.icon(player)} + {subName.render && subName.render(player)} +
+ ); + })} +
diff --git a/src/components/player/player-scores.tsx b/src/components/player/player-scores.tsx index 3eaa379..1e48ea6 100644 --- a/src/components/player/player-scores.tsx +++ b/src/components/player/player-scores.tsx @@ -7,7 +7,8 @@ import ScoreSaberPlayerScoresPage from "@/common/data-fetcher/types/scoresaber/s import { capitalizeFirstLetter } from "@/common/string-utils"; import useWindowDimensions from "@/hooks/use-window-dimensions"; import { useQuery } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; +import { motion, useAnimation } from "framer-motion"; +import { useCallback, useEffect, useState } from "react"; import Card from "../card"; import Pagination from "../input/pagination"; import { Button } from "../ui/button"; @@ -21,6 +22,8 @@ type Props = { export default function PlayerScores({ player, sort, page }: Props) { const { width } = useWindowDimensions(); + const controls = useAnimation(); + const [currentSort, setCurrentSort] = useState(sort); const [currentPage, setCurrentPage] = useState(page); const [previousScores, setPreviousScores] = useState(); @@ -31,11 +34,25 @@ export default function PlayerScores({ player, sort, page }: Props) { staleTime: 30 * 1000, // Data will be cached for 30 seconds }); + const handleAnimation = useCallback(() => { + controls.set({ + x: -50, + opacity: 0, + }); + controls.start({ + x: 0, + opacity: 1, + transition: { duration: 0.25 }, + }); + }, [controls]); + useEffect(() => { - if (data) { - setPreviousScores(data); + if (data == undefined) { + return; } - }, [data]); + setPreviousScores(data); + handleAnimation(); + }, [data, handleAnimation]); useEffect(() => { // Update URL and refetch data when currentSort or currentPage changes @@ -80,11 +97,13 @@ export default function PlayerScores({ player, sort, page }: Props) { ))}
-
- {previousScores.playerScores.map((playerScore, index) => ( - - ))} -
+ +
+ {previousScores.playerScores.map((playerScore, index) => ( + + ))} +
+