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",
"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",

@ -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

@ -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 (
<div className="flex flex-col gap-2">
<PlayerHeader player={player} />
{!player.inactive && (
<>
<PlayerRankChart player={player} />
</>
)}
<PlayerScores player={player} sort={sort} page={page} />
</div>
);

@ -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 <GlobeAmericasIcon className="h-5 w-5" />;
},
@ -17,6 +17,7 @@ const playerSubNames = [
},
},
{
showWhenInactiveOrBanned: false,
icon: (player: ScoreSaberPlayer) => {
return <CountryFlag country={player.country.toLowerCase()} size={15} />;
},
@ -25,6 +26,7 @@ const playerSubNames = [
},
},
{
showWhenInactiveOrBanned: true,
render: (player: ScoreSaberPlayer) => {
return <p className="text-pp">{formatNumberWithCommas(player.pp)}pp</p>;
},
@ -44,15 +46,27 @@ export default function PlayerHeader({ player }: Props) {
</Avatar>
<div>
<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">
{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 (
<PlayerDataPoint icon={subName.icon && subName.icon(player)} key={index}>
{subName.render(player)}
</PlayerDataPoint>
<div key={index} className="flex gap-1 items-center">
{subName.icon && subName.icon(player)}
{subName.render && subName.render(player)}
</div>
);
})}
</div>
</div>
<div className="absolute top-0 right-0">
<ClaimProfile playerId={player.id} />
</div>

@ -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<ScoreSaberPlayerScoresPage | undefined>();
@ -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) {
))}
</div>
<motion.div animate={controls}>
<div className="grid min-w-full grid-cols-1 divide-y divide-border">
{previousScores.playerScores.map((playerScore, index) => (
<Score key={index} playerScore={playerScore} />
))}
</div>
</motion.div>
<Pagination
mobilePagination={width < 768}

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