Update src/components/player/PlayerInfo.tsx
All checks were successful
deploy / deploy (push) Successful in 55s
All checks were successful
deploy / deploy (push) Successful in 55s
This commit is contained in:
parent
49eb341cb6
commit
dd7f78b662
@ -1,265 +1,259 @@
|
|||||||
import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
|
import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
|
||||||
import { useScoresaberScoresStore } from "@/store/scoresaberScoresStore";
|
import { useScoresaberScoresStore } from "@/store/scoresaberScoresStore";
|
||||||
import { useSettingsStore } from "@/store/settingsStore";
|
import { useSettingsStore } from "@/store/settingsStore";
|
||||||
import { formatNumber } from "@/utils/number";
|
import { formatNumber } from "@/utils/number";
|
||||||
import {
|
import {
|
||||||
calcPpBoundary,
|
calcPpBoundary,
|
||||||
getAveragePp,
|
getAveragePp,
|
||||||
getHighestPpPlay,
|
getHighestPpPlay,
|
||||||
getTotalScores,
|
getTotalScores,
|
||||||
} from "@/utils/scoresaber/scores";
|
} from "@/utils/scoresaber/scores";
|
||||||
import {
|
import {
|
||||||
GlobeAsiaAustraliaIcon,
|
GlobeAsiaAustraliaIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
} from "@heroicons/react/20/solid";
|
} from "@heroicons/react/20/solid";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { useStore } from "zustand";
|
import { useStore } from "zustand";
|
||||||
import Avatar from "../Avatar";
|
import Avatar from "../Avatar";
|
||||||
import Card from "../Card";
|
import Card from "../Card";
|
||||||
import Label from "../Label";
|
import Label from "../Label";
|
||||||
|
|
||||||
const PlayerChart = dynamic(() => import("./PlayerChart"));
|
const PlayerChart = dynamic(() => import("./PlayerChart"));
|
||||||
const ReactCountryFlag = dynamic(() => import("react-country-flag"));
|
const ReactCountryFlag = dynamic(() => import("react-country-flag"));
|
||||||
|
|
||||||
type PlayerInfoProps = {
|
type PlayerInfoProps = {
|
||||||
playerData: ScoresaberPlayer;
|
playerData: ScoresaberPlayer;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PlayerInfo({ playerData }: PlayerInfoProps) {
|
export default function PlayerInfo({ playerData }: PlayerInfoProps) {
|
||||||
const playerId = playerData.id;
|
const playerId = playerData.id;
|
||||||
const settingsStore = useStore(useSettingsStore, (store) => store);
|
const settingsStore = useStore(useSettingsStore, (store) => store);
|
||||||
const playerScoreStore = useStore(useScoresaberScoresStore, (store) => store);
|
const playerScoreStore = useStore(useScoresaberScoresStore, (store) => store);
|
||||||
|
|
||||||
// Whether we have scores for this player in the local database
|
// Whether we have scores for this player in the local database
|
||||||
const hasLocalScores = playerScoreStore?.exists(playerId);
|
const hasLocalScores = playerScoreStore?.exists(playerId);
|
||||||
|
|
||||||
const toastId: any = useRef(null);
|
const toastId: any = useRef(null);
|
||||||
|
|
||||||
async function claimProfile() {
|
async function claimProfile() {
|
||||||
settingsStore?.setProfile(playerData);
|
settingsStore?.setProfile(playerData);
|
||||||
addProfile(false);
|
addProfile(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addFriend() {
|
async function addFriend() {
|
||||||
const friend = await settingsStore?.addFriend(playerData.id);
|
const friend = await settingsStore?.addFriend(playerData.id);
|
||||||
if (!friend) {
|
if (!friend) {
|
||||||
toast.error(`Failed to add ${playerData.name} as a friend`);
|
toast.error(`Failed to add ${playerData.name} as a friend`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addProfile(true);
|
addProfile(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeFriend() {
|
async function removeFriend() {
|
||||||
settingsStore?.removeFriend(playerData.id);
|
settingsStore?.removeFriend(playerData.id);
|
||||||
|
|
||||||
toast.success(`Successfully removed ${playerData.name} as a friend`);
|
toast.success(`Successfully removed ${playerData.name} as a friend`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addProfile(isFriend: boolean) {
|
async function addProfile(isFriend: boolean) {
|
||||||
if (!useScoresaberScoresStore.getState().exists(playerId)) {
|
if (!useScoresaberScoresStore.getState().exists(playerId)) {
|
||||||
if (!isFriend) {
|
if (!isFriend) {
|
||||||
toast.success(`Successfully set ${playerData.name} as your profile`);
|
toast.success(`Successfully set ${playerData.name} as your profile`);
|
||||||
} else {
|
} else {
|
||||||
toast.success(`Successfully added ${playerData.name} as a friend`);
|
toast.success(`Successfully added ${playerData.name} as a friend`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const reponse = await playerScoreStore?.addOrUpdatePlayer(
|
const reponse = await playerScoreStore?.addOrUpdatePlayer(
|
||||||
playerId,
|
playerId,
|
||||||
(page, totalPages) => {
|
(page, totalPages) => {
|
||||||
const autoClose = page == totalPages ? 5000 : false;
|
const autoClose = page == totalPages ? 5000 : false;
|
||||||
|
|
||||||
if (page == 1) {
|
if (page == 1) {
|
||||||
toastId.current = toast.info(
|
toastId.current = toast.info(
|
||||||
`Fetching scores for ${playerData.name} page ${page}/${totalPages}`,
|
`Fetching scores for ${playerData.name} page ${page}/${totalPages}`,
|
||||||
{
|
{
|
||||||
autoClose: autoClose,
|
autoClose: autoClose,
|
||||||
progress: page / totalPages,
|
progress: page / totalPages,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (page != totalPages) {
|
if (page != totalPages) {
|
||||||
toast.update(toastId.current, {
|
toast.update(toastId.current, {
|
||||||
progress: page / totalPages,
|
progress: page / totalPages,
|
||||||
render: `Fetching scores for ${playerData.name} page ${page}/${totalPages}`,
|
render: `Fetching scores for ${playerData.name} page ${page}/${totalPages}`,
|
||||||
autoClose: autoClose,
|
autoClose: autoClose,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast.update(toastId.current, {
|
toast.update(toastId.current, {
|
||||||
progress: 0,
|
progress: 0,
|
||||||
render: `Successfully fetched scores for ${playerData.name}`,
|
render: `Successfully fetched scores for ${playerData.name}`,
|
||||||
autoClose: autoClose,
|
autoClose: autoClose,
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Fetching scores for ${playerId} (${page}/${totalPages})`,
|
`Fetching scores for ${playerId} (${page}/${totalPages})`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (reponse?.error) {
|
if (reponse?.error) {
|
||||||
toast.error("Failed to fetch scores");
|
toast.error("Failed to fetch scores");
|
||||||
console.log(reponse.message);
|
console.log(reponse.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOwnProfile = settingsStore.player?.id == playerId;
|
const isOwnProfile = settingsStore.player?.id == playerId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="mt-2">
|
<Card className="mt-2">
|
||||||
{/* Player Info */}
|
{/* Player Info */}
|
||||||
<div className="flex flex-col items-center gap-3 md:flex-row md:items-start">
|
<div className="flex flex-col items-center gap-3 md:flex-row md:items-start">
|
||||||
<div className="min-w-fit">
|
<div className="min-w-fit">
|
||||||
{/* Avatar */}
|
{/* Avatar */}
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-2">
|
||||||
<Avatar url={playerData.profilePicture} label="Avatar" />
|
<Avatar url={playerData.profilePicture} label="Avatar" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Settings Buttons */}
|
{/* Settings Buttons */}
|
||||||
<div className="absolute right-3 top-20 flex flex-col justify-end gap-2 md:relative md:right-0 md:top-0 md:mt-2 md:flex-row md:justify-center">
|
<div className="absolute right-3 top-20 flex flex-col justify-end gap-2 md:relative md:right-0 md:top-0 md:mt-2 md:flex-row md:justify-center">
|
||||||
{!isOwnProfile && (
|
{!isOwnProfile && (
|
||||||
<button
|
<button
|
||||||
className="h-fit w-fit rounded-md bg-blue-500 p-1 hover:bg-blue-600"
|
className="h-fit w-fit rounded-md bg-blue-500 p-1 hover:bg-blue-600"
|
||||||
onClick={claimProfile}
|
onClick={claimProfile}
|
||||||
>
|
>
|
||||||
<HomeIcon title="Set as your Profile" width={24} height={24} />
|
<HomeIcon title="Set as your Profile" width={24} height={24} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isOwnProfile && (
|
{!isOwnProfile && (
|
||||||
<>
|
<>
|
||||||
{!settingsStore?.isFriend(playerId) && (
|
{!settingsStore?.isFriend(playerId) && (
|
||||||
<button
|
<button
|
||||||
className="rounded-md bg-blue-500 p-1 hover:opacity-80"
|
className="rounded-md bg-blue-500 p-1 hover:opacity-80"
|
||||||
onClick={addFriend}
|
onClick={addFriend}
|
||||||
>
|
>
|
||||||
<UserIcon title="Add as Friend" width={24} height={24} />
|
<UserIcon title="Add as Friend" width={24} height={24} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{settingsStore.isFriend(playerId) && (
|
{settingsStore.isFriend(playerId) && (
|
||||||
<button
|
<button
|
||||||
className="rounded-md bg-red-500 p-1 hover:opacity-80"
|
className="rounded-md bg-red-500 p-1 hover:opacity-80"
|
||||||
onClick={removeFriend}
|
onClick={removeFriend}
|
||||||
>
|
>
|
||||||
<XMarkIcon title="Remove Friend" width={24} height={24} />
|
<XMarkIcon title="Remove Friend" width={24} height={24} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 flex w-full flex-col items-center gap-2 md:items-start">
|
<div className="mt-1 flex w-full flex-col items-center gap-2 md:items-start">
|
||||||
{/* Name */}
|
{/* Name */}
|
||||||
<p className="text-2xl leading-none">{playerData.name}</p>
|
<p className="text-2xl leading-none">{playerData.name}</p>
|
||||||
|
|
||||||
<div className="flex items-center gap-3 text-xl">
|
<div className="flex items-center gap-3 text-xl">
|
||||||
{/* Global Rank */}
|
{/* Global Rank */}
|
||||||
<div className="flex items-center gap-1 text-gray-300">
|
<div className="flex items-center gap-1 text-gray-300">
|
||||||
<GlobeAsiaAustraliaIcon width={32} height={32} />
|
<GlobeAsiaAustraliaIcon width={32} height={32} />
|
||||||
|
|
||||||
<a
|
<a
|
||||||
className="flex transform-gpu items-center gap-1 transition-all hover:text-blue-500"
|
className="flex transform-gpu items-center gap-1 transition-all hover:text-blue-500"
|
||||||
href={`/ranking/global/?page=${Math.round(
|
href={`/ranking/global/?page=${Math.round(
|
||||||
playerData.rank / 50,
|
playerData.rank / 50,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
<p>#{formatNumber(playerData.rank)}</p>
|
<p>#{formatNumber(playerData.rank)}</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Country Rank */}
|
{/* Country Rank */}
|
||||||
<div className="text-gray-300">
|
<div className="text-gray-300">
|
||||||
<a
|
<a
|
||||||
className="flex transform-gpu items-center gap-1 transition-all hover:text-blue-500"
|
className="flex transform-gpu items-center gap-1 transition-all hover:text-blue-500"
|
||||||
href={`/ranking/country/${playerData.country}?page=${Math.round(
|
href={`/ranking/country/${playerData.country}?page=${Math.round(
|
||||||
playerData.countryRank / 50,
|
playerData.countryRank / 50,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
<ReactCountryFlag
|
<ReactCountryFlag
|
||||||
countryCode={playerData.country}
|
countryCode={playerData.country}
|
||||||
svg
|
svg
|
||||||
className="!h-7 !w-7"
|
className="!h-7 !w-7"
|
||||||
/>
|
/>
|
||||||
<p>#{formatNumber(playerData.countryRank)}</p>
|
<p>#{formatNumber(playerData.countryRank)}</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* PP */}
|
{/* PP */}
|
||||||
<div className="flex items-center text-gray-300">
|
<div className="flex items-center text-gray-300">
|
||||||
<p>{formatNumber(playerData.pp)}pp</p>
|
<p>{formatNumber(playerData.pp)}pp</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Labels */}
|
{/* Labels */}
|
||||||
<div className="flex flex-wrap justify-center gap-2 md:justify-start">
|
<div className="flex flex-wrap justify-center gap-2 md:justify-start">
|
||||||
<Label
|
<Label
|
||||||
title="Total play count"
|
title="Total play count"
|
||||||
className="bg-blue-500"
|
className="bg-blue-500"
|
||||||
hoverValue="Total ranked song play count"
|
hoverValue="Total ranked song play count"
|
||||||
value={formatNumber(playerData.scoreStats.totalPlayCount)}
|
value={formatNumber(playerData.scoreStats.totalPlayCount)}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
title="Total score"
|
title="Total score"
|
||||||
className="bg-blue-500"
|
className="bg-blue-500"
|
||||||
hoverValue="Total score of all your plays"
|
hoverValue="Total score of all your plays"
|
||||||
value={formatNumber(playerData.scoreStats.totalScore)}
|
value={formatNumber(playerData.scoreStats.totalScore)}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
title="Avg ranked acc"
|
title="Avg ranked acc"
|
||||||
className="bg-blue-500"
|
className="bg-blue-500"
|
||||||
hoverValue="Average accuracy of all your ranked plays"
|
hoverValue="Average accuracy of all your ranked plays"
|
||||||
value={`${playerData.scoreStats.averageRankedAccuracy.toFixed(
|
value={`${playerData.scoreStats.averageRankedAccuracy.toFixed(
|
||||||
2,
|
2,
|
||||||
)}%`}
|
)}%`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{hasLocalScores && (
|
{hasLocalScores && (
|
||||||
<>
|
<>
|
||||||
<Label
|
<Label
|
||||||
title="Total Scores"
|
title="Top PP"
|
||||||
className="bg-blue-500"
|
className="bg-[#8992e8]"
|
||||||
hoverValue="Total amount of scores set"
|
hoverValue="Highest pp play"
|
||||||
value={`${formatNumber(getTotalScores(playerId))}`}
|
value={`${formatNumber(
|
||||||
/>
|
getHighestPpPlay(playerId)?.toFixed(2),
|
||||||
<Label
|
)}pp`}
|
||||||
title="Top PP"
|
/>
|
||||||
className="bg-[#8992e8]"
|
<Label
|
||||||
hoverValue="Highest pp play"
|
title="Avg PP"
|
||||||
value={`${formatNumber(
|
className="bg-[#8992e8]"
|
||||||
getHighestPpPlay(playerId)?.toFixed(2),
|
hoverValue="Average amount of pp per play (best 20 scores)"
|
||||||
)}pp`}
|
value={`${formatNumber(
|
||||||
/>
|
getAveragePp(playerId)?.toFixed(2),
|
||||||
<Label
|
)}pp`}
|
||||||
title="Avg PP"
|
/>
|
||||||
className="bg-[#8992e8]"
|
<Label
|
||||||
hoverValue="Average amount of pp per play (best 20 scores)"
|
title="+ 1pp"
|
||||||
value={`${formatNumber(
|
className="bg-[#8992e8]"
|
||||||
getAveragePp(playerId)?.toFixed(2),
|
hoverValue="Amount of raw pp required to increase your global pp by 1pp"
|
||||||
)}pp`}
|
value={`${formatNumber(
|
||||||
/>
|
calcPpBoundary(playerId, 1)?.toFixed(2),
|
||||||
<Label
|
)}pp raw per global`}
|
||||||
title="+ 1pp"
|
/>
|
||||||
className="bg-[#8992e8]"
|
</>
|
||||||
hoverValue="Amount of raw pp required to increase your global pp by 1pp"
|
)}
|
||||||
value={`${formatNumber(
|
</div>
|
||||||
calcPpBoundary(playerId, 1)?.toFixed(2),
|
|
||||||
)}pp raw per global`}
|
{/* Chart */}
|
||||||
/>
|
<PlayerChart scoresaber={playerData} />
|
||||||
</>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
|
);
|
||||||
{/* Chart */}
|
}
|
||||||
<PlayerChart scoresaber={playerData} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user