From af0e6a1f314d780198e88c491b9cccc73cdee610 Mon Sep 17 00:00:00 2001 From: Priyanshu Kumar Date: Wed, 22 Jan 2025 00:40:09 +0530 Subject: [PATCH] add pagination feature on blog page --- components/helpers/applyFilter.ts | 3 + components/helpers/usePagination.ts | 30 ++++++ components/pagination/Pagination.tsx | 122 +++++++++++++++++++++++ components/pagination/PaginationItem.tsx | 32 ++++++ pages/blog/index.tsx | 38 ++++++- 5 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 components/helpers/usePagination.ts create mode 100644 components/pagination/Pagination.tsx create mode 100644 components/pagination/PaginationItem.tsx diff --git a/components/helpers/applyFilter.ts b/components/helpers/applyFilter.ts index a7f3f217d5eb..c2772cce63e1 100644 --- a/components/helpers/applyFilter.ts +++ b/components/helpers/applyFilter.ts @@ -137,6 +137,9 @@ export const onFilterApply = ( if (query && Object.keys(query).length >= 1) { Object.keys(query).forEach((property) => { + if (property === 'page') { + return; + } const res = result.filter((e) => { if (!query[property] || e[property] === query[property]) { return e[property]; diff --git a/components/helpers/usePagination.ts b/components/helpers/usePagination.ts new file mode 100644 index 000000000000..567f0ca0b5d4 --- /dev/null +++ b/components/helpers/usePagination.ts @@ -0,0 +1,30 @@ +import { useMemo, useState } from 'react'; + +/** + * @description Custom hook for managing pagination logic + * @example const { currentPage, setCurrentPage, currentItems, maxPage } = usePagination(items, 10); + * @param {T[]} items - Array of items to paginate + * @param {number} itemsPerPage - Number of items per page + * @returns {object} + * @returns {number} currentPage - Current page number + * @returns {function} setCurrentPage - Function to update the current page + * @returns {T[]} currentItems - Items for the current page + * @returns {number} maxPage - Total number of pages + */ +export function usePagination(items: T[], itemsPerPage: number) { + const [currentPage, setCurrentPage] = useState(1); + const maxPage = Math.ceil(items.length / itemsPerPage); + + const currentItems = useMemo(() => { + const start = (currentPage - 1) * itemsPerPage; + + return items.slice(start, start + itemsPerPage); + }, [items, currentPage, itemsPerPage]); + + return { + currentPage, + setCurrentPage, + currentItems, + maxPage + }; +} diff --git a/components/pagination/Pagination.tsx b/components/pagination/Pagination.tsx new file mode 100644 index 000000000000..862beade7665 --- /dev/null +++ b/components/pagination/Pagination.tsx @@ -0,0 +1,122 @@ +import React from 'react'; + +import PaginationItem from './PaginationItem'; + +export interface PaginationProps { + // eslint-disable-next-line prettier/prettier + + /** Total number of pages */ + totalPages: number; + + /** Current active page */ + currentPage: number; + + /** Function to handle page changes */ + onPageChange: (page: number) => void; +} + +/** + * This is the Pagination component. It displays a list of page numbers that can be clicked to navigate. + */ +export default function Pagination({ totalPages, currentPage, onPageChange }: PaginationProps) { + const handlePageChange = (page: number) => { + if (page < 1 || page > totalPages) return; + onPageChange(page); + }; + + const getPageNumbers = () => { + const pages = []; + + if (totalPages <= 7) { + for (let i = 1; i <= totalPages; i++) pages.push(i); + } else { + pages.push(1); + if (currentPage > 3) { + pages.push('ellipsis1'); + } + + for (let i = Math.max(2, currentPage - 1); i <= Math.min(totalPages - 1, currentPage + 1); i++) { + pages.push(i); + } + + if (currentPage < totalPages - 2) { + pages.push('ellipsis2'); + } + + pages.push(totalPages); + } + + return pages; + }; + + return ( +
+ {/* Previous button */} + + + {/* Page numbers */} +
+ {getPageNumbers().map((page) => + typeof page === 'number' ? ( + + ) : ( + + ... + + ) + )} +
+ + {/* Next button */} + +
+ ); +} diff --git a/components/pagination/PaginationItem.tsx b/components/pagination/PaginationItem.tsx new file mode 100644 index 000000000000..dd3c5a9e5de5 --- /dev/null +++ b/components/pagination/PaginationItem.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +export interface PaginationItemProps { + // eslint-disable-next-line prettier/prettier + + /** The page number to display */ + pageNumber: number; + + /** Whether this page is currently active */ + isActive: boolean; + + /** Function to handle page change */ + onPageChange: (page: number) => void; +} + +/** + * This is the PaginationItem component. It displays a single page number that can be clicked. + */ +export default function PaginationItem({ pageNumber, isActive, onPageChange }: PaginationItemProps) { + return ( + + ); +} diff --git a/pages/blog/index.tsx b/pages/blog/index.tsx index 37958cec4308..906c440eae21 100644 --- a/pages/blog/index.tsx +++ b/pages/blog/index.tsx @@ -1,11 +1,13 @@ import { useRouter } from 'next/router'; import React, { useContext, useEffect, useState } from 'react'; +import { usePagination } from '@/components/helpers/usePagination'; import Empty from '@/components/illustrations/Empty'; import GenericLayout from '@/components/layout/GenericLayout'; import Loader from '@/components/Loader'; import BlogPostItem from '@/components/navigation/BlogPostItem'; import Filter from '@/components/navigation/Filter'; +import Pagination from '@/components/pagination/Pagination'; import Heading from '@/components/typography/Heading'; import Paragraph from '@/components/typography/Paragraph'; import TextLink from '@/components/typography/TextLink'; @@ -34,6 +36,33 @@ export default function BlogIndexPage() { }) : [] ); + + const postsPerPage = 9; + const { currentPage, setCurrentPage, currentItems, maxPage } = usePagination(posts, postsPerPage); + + const handlePageChange = (page: number) => { + setCurrentPage(page); + + const currentFilters = { ...router.query, page: page.toString() }; + + router.push( + { + pathname: router.pathname, + query: currentFilters + }, + undefined, + { shallow: true } + ); + }; + + useEffect(() => { + const pageFromQuery = parseInt(router.query.page as string, 10); + + if (!Number.isNaN(pageFromQuery) && pageFromQuery >= 1 && pageFromQuery !== currentPage) { + setCurrentPage(pageFromQuery); + } + }, [router.query.page]); + const [isClient, setIsClient] = useState(false); const onFilter = (data: IBlogPost[]) => setPosts(data); @@ -122,16 +151,21 @@ export default function BlogIndexPage() { )} {Object.keys(posts).length > 0 && isClient && ( )} - {Object.keys(posts).length > 0 && !isClient && ( + {Object.keys(currentItems).length > 0 && !isClient && (
)} + {maxPage > 1 && ( +
+ +
+ )}