generate page urls on the pagination (better SEO)
All checks were successful
Deploy / deploy (push) Successful in 6m27s

This commit is contained in:
Lee 2024-10-01 20:33:38 +01:00
parent ec84a456af
commit dc7644876b
3 changed files with 60 additions and 11 deletions

@ -60,9 +60,21 @@ type Props = {
* Callback function that is called when the user clicks on a page number. * Callback function that is called when the user clicks on a page number.
*/ */
onPageChange: (page: number) => void; 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); totalPages = Math.round(totalPages);
const isLoading = loadingPage !== undefined; const isLoading = loadingPage !== undefined;
const [currentPage, setCurrentPage] = useState(page); const [currentPage, setCurrentPage] = useState(page);
@ -72,7 +84,7 @@ export default function Pagination({ mobilePagination, page, totalPages, loading
}, [page]); }, [page]);
const handlePageChange = (newPage: number) => { const handlePageChange = (newPage: number) => {
if (newPage < 1 || newPage > totalPages || newPage == currentPage || isLoading) { if (newPage < 1 || newPage > totalPages || newPage === currentPage || isLoading) {
return; return;
} }
@ -80,6 +92,11 @@ export default function Pagination({ mobilePagination, page, totalPages, loading
onPageChange(newPage); onPageChange(newPage);
}; };
const handleLinkClick = (newPage: number, event: React.MouseEvent) => {
event.preventDefault(); // Prevent default navigation behavior
handlePageChange(newPage);
};
const renderPageNumbers = () => { const renderPageNumbers = () => {
const pageNumbers = []; const pageNumbers = [];
const maxPagesToShow = mobilePagination ? 3 : 4; const maxPagesToShow = mobilePagination ? 3 : 4;
@ -95,7 +112,9 @@ export default function Pagination({ mobilePagination, page, totalPages, loading
pageNumbers.push( pageNumbers.push(
<> <>
<PaginationItemWrapper key="start" isLoadingPage={isLoading}> <PaginationItemWrapper key="start" isLoadingPage={isLoading}>
<PaginationLink onClick={() => handlePageChange(1)}>1</PaginationLink> <PaginationLink href={generatePageUrl ? generatePageUrl(1) : ""} onClick={e => handleLinkClick(1, e)}>
1
</PaginationLink>
</PaginationItemWrapper> </PaginationItemWrapper>
{/* Only show ellipsis if more than 2 pages from the start */} {/* Only show ellipsis if more than 2 pages from the start */}
{startPage > 2 && ( {startPage > 2 && (
@ -111,7 +130,11 @@ export default function Pagination({ mobilePagination, page, totalPages, loading
for (let i = startPage; i <= endPage; i++) { for (let i = startPage; i <= endPage; i++) {
pageNumbers.push( pageNumbers.push(
<PaginationItemWrapper key={i} isLoadingPage={isLoading}> <PaginationItemWrapper key={i} isLoadingPage={isLoading}>
<PaginationLink isActive={i === currentPage} onClick={() => handlePageChange(i)}> <PaginationLink
isActive={i === currentPage}
href={generatePageUrl ? generatePageUrl(i) : ""}
onClick={e => handleLinkClick(i, e)}
>
{loadingPage === i ? <ArrowPathIcon className="w-4 h-4 animate-spin" /> : i} {loadingPage === i ? <ArrowPathIcon className="w-4 h-4 animate-spin" /> : i}
</PaginationLink> </PaginationLink>
</PaginationItemWrapper> </PaginationItemWrapper>
@ -126,7 +149,10 @@ export default function Pagination({ mobilePagination, page, totalPages, loading
<PaginationContent> <PaginationContent>
{/* Previous button for mobile and desktop */} {/* Previous button for mobile and desktop */}
<PaginationItemWrapper isLoadingPage={isLoading}> <PaginationItemWrapper isLoadingPage={isLoading}>
<PaginationPrevious onClick={() => handlePageChange(currentPage - 1)} /> <PaginationPrevious
href={generatePageUrl ? generatePageUrl(currentPage - 1) : ""}
onClick={e => handleLinkClick(currentPage - 1, e)}
/>
</PaginationItemWrapper> </PaginationItemWrapper>
{renderPageNumbers()} {renderPageNumbers()}
@ -138,14 +164,22 @@ export default function Pagination({ mobilePagination, page, totalPages, loading
<PaginationEllipsis className="cursor-default" /> <PaginationEllipsis className="cursor-default" />
</PaginationItemWrapper> </PaginationItemWrapper>
<PaginationItemWrapper key="end" isLoadingPage={isLoading}> <PaginationItemWrapper key="end" isLoadingPage={isLoading}>
<PaginationLink onClick={() => handlePageChange(totalPages)}>{totalPages}</PaginationLink> <PaginationLink
href={generatePageUrl ? generatePageUrl(totalPages) : ""}
onClick={e => handleLinkClick(totalPages, e)}
>
{totalPages}
</PaginationLink>
</PaginationItemWrapper> </PaginationItemWrapper>
</> </>
)} )}
{/* Next button for mobile and desktop */} {/* Next button for mobile and desktop */}
<PaginationItemWrapper isLoadingPage={isLoading}> <PaginationItemWrapper isLoadingPage={isLoading}>
<PaginationNext onClick={() => handlePageChange(currentPage + 1)} /> <PaginationNext
href={generatePageUrl ? generatePageUrl(currentPage + 1) : ""}
onClick={e => handleLinkClick(currentPage + 1, e)}
/>
</PaginationItemWrapper> </PaginationItemWrapper>
</PaginationContent> </PaginationContent>
</ShadCnPagination> </ShadCnPagination>

@ -109,9 +109,6 @@ export default function LeaderboardScores({
if (leaderboardChanged) { if (leaderboardChanged) {
leaderboardChanged(id); leaderboardChanged(id);
} }
// Update the URL
window.history.replaceState(null, "", `/leaderboard/${id}`);
}, },
[leaderboardChanged] [leaderboardChanged]
); );
@ -139,6 +136,11 @@ export default function LeaderboardScores({
} }
}, [currentPage, topOfScoresRef, shouldFetch]); }, [currentPage, topOfScoresRef, shouldFetch]);
useEffect(() => {
// Update the URL
window.history.replaceState(null, "", `/leaderboard/${selectedLeaderboardId}/${currentPage}`);
}, [selectedLeaderboardId, currentPage]);
if (currentScores === undefined) { if (currentScores === undefined) {
return undefined; return undefined;
} }
@ -194,6 +196,9 @@ export default function LeaderboardScores({
page={currentPage} page={currentPage}
totalPages={Math.ceil(currentScores.metadata.total / currentScores.metadata.itemsPerPage)} totalPages={Math.ceil(currentScores.metadata.total / currentScores.metadata.itemsPerPage)}
loadingPage={isLoading ? currentPage : undefined} loadingPage={isLoading ? currentPage : undefined}
generatePageUrl={page => {
return `/leaderboard/${selectedLeaderboardId}/${page}`;
}}
onPageChange={newPage => { onPageChange={newPage => {
setCurrentPage(newPage); setCurrentPage(newPage);
setPreviousPage(currentPage); setPreviousPage(currentPage);

@ -123,12 +123,19 @@ export default function PlayerScores({ initialScoreData, initialSearch, player,
if (scores) handleScoreAnimation(); if (scores) handleScoreAnimation();
}, [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, * Handle updating the URL when the page number,
* sort, or search term changes. * sort, or search term changes.
*/ */
useEffect(() => { 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); window.history.replaceState({ ...window.history.state, as: newUrl, url: newUrl }, "", newUrl);
}, [pageState, debouncedSearchTerm, player.id, isSearchActive]); }, [pageState, debouncedSearchTerm, player.id, isSearchActive]);
@ -215,6 +222,9 @@ export default function PlayerScores({ initialScoreData, initialSearch, player,
page={pageState.page} page={pageState.page}
totalPages={Math.ceil(currentScores.metadata.total / currentScores.metadata.itemsPerPage)} totalPages={Math.ceil(currentScores.metadata.total / currentScores.metadata.itemsPerPage)}
loadingPage={isLoading ? pageState.page : undefined} loadingPage={isLoading ? pageState.page : undefined}
generatePageUrl={page => {
return getUrl(page);
}}
onPageChange={newPage => { onPageChange={newPage => {
setPreviousPage(pageState.page); setPreviousPage(pageState.page);
setPageState({ ...pageState, page: newPage }); setPageState({ ...pageState, page: newPage });