This repository has been archived on 2024-10-29. You can view files and clone it, but cannot push or open issues or pull requests.
Files
scoresaber-reloadedv3/projects/website/src/components/ranking/mini.tsx

113 lines
3.5 KiB
TypeScript
Raw Normal View History

2024-10-15 19:43:23 +01:00
import { formatNumberWithCommas, formatPp } from "@ssr/common/utils/number-utils";
import { GlobeAmericasIcon } from "@heroicons/react/24/solid";
import { useQuery } from "@tanstack/react-query";
import Link from "next/link";
import { ReactElement } from "react";
import Card from "../card";
import CountryFlag from "../country-flag";
2024-10-04 18:25:37 +01:00
import { PlayerRankingSkeleton } from "@/components/ranking/player-ranking-skeleton";
import ScoreSaberPlayer from "@ssr/common/player/impl/scoresaber-player";
2024-10-19 04:53:06 +01:00
import { getPlayersAroundPlayer } from "@ssr/common/utils/player-utils";
import { AroundPlayer } from "@ssr/common/types/around-player";
2024-10-19 15:31:02 +01:00
import { PlayerInfo } from "@/components/player/player-info";
2024-09-28 05:57:35 +01:00
const PLAYER_NAME_MAX_LENGTH = 18;
type MiniProps = {
2024-10-04 18:25:37 +01:00
/**
* The type of ranking to display.
*/
type: "Global" | "Country";
2024-10-04 18:25:37 +01:00
/**
* The player on this profile.
*/
2024-09-27 23:04:14 +01:00
player: ScoreSaberPlayer;
2024-10-04 18:25:37 +01:00
/**
* Whether the data should be updated
*/
shouldUpdate?: boolean;
};
type Variants = {
[key: string]: {
2024-09-27 23:04:14 +01:00
icon: (player: ScoreSaberPlayer) => ReactElement;
};
};
const miniVariants: Variants = {
Global: {
2024-10-19 15:37:25 +01:00
icon: () => <GlobeAmericasIcon className="w-5 h-5" />,
},
Country: {
2024-09-27 23:04:14 +01:00
icon: (player: ScoreSaberPlayer) => {
return <CountryFlag code={player.country} size={12} />;
},
},
};
2024-10-04 18:25:37 +01:00
export default function Mini({ type, player, shouldUpdate }: MiniProps) {
if (shouldUpdate == undefined) {
shouldUpdate = true;
}
2024-10-10 01:52:05 +01:00
const variant = miniVariants[type];
const icon = variant.icon(player);
2024-10-19 04:53:06 +01:00
const {
data: response,
isLoading,
isError,
} = useQuery({
queryKey: ["mini-ranking-" + type, player.id, type],
queryFn: () => getPlayersAroundPlayer(player.id, type.toLowerCase() as AroundPlayer),
2024-10-04 18:25:37 +01:00
enabled: shouldUpdate,
});
2024-10-19 04:53:06 +01:00
if (isLoading || !response) {
2024-10-10 01:52:05 +01:00
return <PlayerRankingSkeleton />;
}
if (isError) {
2024-10-10 01:52:05 +01:00
return <p className="text-red-500">Error loading ranking</p>;
}
return (
<Card className="flex gap-2 sticky select-none text-sm w-[400px]">
<div className="flex gap-2">
{icon}
<p>{type} Ranking</p>
</div>
2024-10-19 15:37:25 +01:00
<div className="flex flex-col text-xs">
2024-10-19 04:53:06 +01:00
{response.players.map((playerRanking, index) => {
const rank = type == "Global" ? playerRanking.rank : playerRanking.countryRank;
const ppDifference = playerRanking.pp - player.pp;
return (
<Link
key={index}
href={`/player/${playerRanking.id}`}
2024-10-19 17:48:29 +01:00
className="grid gap-2 grid-cols-[auto_1fr_auto] items-center bg-accent px-2 py-1.5 cursor-pointer transform-gpu transition-all hover:brightness-75 first:rounded-t last:rounded-b"
>
<p className="text-gray-400">#{formatNumberWithCommas(rank)}</p>
2024-10-04 18:25:37 +01:00
<div className="flex gap-2 items-center">
2024-10-19 17:48:29 +01:00
<PlayerInfo player={playerRanking} highlightedPlayer={player} hideCountryFlag hoverBrightness={false} />
</div>
2024-10-04 18:25:37 +01:00
<div className="inline-flex min-w-[11.5em] gap-2 items-center">
2024-10-22 12:26:58 +01:00
<p className="text-ssr text-right">{formatPp(playerRanking.pp)}pp</p>
{playerRanking.id !== player.id && (
<p className={`text-xs text-right ${ppDifference > 0 ? "text-green-400" : "text-red-400"}`}>
{ppDifference > 0 ? "+" : ""}
{formatPp(ppDifference)}
</p>
)}
</div>
</Link>
);
})}
</div>
</Card>
);
}