diff --git a/components/helpers/applyFilter.ts b/components/helpers/applyFilter.ts index a7f3f217d5eb..9386990f9d6f 100644 --- a/components/helpers/applyFilter.ts +++ b/components/helpers/applyFilter.ts @@ -133,10 +133,14 @@ export const onFilterApply = ( onFilter: (result: DataObject[], query: Filter) => void, query: Filter ): void => { + const nonFilterableKeys = ['page']; let result = inputData; if (query && Object.keys(query).length >= 1) { Object.keys(query).forEach((property) => { + if (nonFilterableKeys.includes(property)) { + return; // Skip non-filterable keys like 'page' + } const res = result.filter((e) => { if (!query[property] || e[property] === query[property]) { return e[property]; diff --git a/components/icons/Next.tsx b/components/icons/Next.tsx new file mode 100644 index 000000000000..97e34cd26bc4 --- /dev/null +++ b/components/icons/Next.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +/* eslint-disable max-len */ +/** + * @description Icons for Next button + */ +export default function IconNext() { + return ( + + + + ); +} diff --git a/components/icons/Previous.tsx b/components/icons/Previous.tsx new file mode 100644 index 000000000000..3bf10d5e3b84 --- /dev/null +++ b/components/icons/Previous.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +/* eslint-disable max-len */ +/** + * @description Icons for Previous button in pagination + */ +export default function IconPrevious() { + return ( + + + + ); +} diff --git a/components/navigation/BlogPagination.tsx b/components/navigation/BlogPagination.tsx new file mode 100644 index 000000000000..004b70873aef --- /dev/null +++ b/components/navigation/BlogPagination.tsx @@ -0,0 +1,123 @@ +import React, { useEffect, useState } from 'react'; + +import { ButtonIconPosition } from '@/types/components/buttons/ButtonPropsType'; + +import Button from '../buttons/Button'; +import IconNext from '../icons/Next'; +import IconPrevious from '../icons/Previous'; + +/** + * Props for the BlogPagination component + * @property {number} blogsPerPage - Number of blogs to display per page + * @property {number} totalBlogs - Total number of blogs + * @property {function} paginate - Callback function to handle page changes + * @property {number} currentPage - Current active page number + */ +interface BlogPaginationProps { + // eslint-disable-next-line prettier/prettier + + blogsPerPage: number; + totalBlogs: number; + paginate: (pageNumber: number) => void; + currentPage: number; +} + +/** + * A pagination component for blog posts that displays page numbers and navigation buttons + * @param {BlogPaginationProps} props - The props for the component + * @returns {JSX.Element} A navigation element with pagination controls + */ +export default function BlogPagination({ + blogsPerPage, + totalBlogs, + paginate, + currentPage +}: BlogPaginationProps): JSX.Element { + const totalPages: number = Math.ceil(totalBlogs / blogsPerPage); + const pagesToShow: number = 6; + const [pageNumbers, setPageNumbers] = useState<(number | string)[]>([]); + + const calculatePageNumbers = () => { + const numbers: (number | string)[] = []; + + if (totalPages < 1) return []; + if (totalPages <= pagesToShow) { + for (let i = 1; i <= totalPages; i++) { + numbers.push(i); + } + } else if (currentPage <= 2) { + for (let i = 1; i <= 3; i++) { + numbers.push(i); + } + numbers.push('...'); + numbers.push(totalPages - 2); + numbers.push(totalPages - 1); + numbers.push(totalPages); + } else if (currentPage >= totalPages - 1) { + numbers.push(1); + numbers.push(2); + numbers.push(3); + numbers.push('...'); + for (let i = totalPages - 2; i <= totalPages; i++) { + numbers.push(i); + } + } else { + numbers.push(1); + numbers.push('...'); + numbers.push(currentPage - 1); + numbers.push(currentPage); + numbers.push(currentPage + 1); + numbers.push('...'); + numbers.push(totalPages); + } + + return numbers; + }; + + useEffect(() => { + setPageNumbers(calculatePageNumbers()); + }, [currentPage, totalBlogs]); + + return ( + + ); +} diff --git a/pages/blog/index.tsx b/pages/blog/index.tsx index b6a13e507157..54e07e92fa5a 100644 --- a/pages/blog/index.tsx +++ b/pages/blog/index.tsx @@ -1,9 +1,10 @@ import { useRouter } from 'next/router'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; import Empty from '@/components/illustrations/Empty'; import GenericLayout from '@/components/layout/GenericLayout'; import Loader from '@/components/Loader'; +import BlogPagination from '@/components/navigation/BlogPagination'; import BlogPostItem from '@/components/navigation/BlogPostItem'; import Filter from '@/components/navigation/Filter'; import Heading from '@/components/typography/Heading'; @@ -35,7 +36,6 @@ export default function BlogIndexPage() { : [] ); const [isClient, setIsClient] = useState(false); - const onFilter = (data: IBlogPost[]) => setPosts(data); const toFilter = [ { @@ -50,14 +50,61 @@ export default function BlogIndexPage() { } ]; const clearFilters = () => { - router.push(`${router.pathname}`, undefined, { - shallow: true - }); + const { page } = router.query; + + router.push( + { + pathname: router.pathname, + query: { ...(page && { page }) } + }, + undefined, + { + shallow: true + } + ); }; - const showClearFilters = Object.keys(router.query).length > 0; + const showClearFilters = Object.keys(router.query).length > 1; const description = 'Find the latest and greatest stories from our community'; const image = '/img/social/blog.webp'; + const blogsPerPage = 9; + + const [currentPage, setCurrentPage] = useState(1); + const currentPosts = useMemo(() => { + const indexOfLastPost = currentPage * blogsPerPage; + const indexOfFirstPost = indexOfLastPost - blogsPerPage; + + return posts.slice(indexOfFirstPost, indexOfLastPost); + }, [currentPage, posts]); + + const paginate = (pageNumber: number) => { + setCurrentPage(pageNumber); + const { query } = router; + const newQuery = { + ...query, + page: pageNumber + }; + + router.push( + { + pathname: router.pathname, + query: newQuery + }, + undefined, + { + shallow: true + } + ); + }; + + useEffect(() => { + const currentPageNumber = Math.max( + 1, + Number.isNaN(parseInt(router.query.page as string, 10)) ? 1 : parseInt(router.query.page as string, 10) + ); + + setCurrentPage(currentPageNumber); + }, [router.query.page]); useEffect(() => { setIsClient(true); @@ -114,7 +161,7 @@ export default function BlogIndexPage() { )}
- {Object.keys(posts).length === 0 && ( + {(Object.keys(posts).length === 0 || Object.keys(currentPosts).length === 0) && (

No post matches your filter

@@ -122,16 +169,23 @@ export default function BlogIndexPage() { )} {Object.keys(posts).length > 0 && isClient && (
    - {posts.map((post, index) => ( + {currentPosts.map((post, index) => ( ))}
)} - {Object.keys(posts).length > 0 && !isClient && ( + {Object.keys(currentPosts).length > 0 && !isClient && (
)} + {/* Pagination component */} +