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

@ -12,15 +12,11 @@ import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
import { ScoresaberPlayerScore } from "@/schemas/scoresaber/playerScore"; import { ScoresaberPlayerScore } from "@/schemas/scoresaber/playerScore";
import { usePlayerScoresStore } from "@/store/playerScoresStore"; import { usePlayerScoresStore } from "@/store/playerScoresStore";
import { useSettingsStore } from "@/store/settingsStore"; import { useSettingsStore } from "@/store/settingsStore";
import { SortType, SortTypes } from "@/types/SortTypes";
import { formatNumber } from "@/utils/number"; import { formatNumber } from "@/utils/number";
import { fetchScores, getPlayerInfo } from "@/utils/scoresaber/api"; import { fetchScores, getPlayerInfo } from "@/utils/scoresaber/api";
import useStore from "@/utils/useStore"; import useStore from "@/utils/useStore";
import { import { GlobeAsiaAustraliaIcon, HomeIcon } from "@heroicons/react/20/solid";
ClockIcon,
GlobeAsiaAustraliaIcon,
HomeIcon,
TrophyIcon,
} from "@heroicons/react/20/solid";
import Image from "next/image"; import Image from "next/image";
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
@ -40,27 +36,11 @@ type PlayerInfo = {
player: ScoresaberPlayer | undefined; player: ScoresaberPlayer | undefined;
}; };
type SortType = { const DEFAULT_SORT_TYPE = SortTypes.top;
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;
export default function Player({ params }: { params: { id: string } }) { export default function Player({ params }: { params: { id: string } }) {
const [mounted, setMounted] = useState(false);
const settingsStore = useStore(useSettingsStore, (store) => store); const settingsStore = useStore(useSettingsStore, (store) => store);
const playerScoreStore = useStore(usePlayerScoresStore, (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; page = Number.parseInt(pageString) || 1;
} }
let sortType; let sortType: SortType;
const sortTypeString = searchParams.get("sort"); const sortTypeString = searchParams.get("sort");
if (sortTypeString == null) { if (sortTypeString == null) {
// todo: check settings to get last used sort type // todo: check settings to get last used sort type
sortType = DEFAULT_SORT_TYPE; sortType =
useSettingsStore.getState().lastUsedSortType || DEFAULT_SORT_TYPE;
} else { } else {
sortType = sortTypes[sortTypeString] || DEFAULT_SORT_TYPE; sortType = SortTypes[sortTypeString] || DEFAULT_SORT_TYPE;
} }
const [error, setError] = useState(false); const [error, setError] = useState(false);
@ -119,6 +100,9 @@ export default function Player({ params }: { params: { id: string } }) {
page: page, page: page,
sortType: sortType, sortType: sortType,
}); });
useSettingsStore.setState({
lastUsedSortType: sortType,
});
if (page > 1) { if (page > 1) {
router.push( router.push(
@ -178,6 +162,8 @@ export default function Player({ params }: { params: { id: string } }) {
} }
useEffect(() => { useEffect(() => {
setMounted(true);
if (!params.id) { if (!params.id) {
setError(true); setError(true);
setPlayer({ ...player, loading: false }); setPlayer({ ...player, loading: false });
@ -187,6 +173,10 @@ export default function Player({ params }: { params: { id: string } }) {
return; return;
} }
if (mounted == true) {
return;
}
getPlayerInfo(params.id).then((playerResponse) => { getPlayerInfo(params.id).then((playerResponse) => {
if (!playerResponse) { if (!playerResponse) {
setError(true); setError(true);
@ -197,7 +187,7 @@ export default function Player({ params }: { params: { id: string } }) {
setPlayer({ ...player, player: playerResponse, loading: false }); setPlayer({ ...player, player: playerResponse, loading: false });
updateScoresPage(scores.sortType, 1); updateScoresPage(scores.sortType, 1);
}); });
}, [error, params.id, player, scores, updateScoresPage]); }, [error, mounted, params.id, player, scores, updateScoresPage]);
if (player.loading || error || !player.player) { if (player.loading || error || !player.player) {
return ( return (
@ -318,7 +308,7 @@ export default function Player({ params }: { params: { id: string } }) {
{/* Sort */} {/* Sort */}
<div className="m-2 w-full text-sm"> <div className="m-2 w-full text-sm">
<div className="flex justify-center gap-2"> <div className="flex justify-center gap-2">
{Object.values(sortTypes).map((sortType) => { {Object.values(SortTypes).map((sortType) => {
return ( return (
<button <button
key={sortType.value} 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"; import { createJSONStorage, persist } from "zustand/middleware";
type Player = { type Player = {
lastUpdated: Date; lastUpdated: number;
id: string; id: string;
scores: ScoresaberPlayerScore[]; scores: ScoresaberPlayerScore[];
}; };
@ -81,7 +81,7 @@ export const usePlayerScoresStore = create<PlayerScoresStore>()(
...players, ...players,
{ {
id: playerId, id: playerId,
lastUpdated: new Date(), lastUpdated: new Date().getTime(),
scores: scores, scores: scores,
}, },
], ],
@ -99,15 +99,22 @@ export const usePlayerScoresStore = create<PlayerScoresStore>()(
if (player == undefined) continue; if (player == undefined) continue;
// Skip if the player was already updated recently // 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; continue;
console.log(`Updating scores for ${player.id}...`); console.log(`Updating scores for ${player.id}...`);
let oldScores = player.scores; let oldScores = player.scores;
// Sort the scores by id, so we know when to stop searching for new scores // Sort the scores by date (newset to oldest), so we know when to stop searching for new scores
oldScores = oldScores.sort((a, b) => b.score.id - a.score.id); 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; const mostRecentScore = oldScores?.[0].score;
if (mostRecentScore == undefined) continue; if (mostRecentScore == undefined) continue;
@ -143,13 +150,15 @@ export const usePlayerScoresStore = create<PlayerScoresStore>()(
newPlayers = newPlayers.filter((playerr) => playerr.id != player.id); newPlayers = newPlayers.filter((playerr) => playerr.id != player.id);
// Add the player // Add the player
newPlayers.push({ newPlayers.push({
lastUpdated: new Date(), lastUpdated: new Date().getTime(),
id: player.id, id: player.id,
scores: oldScores, scores: oldScores,
}); });
if (newScoresCount > 0) {
console.log(`Found ${newScoresCount} new scores for ${player.id}`); console.log(`Found ${newScoresCount} new scores for ${player.id}`);
} }
}
}, },
}), }),
{ {

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