add player stat badges
All checks were successful
Deploy SSR / deploy (push) Successful in 1m12s

This commit is contained in:
Lee 2024-09-12 19:09:54 +01:00
parent 17c40edc3a
commit e8d864e531
5 changed files with 114 additions and 25 deletions

@ -5,6 +5,7 @@ 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 PlayerStats from "./player-stats";
const playerData = [ const playerData = [
{ {
@ -40,33 +41,38 @@ type Props = {
export default function PlayerHeader({ player }: Props) { export default function PlayerHeader({ player }: Props) {
return ( return (
<Card> <Card>
<div className="flex gap-3 flex-col items-center text-center sm:flex-row sm:items-start sm:text-start relative select-none"> <div className="flex gap-3 flex-col items-center text-center lg:flex-row lg:items-start lg:text-start relative select-none">
<Avatar className="w-32 h-32 pointer-events-none"> <Avatar className="w-32 h-32 pointer-events-none">
<AvatarImage fetchPriority="high" src={player.profilePicture} /> <AvatarImage fetchPriority="high" src={player.profilePicture} />
</Avatar> </Avatar>
<div> <div className="w-full flex gap-2 flex-col justify-center items-center lg:justify-start lg:items-start">
<p className="font-bold text-2xl">{player.name}</p> <div>
<div className="flex flex-col"> <p className="font-bold text-2xl">{player.name}</p>
<div> <div className="flex flex-col">
{player.inactive && <p className="text-gray-400">Inactive Account</p>} <div>
{player.banned && <p className="text-red-500">Banned Account</p>} {player.inactive && <p className="text-gray-400">Inactive Account</p>}
</div> {player.banned && <p className="text-red-500">Banned Account</p>}
<div className="flex gap-2"> </div>
{playerData.map((subName, index) => { <div className="flex gap-2">
// Check if the player is inactive or banned and if the data should be shown {playerData.map((subName, index) => {
if (!subName.showWhenInactiveOrBanned && (player.inactive || player.banned)) { // Check if the player is inactive or banned and if the data should be shown
return null; if (!subName.showWhenInactiveOrBanned && (player.inactive || player.banned)) {
} return null;
}
return ( return (
<div key={index} className="flex gap-1 items-center"> <div key={index} className="flex gap-1 items-center">
{subName.icon && subName.icon(player)} {subName.icon && subName.icon(player)}
{subName.render && subName.render(player)} {subName.render && subName.render(player)}
</div> </div>
); );
})} })}
</div>
</div> </div>
</div> </div>
<PlayerStats player={player} />
<div className="absolute top-0 right-0"> <div className="absolute top-0 right-0">
<ClaimProfile playerId={player.id} /> <ClaimProfile playerId={player.id} />
</div> </div>

@ -97,7 +97,7 @@ export default function PlayerRankChart({ player }: Props) {
lineTension: 0.5, lineTension: 0.5,
data: playerRankHistory, data: playerRankHistory,
label: "Rank", label: "Rank",
borderColor: "#c084fc", borderColor: "#a147fa",
fill: false, fill: false,
color: "#fff", color: "#fff",
}, },

@ -0,0 +1,71 @@
import ScoreSaberPlayer from "@/common/data-fetcher/types/scoresaber/scoresaber-player";
import { formatNumberWithCommas } from "@/common/number-utils";
import StatValue from "@/components/stat-value";
import { ClassValue } from "clsx";
type Badge = {
name: string;
color?: ClassValue;
create: (player: ScoreSaberPlayer) => string | React.ReactNode | undefined;
};
const badges: Badge[] = [
{
name: "Ranked Play Count",
color: "bg-pp",
create: (player: ScoreSaberPlayer) => {
return formatNumberWithCommas(player.scoreStats.rankedPlayCount);
},
},
{
name: "Total Ranked Score",
color: "bg-pp",
create: (player: ScoreSaberPlayer) => {
return formatNumberWithCommas(player.scoreStats.totalRankedScore);
},
},
{
name: "Average Ranked Accuracy",
color: "bg-pp",
create: (player: ScoreSaberPlayer) => {
return player.scoreStats.averageRankedAccuracy.toFixed(2) + "%";
},
},
{
name: "Total Play Count",
create: (player: ScoreSaberPlayer) => {
return formatNumberWithCommas(player.scoreStats.totalPlayCount);
},
},
{
name: "Total Score",
create: (player: ScoreSaberPlayer) => {
return formatNumberWithCommas(player.scoreStats.totalScore);
},
},
{
name: "Total Replays Watched",
create: (player: ScoreSaberPlayer) => {
return formatNumberWithCommas(player.scoreStats.replaysWatched);
},
},
];
type Props = {
player: ScoreSaberPlayer;
};
export default function PlayerStats({ player }: Props) {
return (
<div className={`flex flex-wrap gap-2 w-full justify-center lg:justify-start`}>
{badges.map((badge, index) => {
const toRender = badge.create(player);
if (toRender === undefined) {
return <div key={index} />;
}
return <StatValue key={index} color={badge.color} name={badge.name} value={toRender} />;
})}
</div>
);
}

@ -1,18 +1,30 @@
import clsx, { ClassValue } from "clsx";
type Props = { type Props = {
/** /**
* The stat name. * The stat name.
*/ */
name?: string; name?: string;
/**
* The background color of the stat.
*/
color?: ClassValue;
/** /**
* The value of the stat. * The value of the stat.
*/ */
value: React.ReactNode; value: React.ReactNode;
}; };
export default function StatValue({ name, value }: Props) { export default function StatValue({ name, color, value }: Props) {
return ( return (
<div className="flex min-w-16 gap-2 bg-accent h-[28px] p-1 items-center justify-center rounded-md text-sm"> <div
className={clsx(
"flex min-w-16 gap-2 h-[28px] p-1 items-center justify-center rounded-md text-sm",
color ? color : "bg-accent"
)}
>
{name && ( {name && (
<> <>
<p>{name}</p> <p>{name}</p>

@ -10,7 +10,7 @@ const config: Config = {
theme: { theme: {
extend: { extend: {
colors: { colors: {
pp: "#c084fc", pp: "#a147fa",
background: "hsl(var(--background))", background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))", foreground: "hsl(var(--foreground))",
card: { card: {