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

View File

@ -5,6 +5,7 @@ import Card from "../card";
import CountryFlag from "../country-flag";
import { Avatar, AvatarImage } from "../ui/avatar";
import ClaimProfile from "./claim-profile";
import PlayerStats from "./player-stats";
const playerData = [
{
@ -40,33 +41,38 @@ type Props = {
export default function PlayerHeader({ player }: Props) {
return (
<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">
<AvatarImage fetchPriority="high" src={player.profilePicture} />
</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">
{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;
}
<div className="w-full flex gap-2 flex-col justify-center items-center lg:justify-start lg:items-start">
<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">
{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 (
<div key={index} className="flex gap-1 items-center">
{subName.icon && subName.icon(player)}
{subName.render && subName.render(player)}
</div>
);
})}
return (
<div key={index} className="flex gap-1 items-center">
{subName.icon && subName.icon(player)}
{subName.render && subName.render(player)}
</div>
);
})}
</div>
</div>
</div>
<PlayerStats player={player} />
<div className="absolute top-0 right-0">
<ClaimProfile playerId={player.id} />
</div>

View File

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

View File

@ -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>
);
}

View File

@ -1,18 +1,30 @@
import clsx, { ClassValue } from "clsx";
type Props = {
/**
* The stat name.
*/
name?: string;
/**
* The background color of the stat.
*/
color?: ClassValue;
/**
* The value of the stat.
*/
value: React.ReactNode;
};
export default function StatValue({ name, value }: Props) {
export default function StatValue({ name, color, value }: Props) {
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 && (
<>
<p>{name}</p>

View File

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