load initial scores server sided to prevent flashing
All checks were successful
Deploy SSR / deploy (push) Successful in 1m12s

This commit is contained in:
Lee 2024-09-12 12:47:21 +01:00
parent 4d19fd3bfd
commit aba0d4ba57
4 changed files with 38 additions and 42 deletions

@ -43,6 +43,7 @@ export default async function Search({ params: { slug } }: Props) {
const sort: ScoreSort = (slug[1] as ScoreSort) || "recent"; // The sorting method
const page = parseInt(slug[2]) || 1; // The page number
const player = await scoresaberFetcher.lookupPlayer(id, false);
const scores = await scoresaberFetcher.lookupPlayerScores(id, sort, page);
if (player == undefined) {
// Invalid player id
@ -51,7 +52,7 @@ export default async function Search({ params: { slug } }: Props) {
return (
<div className="flex flex-col h-full w-full">
<PlayerData initalPlayerData={player} sort={sort} page={page} />
<PlayerData initalPlayerData={player} initialScoreData={scores} sort={sort} page={page} />
</div>
);
}

@ -18,11 +18,12 @@ class BeatSaverFetcher extends DataFetcher {
* @returns the map that match the query, or undefined if no map were found
*/
async getMapBsr(query: string, useProxy = true): Promise<string | undefined> {
this.log(`Looking up the bsr for the map with hash ${query}...`);
this.log(`Looking up the bsr for map hash ${query}...`);
const map = await db.beatSaverMaps.get(query);
// The map is cached
if (map != undefined) {
this.log(`Found cached bsr ${map.bsr} for map hash ${query}`);
return map.bsr;
}
@ -42,6 +43,7 @@ class BeatSaverFetcher extends DataFetcher {
hash: query,
bsr: bsr,
});
this.log(`Looked up bsr ${bsr} for map hash ${query}`);
return bsr;
}
}

@ -3,6 +3,7 @@
import { scoresaberFetcher } from "@/common/data-fetcher/impl/scoresaber";
import { ScoreSort } from "@/common/data-fetcher/sort";
import ScoreSaberPlayer from "@/common/data-fetcher/types/scoresaber/scoresaber-player";
import ScoreSaberPlayerScoresPage from "@/common/data-fetcher/types/scoresaber/scoresaber-player-scores-page";
import { useQuery } from "@tanstack/react-query";
import PlayerHeader from "./player-header";
import PlayerRankChart from "./player-rank-chart";
@ -12,11 +13,12 @@ const REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes
type Props = {
initalPlayerData: ScoreSaberPlayer;
initialScoreData?: ScoreSaberPlayerScoresPage;
sort: ScoreSort;
page: number;
};
export default function PlayerData({ initalPlayerData, sort, page }: Props) {
export default function PlayerData({ initalPlayerData, initialScoreData, sort, page }: Props) {
let player = initalPlayerData;
const { data, isLoading, isError } = useQuery({
queryKey: ["player", player.id],
@ -36,7 +38,7 @@ export default function PlayerData({ initalPlayerData, sort, page }: Props) {
<PlayerRankChart player={player} />
</>
)}
<PlayerScores player={player} sort={sort} page={page} />
<PlayerScores initialScoreData={initialScoreData} player={player} sort={sort} page={page} />
</div>
);
}

@ -15,70 +15,63 @@ import { Button } from "../ui/button";
import Score from "./score/score";
type Props = {
initialScoreData?: ScoreSaberPlayerScoresPage;
player: ScoreSaberPlayer;
sort: ScoreSort;
page: number;
};
export default function PlayerScores({ player, sort, page }: Props) {
export default function PlayerScores({ initialScoreData, player, sort, page }: Props) {
const { width } = useWindowDimensions();
const controls = useAnimation();
const [currentSort, setCurrentSort] = useState(sort);
const [currentPage, setCurrentPage] = useState(page);
const [previousScores, setPreviousScores] = useState<ScoreSaberPlayerScoresPage | undefined>();
const { data, isError, isLoading, refetch } = useQuery({
const {
data: scores,
isError,
isLoading,
refetch,
} = useQuery({
queryKey: ["playerScores", player.id, currentSort, currentPage],
queryFn: () => scoresaberFetcher.lookupPlayerScores(player.id, currentSort, currentPage),
staleTime: 30 * 1000, // Data will be cached for 30 seconds
staleTime: 30 * 1000, // Cache data for 30 seconds
initialData: initialScoreData,
});
const handleAnimation = useCallback(() => {
controls.set({
x: -50,
opacity: 0,
});
controls.start({
x: 0,
opacity: 1,
transition: { duration: 0.25 },
});
controls.set({ x: -50, opacity: 0 });
controls.start({ x: 0, opacity: 1, transition: { duration: 0.25 } });
}, [controls]);
useEffect(() => {
if (data == undefined) {
return;
}
setPreviousScores(data);
if (scores) {
handleAnimation();
}, [data, handleAnimation]);
}
}, [scores, handleAnimation]);
useEffect(() => {
// Update URL and refetch data when currentSort or currentPage changes
const newUrl = `/player/${player.id}/${currentSort}/${currentPage}`;
window.history.replaceState({ ...window.history.state, as: newUrl, url: newUrl }, "", newUrl);
refetch();
}, [currentSort, currentPage, refetch, player.id]);
/**
* Updates the current sort and resets the page to 1
*/
function handleSortChange(newSort: ScoreSort) {
const handleSortChange = (newSort: ScoreSort) => {
if (newSort !== currentSort) {
setCurrentSort(newSort);
setCurrentPage(1); // Reset the page
}
setCurrentPage(1); // Reset page to 1 on sort change
}
};
if (previousScores === undefined) {
return null;
if (scores === undefined) {
return undefined;
}
if (isError) {
return (
<Card className="gap-2">
<p>Oopsies!</p>
<p>Oopsies! Something went wrong.</p>
</Card>
);
}
@ -86,20 +79,20 @@ export default function PlayerScores({ player, sort, page }: Props) {
return (
<Card className="flex gap-4">
<div className="flex items-center flex-row w-full gap-2 justify-center">
{Object.keys(ScoreSort).map((sort, index) => (
{Object.values(ScoreSort).map((sortOption) => (
<Button
variant={sort == currentSort ? "default" : "outline"}
key={index}
onClick={() => handleSortChange(sort as ScoreSort)}
variant={sortOption === currentSort ? "default" : "outline"}
key={sortOption}
onClick={() => handleSortChange(sortOption)}
>
{capitalizeFirstLetter(sort)}
{capitalizeFirstLetter(sortOption)}
</Button>
))}
</div>
<motion.div animate={controls}>
<div className="grid min-w-full grid-cols-1 divide-y divide-border">
{previousScores.playerScores.map((playerScore, index) => (
{scores.playerScores.map((playerScore, index) => (
<Score key={index} playerScore={playerScore} />
))}
</div>
@ -108,11 +101,9 @@ export default function PlayerScores({ player, sort, page }: Props) {
<Pagination
mobilePagination={width < 768}
page={currentPage}
totalPages={Math.ceil(previousScores.metadata.total / previousScores.metadata.itemsPerPage)}
totalPages={Math.ceil(scores.metadata.total / scores.metadata.itemsPerPage)}
loadingPage={isLoading ? currentPage : undefined}
onPageChange={(newPage) => {
setCurrentPage(newPage);
}}
onPageChange={setCurrentPage}
/>
</Card>
);