add a simple page switch animation and show if the player is inactive or banned
All checks were successful
Deploy SSR / deploy (push) Successful in 1m38s

This commit is contained in:
Lee 2024-09-12 12:27:20 +01:00
parent 763de454e7
commit 4d19fd3bfd
7 changed files with 81 additions and 33 deletions

@ -25,6 +25,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dexie": "^4.0.8", "dexie": "^4.0.8",
"dexie-react-hooks": "^1.1.7", "dexie-react-hooks": "^1.1.7",
"framer-motion": "^11.5.4",
"ky": "^1.7.2", "ky": "^1.7.2",
"lucide-react": "^0.439.0", "lucide-react": "^0.439.0",
"next": "14.2.9", "next": "14.2.9",

@ -53,6 +53,9 @@ dependencies:
dexie-react-hooks: dexie-react-hooks:
specifier: ^1.1.7 specifier: ^1.1.7
version: 1.1.7(@types/react@18.3.5)(dexie@4.0.8)(react@18.3.1) 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: ky:
specifier: ^1.7.2 specifier: ^1.7.2
version: 1.7.2 version: 1.7.2
@ -2038,6 +2041,25 @@ packages:
cross-spawn: 7.0.3 cross-spawn: 7.0.3
signal-exit: 4.1.0 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: /fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true dev: true

@ -1,13 +0,0 @@
type Props = {
icon?: React.ReactNode;
children: React.ReactNode;
};
export default function PlayerDataPoint({ icon, children }: Props) {
return (
<div className="flex gap-1 items-center">
{icon}
{children}
</div>
);
}

@ -31,7 +31,11 @@ export default function PlayerData({ initalPlayerData, sort, page }: Props) {
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<PlayerHeader player={player} /> <PlayerHeader player={player} />
{!player.inactive && (
<>
<PlayerRankChart player={player} /> <PlayerRankChart player={player} />
</>
)}
<PlayerScores player={player} sort={sort} page={page} /> <PlayerScores player={player} sort={sort} page={page} />
</div> </div>
); );

@ -5,10 +5,10 @@ import Card from "../card";
import CountryFlag from "../country-flag"; import CountryFlag from "../country-flag";
import { Avatar, AvatarImage } from "../ui/avatar"; import { Avatar, AvatarImage } from "../ui/avatar";
import ClaimProfile from "./claim-profile"; import ClaimProfile from "./claim-profile";
import PlayerDataPoint from "./player-data-point";
const playerSubNames = [ const playerData = [
{ {
showWhenInactiveOrBanned: false,
icon: () => { icon: () => {
return <GlobeAmericasIcon className="h-5 w-5" />; return <GlobeAmericasIcon className="h-5 w-5" />;
}, },
@ -17,6 +17,7 @@ const playerSubNames = [
}, },
}, },
{ {
showWhenInactiveOrBanned: false,
icon: (player: ScoreSaberPlayer) => { icon: (player: ScoreSaberPlayer) => {
return <CountryFlag country={player.country.toLowerCase()} size={15} />; return <CountryFlag country={player.country.toLowerCase()} size={15} />;
}, },
@ -25,6 +26,7 @@ const playerSubNames = [
}, },
}, },
{ {
showWhenInactiveOrBanned: true,
render: (player: ScoreSaberPlayer) => { render: (player: ScoreSaberPlayer) => {
return <p className="text-pp">{formatNumberWithCommas(player.pp)}pp</p>; return <p className="text-pp">{formatNumberWithCommas(player.pp)}pp</p>;
}, },
@ -44,15 +46,27 @@ export default function PlayerHeader({ player }: Props) {
</Avatar> </Avatar>
<div> <div>
<p className="font-bold text-2xl">{player.name}</p> <p className="font-bold text-2xl">{player.name}</p>
<div className="flex flex-col">
<div>
{player.inactive && <p className="text-gray-400">Inactive Account</p>}
{player.banned && <p className="text-red-500">Banned Account</p>}
</div>
<div className="flex gap-2"> <div className="flex gap-2">
{playerSubNames.map((subName, index) => { {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 ( return (
<PlayerDataPoint icon={subName.icon && subName.icon(player)} key={index}> <div key={index} className="flex gap-1 items-center">
{subName.render(player)} {subName.icon && subName.icon(player)}
</PlayerDataPoint> {subName.render && subName.render(player)}
</div>
); );
})} })}
</div> </div>
</div>
<div className="absolute top-0 right-0"> <div className="absolute top-0 right-0">
<ClaimProfile playerId={player.id} /> <ClaimProfile playerId={player.id} />
</div> </div>

@ -7,7 +7,8 @@ import ScoreSaberPlayerScoresPage from "@/common/data-fetcher/types/scoresaber/s
import { capitalizeFirstLetter } from "@/common/string-utils"; import { capitalizeFirstLetter } from "@/common/string-utils";
import useWindowDimensions from "@/hooks/use-window-dimensions"; import useWindowDimensions from "@/hooks/use-window-dimensions";
import { useQuery } from "@tanstack/react-query"; 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 Card from "../card";
import Pagination from "../input/pagination"; import Pagination from "../input/pagination";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
@ -21,6 +22,8 @@ type Props = {
export default function PlayerScores({ player, sort, page }: Props) { export default function PlayerScores({ player, sort, page }: Props) {
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
const controls = useAnimation();
const [currentSort, setCurrentSort] = useState(sort); const [currentSort, setCurrentSort] = useState(sort);
const [currentPage, setCurrentPage] = useState(page); const [currentPage, setCurrentPage] = useState(page);
const [previousScores, setPreviousScores] = useState<ScoreSaberPlayerScoresPage | undefined>(); const [previousScores, setPreviousScores] = useState<ScoreSaberPlayerScoresPage | undefined>();
@ -31,11 +34,25 @@ export default function PlayerScores({ player, sort, page }: Props) {
staleTime: 30 * 1000, // Data will be cached for 30 seconds 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(() => { useEffect(() => {
if (data) { if (data == undefined) {
setPreviousScores(data); return;
} }
}, [data]); setPreviousScores(data);
handleAnimation();
}, [data, handleAnimation]);
useEffect(() => { useEffect(() => {
// Update URL and refetch data when currentSort or currentPage changes // Update URL and refetch data when currentSort or currentPage changes
@ -80,11 +97,13 @@ export default function PlayerScores({ player, sort, page }: Props) {
))} ))}
</div> </div>
<motion.div animate={controls}>
<div className="grid min-w-full grid-cols-1 divide-y divide-border"> <div className="grid min-w-full grid-cols-1 divide-y divide-border">
{previousScores.playerScores.map((playerScore, index) => ( {previousScores.playerScores.map((playerScore, index) => (
<Score key={index} playerScore={playerScore} /> <Score key={index} playerScore={playerScore} />
))} ))}
</div> </div>
</motion.div>
<Pagination <Pagination
mobilePagination={width < 768} mobilePagination={width < 768}

@ -84,6 +84,7 @@ export default function Score({ playerScore }: Props) {
height={64} height={64}
alt="Song Artwork" alt="Song Artwork"
className="rounded-md min-w-[64px]" className="rounded-md min-w-[64px]"
priority
/> />
</div> </div>
<div className="flex"> <div className="flex">