when selecting a sort type it will get stored and used as the new default
All checks were successful
deploy / deploy (push) Successful in 1m0s

This commit is contained in:
Lee 2023-10-22 02:47:03 +01:00
parent 1ff7c246c3
commit e9c80143ff
6 changed files with 84 additions and 38 deletions

@ -1,3 +1,4 @@
import AppProvider from "@/components/AppProvider";
import { Metadata } from "next";
import { Inter } from "next/font/google";
import Image from "next/image";
@ -40,7 +41,7 @@ export default function RootLayout({
/>
</div>
{children}
<AppProvider>{children}</AppProvider>
</body>
</html>
);

@ -12,15 +12,11 @@ import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
import { ScoresaberPlayerScore } from "@/schemas/scoresaber/playerScore";
import { usePlayerScoresStore } from "@/store/playerScoresStore";
import { useSettingsStore } from "@/store/settingsStore";
import { SortType, SortTypes } from "@/types/SortTypes";
import { formatNumber } from "@/utils/number";
import { fetchScores, getPlayerInfo } from "@/utils/scoresaber/api";
import useStore from "@/utils/useStore";
import {
ClockIcon,
GlobeAsiaAustraliaIcon,
HomeIcon,
TrophyIcon,
} from "@heroicons/react/20/solid";
import { GlobeAsiaAustraliaIcon, HomeIcon } from "@heroicons/react/20/solid";
import Image from "next/image";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useRef, useState } from "react";
@ -40,27 +36,11 @@ type PlayerInfo = {
player: ScoresaberPlayer | undefined;
};
type SortType = {
name: string;
value: string;
icon: JSX.Element;
};
const sortTypes: { [key: string]: SortType } = {
top: {
name: "Top Scores",
value: "top",
icon: <TrophyIcon width={20} height={20} />,
},
recent: {
name: "Recent Scores",
value: "recent",
icon: <ClockIcon width={20} height={20} />,
},
};
const DEFAULT_SORT_TYPE = sortTypes.top;
const DEFAULT_SORT_TYPE = SortTypes.top;
export default function Player({ params }: { params: { id: string } }) {
const [mounted, setMounted] = useState(false);
const settingsStore = useStore(useSettingsStore, (store) => store);
const playerScoreStore = useStore(usePlayerScoresStore, (store) => store);
@ -75,13 +55,14 @@ export default function Player({ params }: { params: { id: string } }) {
page = Number.parseInt(pageString) || 1;
}
let sortType;
let sortType: SortType;
const sortTypeString = searchParams.get("sort");
if (sortTypeString == null) {
// todo: check settings to get last used sort type
sortType = DEFAULT_SORT_TYPE;
sortType =
useSettingsStore.getState().lastUsedSortType || DEFAULT_SORT_TYPE;
} else {
sortType = sortTypes[sortTypeString] || DEFAULT_SORT_TYPE;
sortType = SortTypes[sortTypeString] || DEFAULT_SORT_TYPE;
}
const [error, setError] = useState(false);
@ -119,6 +100,9 @@ export default function Player({ params }: { params: { id: string } }) {
page: page,
sortType: sortType,
});
useSettingsStore.setState({
lastUsedSortType: sortType,
});
if (page > 1) {
router.push(
@ -178,6 +162,8 @@ export default function Player({ params }: { params: { id: string } }) {
}
useEffect(() => {
setMounted(true);
if (!params.id) {
setError(true);
setPlayer({ ...player, loading: false });
@ -187,6 +173,10 @@ export default function Player({ params }: { params: { id: string } }) {
return;
}
if (mounted == true) {
return;
}
getPlayerInfo(params.id).then((playerResponse) => {
if (!playerResponse) {
setError(true);
@ -197,7 +187,7 @@ export default function Player({ params }: { params: { id: string } }) {
setPlayer({ ...player, player: playerResponse, loading: false });
updateScoresPage(scores.sortType, 1);
});
}, [error, params.id, player, scores, updateScoresPage]);
}, [error, mounted, params.id, player, scores, updateScoresPage]);
if (player.loading || error || !player.player) {
return (
@ -318,7 +308,7 @@ export default function Player({ params }: { params: { id: string } }) {
{/* Sort */}
<div className="m-2 w-full text-sm">
<div className="flex justify-center gap-2">
{Object.values(sortTypes).map((sortType) => {
{Object.values(SortTypes).map((sortType) => {
return (
<button
key={sortType.value}

@ -0,0 +1,19 @@
"use client";
import { usePlayerScoresStore } from "@/store/playerScoresStore";
type AppProviderProps = {
children: React.ReactNode;
};
usePlayerScoresStore.getState().updatePlayerScores();
setTimeout(
() => {
usePlayerScoresStore.getState().updatePlayerScores();
},
1000 * 60 * 10,
); // fetch new scores every 10 minutes
export default function AppProvider({ children }: AppProviderProps) {
return <>{children}</>;
}

@ -6,7 +6,7 @@ import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
type Player = {
lastUpdated: Date;
lastUpdated: number;
id: string;
scores: ScoresaberPlayerScore[];
};
@ -81,7 +81,7 @@ export const usePlayerScoresStore = create<PlayerScoresStore>()(
...players,
{
id: playerId,
lastUpdated: new Date(),
lastUpdated: new Date().getTime(),
scores: scores,
},
],
@ -99,15 +99,22 @@ export const usePlayerScoresStore = create<PlayerScoresStore>()(
if (player == undefined) continue;
// Skip if the player was already updated recently
if (player.lastUpdated > new Date(Date.now() - UPDATE_INTERVAL))
if (
player.lastUpdated >
new Date(Date.now() - UPDATE_INTERVAL).getTime()
)
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);
// Sort the scores by date (newset to oldest), so we know when to stop searching for new scores
oldScores = oldScores.sort((a, b) => {
const aDate = new Date(a.score.timeSet);
const bDate = new Date(b.score.timeSet);
return bDate.getTime() - aDate.getTime();
});
const mostRecentScore = oldScores?.[0].score;
if (mostRecentScore == undefined) continue;
@ -143,13 +150,15 @@ export const usePlayerScoresStore = create<PlayerScoresStore>()(
newPlayers = newPlayers.filter((playerr) => playerr.id != player.id);
// Add the player
newPlayers.push({
lastUpdated: new Date(),
lastUpdated: new Date().getTime(),
id: player.id,
scores: oldScores,
});
if (newScoresCount > 0) {
console.log(`Found ${newScoresCount} new scores for ${player.id}`);
}
}
},
}),
{

@ -1,5 +1,6 @@
"use client";
import { SortType, SortTypes } from "@/types/SortTypes";
import { getPlayerInfo } from "@/utils/scoresaber/api";
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
@ -7,9 +8,11 @@ import { createJSONStorage, persist } from "zustand/middleware";
interface SettingsStore {
userId: string | undefined;
profilePicture: string | undefined;
lastUsedSortType: SortType;
setUserId: (userId: string) => void;
setProfilePicture: (profilePicture: string) => void;
setLastUsedSortType: (sortType: SortType) => void;
refreshProfile: () => void;
}
@ -18,6 +21,7 @@ export const useSettingsStore = create<SettingsStore>()(
(set) => ({
userId: undefined,
profilePicture: undefined,
lastUsedSortType: SortTypes.top,
setUserId: (userId: string) => {
set({ userId });
@ -25,6 +29,9 @@ export const useSettingsStore = create<SettingsStore>()(
setProfilePicture: (profilePicture: string) => set({ profilePicture }),
setLastUsedSortType: (sortType: SortType) =>
set({ lastUsedSortType: sortType }),
async refreshProfile() {
const id = useSettingsStore.getState().userId;
if (!id) return;

20
src/types/SortTypes.tsx Normal file

@ -0,0 +1,20 @@
import { ClockIcon, TrophyIcon } from "@heroicons/react/20/solid";
export type SortType = {
name: string;
value: string;
icon: JSX.Element;
};
export const SortTypes: { [key: string]: SortType } = {
top: {
name: "Top Scores",
value: "top",
icon: <TrophyIcon width={20} height={20} />,
},
recent: {
name: "Recent Scores",
value: "recent",
icon: <ClockIcon width={20} height={20} />,
},
};