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
All checks were successful
Deploy SSR / deploy (push) Successful in 1m38s
This commit is contained in:
parent
763de454e7
commit
4d19fd3bfd
@ -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",
|
||||||
|
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@ -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">
|
||||||
|
Reference in New Issue
Block a user