This commit is contained in:
parent
0a4708c6bc
commit
1ff7c246c3
@ -10,6 +10,7 @@ import Score from "@/components/Score";
|
||||
import { Spinner } from "@/components/Spinner";
|
||||
import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
|
||||
import { ScoresaberPlayerScore } from "@/schemas/scoresaber/playerScore";
|
||||
import { usePlayerScoresStore } from "@/store/playerScoresStore";
|
||||
import { useSettingsStore } from "@/store/settingsStore";
|
||||
import { formatNumber } from "@/utils/number";
|
||||
import { fetchScores, getPlayerInfo } from "@/utils/scoresaber/api";
|
||||
@ -22,7 +23,7 @@ import {
|
||||
} from "@heroicons/react/20/solid";
|
||||
import Image from "next/image";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import ReactCountryFlag from "react-country-flag";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
@ -60,13 +61,9 @@ const sortTypes: { [key: string]: SortType } = {
|
||||
const DEFAULT_SORT_TYPE = sortTypes.top;
|
||||
|
||||
export default function Player({ params }: { params: { id: string } }) {
|
||||
const settingsStore = useStore(useSettingsStore, (state) => {
|
||||
return {
|
||||
userId: state.userId,
|
||||
setUserId: state.setUserId,
|
||||
refreshProfile: state.refreshProfile,
|
||||
};
|
||||
});
|
||||
const settingsStore = useStore(useSettingsStore, (store) => store);
|
||||
const playerScoreStore = useStore(usePlayerScoresStore, (store) => store);
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
|
||||
@ -141,9 +138,42 @@ export default function Player({ params }: { params: { id: string } }) {
|
||||
[params.id, router, scores],
|
||||
);
|
||||
|
||||
function claimProfile() {
|
||||
const toastId: any = useRef(null);
|
||||
|
||||
async function claimProfile() {
|
||||
settingsStore?.setUserId(params.id);
|
||||
settingsStore?.refreshProfile();
|
||||
|
||||
const reponse = await playerScoreStore?.addPlayer(
|
||||
params.id,
|
||||
(page, totalPages) => {
|
||||
const autoClose = page == totalPages ? 5000 : false;
|
||||
|
||||
if (page == 1) {
|
||||
toastId.current = toast.info(
|
||||
`Fetching scores ${page}/${totalPages}`,
|
||||
{
|
||||
autoClose: autoClose,
|
||||
progress: page / totalPages,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
toast.update(toastId.current, {
|
||||
progress: page / totalPages,
|
||||
render: `Fetching scores ${page}/${totalPages}`,
|
||||
autoClose: autoClose,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Fetching scores for ${params.id} (${page}/${totalPages})`);
|
||||
},
|
||||
);
|
||||
if (reponse?.error) {
|
||||
toast.error("Failed to claim profile");
|
||||
console.log(reponse.message);
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success("Successfully claimed profile");
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,12 @@ import Navbar from "./Navbar";
|
||||
export default function Container({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<ToastContainer className="z-50" position="top-right" theme="dark" />
|
||||
<ToastContainer
|
||||
className="z-50"
|
||||
position="top-right"
|
||||
theme="dark"
|
||||
pauseOnFocusLoss={false}
|
||||
/>
|
||||
<div className="m-auto flex h-screen min-h-full flex-col items-center opacity-90 md:max-w-[1200px]">
|
||||
<Navbar />
|
||||
<div className="w-full flex-1">{children}</div>
|
||||
|
@ -41,12 +41,7 @@ function NavbarButton({ text, icon, href, children }: ButtonProps) {
|
||||
}
|
||||
|
||||
export default function Navbar() {
|
||||
const settingsStore = useStore(useSettingsStore, (state) => {
|
||||
return {
|
||||
profilePicture: state.profilePicture,
|
||||
userId: state.userId,
|
||||
};
|
||||
});
|
||||
const settingsStore = useStore(useSettingsStore, (state) => state);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
160
src/store/playerScoresStore.ts
Normal file
160
src/store/playerScoresStore.ts
Normal file
@ -0,0 +1,160 @@
|
||||
"use client";
|
||||
|
||||
import { ScoresaberPlayerScore } from "@/schemas/scoresaber/playerScore";
|
||||
import { fetchAllScores, fetchScores } from "@/utils/scoresaber/api";
|
||||
import { create } from "zustand";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
|
||||
type Player = {
|
||||
lastUpdated: Date;
|
||||
id: string;
|
||||
scores: ScoresaberPlayerScore[];
|
||||
};
|
||||
|
||||
interface PlayerScoresStore {
|
||||
players: Player[];
|
||||
|
||||
exists: (playerId: string) => boolean;
|
||||
get(playerId: string): Player | undefined;
|
||||
|
||||
addPlayer: (
|
||||
playerId: string,
|
||||
callback?: (page: number, totalPages: number) => void,
|
||||
) => Promise<{
|
||||
error: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
updatePlayerScores: () => void;
|
||||
}
|
||||
|
||||
const UPDATE_INTERVAL = 1000 * 60 * 60; // 1 hour
|
||||
|
||||
export const usePlayerScoresStore = create<PlayerScoresStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
players: [],
|
||||
|
||||
exists: (playerId: string) => {
|
||||
const players: Player[] = usePlayerScoresStore.getState().players;
|
||||
return players.some((player) => player.id == playerId);
|
||||
},
|
||||
|
||||
get: (playerId: string) => {
|
||||
const players: Player[] = usePlayerScoresStore.getState().players;
|
||||
return players.find((player) => player.id == playerId);
|
||||
},
|
||||
|
||||
addPlayer: async (
|
||||
playerId: string,
|
||||
callback?: (page: number, totalPages: number) => void,
|
||||
) => {
|
||||
const players = usePlayerScoresStore.getState().players;
|
||||
|
||||
// Check if the player already exists
|
||||
if (usePlayerScoresStore.getState().exists(playerId)) {
|
||||
return {
|
||||
error: true,
|
||||
message: "Player already exists",
|
||||
};
|
||||
}
|
||||
|
||||
// Get all of the players scores
|
||||
const scores = await fetchAllScores(
|
||||
playerId,
|
||||
"recent",
|
||||
(page, totalPages) => {
|
||||
if (callback) callback(page, totalPages);
|
||||
},
|
||||
);
|
||||
|
||||
if (scores == undefined) {
|
||||
return {
|
||||
error: true,
|
||||
message: "Could not fetch scores for player",
|
||||
};
|
||||
}
|
||||
|
||||
console.log(scores);
|
||||
|
||||
set({
|
||||
players: [
|
||||
...players,
|
||||
{
|
||||
id: playerId,
|
||||
lastUpdated: new Date(),
|
||||
scores: scores,
|
||||
},
|
||||
],
|
||||
});
|
||||
return {
|
||||
error: false,
|
||||
message: "Player added successfully",
|
||||
};
|
||||
},
|
||||
|
||||
updatePlayerScores: async () => {
|
||||
const players = usePlayerScoresStore.getState().players;
|
||||
|
||||
for (const player of players) {
|
||||
if (player == undefined) continue;
|
||||
|
||||
// Skip if the player was already updated recently
|
||||
if (player.lastUpdated > new Date(Date.now() - UPDATE_INTERVAL))
|
||||
continue;
|
||||
|
||||
console.log(`Updating scores for ${player.id}...`);
|
||||
|
||||
let oldScores = player.scores;
|
||||
|
||||
// Sort the scores by id, so we know when to stop searching for new scores
|
||||
oldScores = oldScores.sort((a, b) => b.score.id - a.score.id);
|
||||
|
||||
const mostRecentScore = oldScores?.[0].score;
|
||||
if (mostRecentScore == undefined) continue;
|
||||
let search = true;
|
||||
|
||||
let page = 0;
|
||||
let newScoresCount = 0;
|
||||
while (search) {
|
||||
page++;
|
||||
const newScores = await fetchScores(player.id, page);
|
||||
if (newScores == undefined) continue;
|
||||
|
||||
for (const newScore of newScores.scores) {
|
||||
if (newScore.score.id == mostRecentScore.id) {
|
||||
search = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// remove the old score
|
||||
const oldScoreIndex = oldScores.findIndex(
|
||||
(score) => score.score.id == newScore.score.id,
|
||||
);
|
||||
if (oldScoreIndex != -1) {
|
||||
oldScores = oldScores.splice(oldScoreIndex, 1);
|
||||
}
|
||||
oldScores.push(newScore);
|
||||
newScoresCount++;
|
||||
}
|
||||
}
|
||||
|
||||
let newPlayers = players;
|
||||
// Remove the player if it already exists
|
||||
newPlayers = newPlayers.filter((playerr) => playerr.id != player.id);
|
||||
// Add the player
|
||||
newPlayers.push({
|
||||
lastUpdated: new Date(),
|
||||
id: player.id,
|
||||
scores: oldScores,
|
||||
});
|
||||
|
||||
console.log(`Found ${newScoresCount} new scores for ${player.id}`);
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "playerScores",
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
},
|
||||
),
|
||||
);
|
@ -22,7 +22,9 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||
setUserId: (userId: string) => {
|
||||
set({ userId });
|
||||
},
|
||||
|
||||
setProfilePicture: (profilePicture: string) => set({ profilePicture }),
|
||||
|
||||
async refreshProfile() {
|
||||
const id = useSettingsStore.getState().userId;
|
||||
if (!id) return;
|
||||
|
@ -120,6 +120,7 @@ export async function fetchScores(
|
||||
export async function fetchAllScores(
|
||||
playerId: string,
|
||||
searchType: string,
|
||||
callback?: (currentPage: number, totalPages: number) => void,
|
||||
): Promise<ScoresaberPlayerScore[] | undefined> {
|
||||
const scores = new Array();
|
||||
|
||||
@ -131,15 +132,21 @@ export async function fetchAllScores(
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
const { scores } = response;
|
||||
if (scores.length === 0) {
|
||||
const { scores: scoresFetched } = response;
|
||||
if (scoresFetched.length === 0) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
scores.push(...scores);
|
||||
scores.push(...scoresFetched);
|
||||
|
||||
if (callback) {
|
||||
callback(page, response.pageInfo.totalPages);
|
||||
}
|
||||
page++;
|
||||
} while (!done);
|
||||
|
||||
console.log(scores);
|
||||
|
||||
return scores as ScoresaberPlayerScore[];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user