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
All checks were successful
deploy / deploy (push) Successful in 1m0s
This commit is contained in:
parent
1ff7c246c3
commit
e9c80143ff
@ -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}
|
||||
|
19
src/components/AppProvider.tsx
Normal file
19
src/components/AppProvider.tsx
Normal file
@ -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
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} />,
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue
Block a user