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 { 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}
|
||||||
|
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";
|
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
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