diff --git a/src/components/input/pagination.tsx b/src/components/input/pagination.tsx index f03f400..e8c43b2 100644 --- a/src/components/input/pagination.tsx +++ b/src/components/input/pagination.tsx @@ -60,9 +60,21 @@ type Props = { * Callback function that is called when the user clicks on a page number. */ onPageChange: (page: number) => void; + + /** + * Optional callback to generate the URL for each page. + */ + generatePageUrl?: (page: number) => string; }; -export default function Pagination({ mobilePagination, page, totalPages, loadingPage, onPageChange }: Props) { +export default function Pagination({ + mobilePagination, + page, + totalPages, + loadingPage, + onPageChange, + generatePageUrl, +}: Props) { totalPages = Math.round(totalPages); const isLoading = loadingPage !== undefined; const [currentPage, setCurrentPage] = useState(page); @@ -72,7 +84,7 @@ export default function Pagination({ mobilePagination, page, totalPages, loading }, [page]); const handlePageChange = (newPage: number) => { - if (newPage < 1 || newPage > totalPages || newPage == currentPage || isLoading) { + if (newPage < 1 || newPage > totalPages || newPage === currentPage || isLoading) { return; } @@ -80,6 +92,11 @@ export default function Pagination({ mobilePagination, page, totalPages, loading onPageChange(newPage); }; + const handleLinkClick = (newPage: number, event: React.MouseEvent) => { + event.preventDefault(); // Prevent default navigation behavior + handlePageChange(newPage); + }; + const renderPageNumbers = () => { const pageNumbers = []; const maxPagesToShow = mobilePagination ? 3 : 4; @@ -95,7 +112,9 @@ export default function Pagination({ mobilePagination, page, totalPages, loading pageNumbers.push( <> - handlePageChange(1)}>1 + handleLinkClick(1, e)}> + 1 + {/* Only show ellipsis if more than 2 pages from the start */} {startPage > 2 && ( @@ -111,7 +130,11 @@ export default function Pagination({ mobilePagination, page, totalPages, loading for (let i = startPage; i <= endPage; i++) { pageNumbers.push( - handlePageChange(i)}> + handleLinkClick(i, e)} + > {loadingPage === i ? : i} @@ -126,7 +149,10 @@ export default function Pagination({ mobilePagination, page, totalPages, loading {/* Previous button for mobile and desktop */} - handlePageChange(currentPage - 1)} /> + handleLinkClick(currentPage - 1, e)} + /> {renderPageNumbers()} @@ -138,14 +164,22 @@ export default function Pagination({ mobilePagination, page, totalPages, loading - handlePageChange(totalPages)}>{totalPages} + handleLinkClick(totalPages, e)} + > + {totalPages} + )} {/* Next button for mobile and desktop */} - handlePageChange(currentPage + 1)} /> + handleLinkClick(currentPage + 1, e)} + /> diff --git a/src/components/leaderboard/leaderboard-scores.tsx b/src/components/leaderboard/leaderboard-scores.tsx index 7dfdab7..e3f393c 100644 --- a/src/components/leaderboard/leaderboard-scores.tsx +++ b/src/components/leaderboard/leaderboard-scores.tsx @@ -109,9 +109,6 @@ export default function LeaderboardScores({ if (leaderboardChanged) { leaderboardChanged(id); } - - // Update the URL - window.history.replaceState(null, "", `/leaderboard/${id}`); }, [leaderboardChanged] ); @@ -139,6 +136,11 @@ export default function LeaderboardScores({ } }, [currentPage, topOfScoresRef, shouldFetch]); + useEffect(() => { + // Update the URL + window.history.replaceState(null, "", `/leaderboard/${selectedLeaderboardId}/${currentPage}`); + }, [selectedLeaderboardId, currentPage]); + if (currentScores === undefined) { return undefined; } @@ -194,6 +196,9 @@ export default function LeaderboardScores({ page={currentPage} totalPages={Math.ceil(currentScores.metadata.total / currentScores.metadata.itemsPerPage)} loadingPage={isLoading ? currentPage : undefined} + generatePageUrl={page => { + return `/leaderboard/${selectedLeaderboardId}/${page}`; + }} onPageChange={newPage => { setCurrentPage(newPage); setPreviousPage(currentPage); diff --git a/src/components/player/player-scores.tsx b/src/components/player/player-scores.tsx index d4542de..34b881d 100644 --- a/src/components/player/player-scores.tsx +++ b/src/components/player/player-scores.tsx @@ -123,12 +123,19 @@ export default function PlayerScores({ initialScoreData, initialSearch, player, if (scores) handleScoreAnimation(); }, [scores, handleScoreAnimation]); + /** + * Gets the URL to the page. + */ + const getUrl = (page: number) => { + return `/player/${player.id}/${pageState.sort}/${page}${isSearchActive ? `?search=${debouncedSearchTerm}` : ""}`; + }; + /** * Handle updating the URL when the page number, * sort, or search term changes. */ useEffect(() => { - const newUrl = `/player/${player.id}/${pageState.sort}/${pageState.page}${isSearchActive ? `?search=${debouncedSearchTerm}` : ""}`; + const newUrl = getUrl(pageState.page); window.history.replaceState({ ...window.history.state, as: newUrl, url: newUrl }, "", newUrl); }, [pageState, debouncedSearchTerm, player.id, isSearchActive]); @@ -215,6 +222,9 @@ export default function PlayerScores({ initialScoreData, initialSearch, player, page={pageState.page} totalPages={Math.ceil(currentScores.metadata.total / currentScores.metadata.itemsPerPage)} loadingPage={isLoading ? pageState.page : undefined} + generatePageUrl={page => { + return getUrl(page); + }} onPageChange={newPage => { setPreviousPage(pageState.page); setPageState({ ...pageState, page: newPage });