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/navigation/BlogPagination.tsx b/components/navigation/BlogPagination.tsx new file mode 100644 index 000000000000..6479e105faf5 --- /dev/null +++ b/components/navigation/BlogPagination.tsx @@ -0,0 +1,126 @@ +import React, { useEffect, useState } from 'react'; + +import { ButtonIconPosition } from '@/types/components/buttons/ButtonPropsType'; + +import Button from '../buttons/Button'; +import IconArrowLeft from '../icons/ArrowLeft'; +import IconArrowRight from '../icons/ArrowRight'; + +/** + * 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) { + 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 37958cec4308..9b8d556436d0 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'; @@ -32,106 +33,184 @@ export default function BlogIndexPage() { return i2Date.getTime() - i1Date.getTime(); }) - : [] + : [], ); const [isClient, setIsClient] = useState(false); - const onFilter = (data: IBlogPost[]) => setPosts(data); + const onFilter = (data: IBlogPost[]) => { + setPosts(data); + }; const toFilter = [ { - name: 'type' + name: 'type', }, { name: 'authors', - unique: 'name' + unique: 'name', }, { - name: 'tags' - } + name: 'tags', + }, ]; 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 = parseInt(router.query.page as string, 10) || 1; + + const currentPosts = useMemo(() => { + const indexOfLastPost = currentPage * blogsPerPage; + const indexOfFirstPost = indexOfLastPost - blogsPerPage; + + return posts.slice(indexOfFirstPost, indexOfLastPost); + }, [currentPage, posts]); + + const paginate = (pageNumber: number) => { + const { query } = router; + const newQuery = { + ...query, + page: pageNumber, + }; + + router.push( + { + pathname: router.pathname, + query: newQuery, + }, + undefined, + { + shallow: true, + }, + ); + }; + + useEffect(() => { + if (router.isReady && !router.query.page) { + router.replace( + { pathname: router.pathname, query: { page: '1' } }, + undefined, + { shallow: true }, + ); + } + }, [router.isReady]); useEffect(() => { - setIsClient(true); - }, []); + if (router.isReady) { + setIsClient(true); + } + }, [router.isReady]); return ( - -
-
-
+ +
+
+
-
-
+
+
Welcome to our blog! - + Find the latest and greatest stories from our community - + Want to publish a blog post? We love community stories.{' '} - + Submit yours! - + We have an RSS feed - RSS Feed, too! + RSS Feed, too!
-
+
{showClearFilters && ( )}
- {Object.keys(posts).length === 0 && ( -
+ {(Object.keys(posts).length === 0 || + Object.keys(currentPosts).length === 0) && ( +
-

No post matches your filter

+

+ No post matches your filter +

)} {Object.keys(posts).length > 0 && isClient && ( -
    - {posts.map((post, index) => ( +
      + {currentPosts.map((post, index) => ( ))}
    )} {Object.keys(posts).length > 0 && !isClient && ( -
    - +
    +
    )} + {/* Pagination component */} +