Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added pagination in blog page #3591

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/helpers/applyFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
101 changes: 101 additions & 0 deletions components/navigation/BlogPagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react';
import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';
import IconArrowRight from '../icons/ArrowRight';
import IconArrowLeft from '../icons/ArrowLeft';
import Button from '../buttons/Button';
import { ButtonIconPosition } from '@/types/components/buttons/ButtonPropsType';


interface BlogPaginationProps {
blogsPerPage: number;
totalBlogs: number;
paginate: (pageNumber: number) => void;
}

export default function BlogPagination({ blogsPerPage, totalBlogs, paginate }: BlogPaginationProps) {
const router = useRouter();
const { page } = router.query;
const currentPage: number = parseInt(page as string);
const totalPages: number = Math.ceil(totalBlogs / blogsPerPage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation for currentPage.

The parseInt call is missing a radix parameter and there's no validation for NaN values.

-    const currentPage: number = parseInt(page as string);
+    const currentPage: number = Number.isNaN(parseInt(page as string, 10)) ? 1 : parseInt(page as string, 10);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const currentPage: number = parseInt(page as string);
const totalPages: number = Math.ceil(totalBlogs / blogsPerPage);
const currentPage: number = Number.isNaN(parseInt(page as string, 10)) ? 1 : parseInt(page as string, 10);
const totalPages: number = Math.ceil(totalBlogs / blogsPerPage);
🧰 Tools
🪛 eslint

[error] 19-19: Delete ··

(prettier/prettier)


[error] 19-19: Missing radix parameter.

(radix)


[error] 20-20: Replace ···· with ··

(prettier/prettier)

const pagesToShow: number = 6;
const [pageNumbers, setPageNumbers] = useState<(number | string)[]>([]);

const calculatePageNumbers = () => {
const pageNumbers: (number | string)[] = [];
if (totalPages <= pagesToShow) {
for (let i = 1; i <= totalPages; i++) {
pageNumbers.push(i);
}
} else {
if (currentPage <= 2) {
for (let i = 1; i <= 3; i++) {
pageNumbers.push(i);
}
pageNumbers.push('...');
pageNumbers.push(totalPages - 2);
pageNumbers.push(totalPages - 1);
pageNumbers.push(totalPages);
} else if (currentPage >= totalPages - 1) {
pageNumbers.push(1);
pageNumbers.push(2);
pageNumbers.push(3);
pageNumbers.push('...');
for (let i = totalPages - 2; i <= totalPages; i++) {
pageNumbers.push(i);
}
} else {
pageNumbers.push(1);
pageNumbers.push('...');
pageNumbers.push(currentPage - 1);
pageNumbers.push(currentPage);
pageNumbers.push(currentPage + 1);
pageNumbers.push('...');
pageNumbers.push(totalPages);
}
}
return pageNumbers;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve pagination calculation robustness.

  1. The pageNumbers variable shadows the state variable.
  2. Add validation for edge cases.
 const calculatePageNumbers = () => {
-    const pageNumbers: (number | string)[] = [];
+    const numbers: (number | string)[] = [];
+    if (totalPages < 1) return [];
     if (totalPages <= pagesToShow) {
         for (let i = 1; i <= totalPages; i++) {
-            pageNumbers.push(i);
+            numbers.push(i);
         }
     } else {
         // ... rest of the logic, replace pageNumbers with numbers
     }
-    return pageNumbers;
+    return numbers;
 };

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 eslint

[error] 21-21: Delete ··

(prettier/prettier)


[error] 22-22: Replace ········ with ····

(prettier/prettier)


[error] 22-22: Expected blank line after variable declarations.

(newline-after-var)


[error] 22-22: 'pageNumbers' is already declared in the upper scope on line 19 column 12.

(@typescript-eslint/no-shadow)


[error] 23-23: Delete ····

(prettier/prettier)


[error] 23-53: Expected blank line before this statement.

(padding-line-between-statements)


[error] 24-24: Replace ············ with ······

(prettier/prettier)


[error] 25-25: Replace ················ with ········

(prettier/prettier)


[error] 26-26: Delete ······

(prettier/prettier)


[error] 27-27: Replace ········ with ····

(prettier/prettier)


[error] 28-28: Delete ······

(prettier/prettier)


[error] 28-52: Unexpected if as the only statement in an else block.

(no-lonely-if)


[error] 29-29: Replace ················ with ········

(prettier/prettier)


[error] 30-30: Delete ··········

(prettier/prettier)


[error] 31-31: Replace ················ with ········

(prettier/prettier)


[error] 32-32: Delete ········

(prettier/prettier)


[error] 33-33: Replace ················ with ········

(prettier/prettier)


[error] 34-34: Replace ················ with ········

(prettier/prettier)


[error] 35-35: Replace ················ with ········

(prettier/prettier)


[error] 36-36: Delete ······

(prettier/prettier)


[error] 37-37: Replace ················ with ········

(prettier/prettier)


[error] 38-38: Replace ················ with ········

(prettier/prettier)


[error] 39-39: Replace ················ with ········

(prettier/prettier)


[error] 40-40: Replace ················ with ········

(prettier/prettier)


[error] 41-41: Delete ········

(prettier/prettier)


[error] 42-42: Replace ···················· with ··········

(prettier/prettier)


[error] 43-43: Replace ················ with ········

(prettier/prettier)


[error] 44-44: Delete ······

(prettier/prettier)


[error] 45-45: Replace ················ with ········

(prettier/prettier)


[error] 46-46: Replace ················ with ········

(prettier/prettier)


[error] 47-47: Replace ················ with ········

(prettier/prettier)


[error] 48-48: Replace ················ with ········

(prettier/prettier)


[error] 49-49: Replace ················ with ········

(prettier/prettier)


[error] 50-50: Replace ················ with ········

(prettier/prettier)


[error] 51-51: Delete ········

(prettier/prettier)


[error] 52-52: Delete ······

(prettier/prettier)


[error] 53-53: Replace ········ with ····

(prettier/prettier)


[error] 54-54: Delete ····

(prettier/prettier)


[error] 54-54: Expected blank line before this statement.

(padding-line-between-statements)


[error] 55-55: Delete ··

(prettier/prettier)

🪛 GitHub Actions: PR testing - if Node project

[error] 22-22: Variable 'pageNumbers' is already declared in the upper scope


useEffect(() => {
setPageNumbers(calculatePageNumbers());
}, [currentPage, totalBlogs]);

return (
<div className='flex justify-center items-center p-4 gap-2 mt-8'>
<Button
className={`${currentPage === 1 && 'opacity-50 cursor-not-allowed'} w-[120px] h-[35px] py-2 px-4 rounded-l-md`}
bgClassName='bg-white'
textClassName='text-[#212525] font-inter text-[14px] font-normal'
text='Previous'
disabled={currentPage === 1}
onClick={() => paginate(currentPage - 1)}
icon={<IconArrowLeft className='w-4 h-4 inline-block' />}
iconPosition={ButtonIconPosition.LEFT}
/>
<div className='flex justify-center gap-3 w-[35vw]'>
{pageNumbers.map((number, index) => (
<button
key={index}
className={`w-[40px] h-[40px] ${number === currentPage ? 'border rounded bg-[#6200EE] text-white' : 'text-[#6B6B6B'}`}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix className syntax error.

There's a missing closing bracket in the className string.

-className={`w-[40px] h-[40px] ${number === currentPage ? 'border rounded bg-[#6200EE] text-white' : 'text-[#6B6B6B'}`}
+className={`w-[40px] h-[40px] ${number === currentPage ? 'border rounded bg-[#6200EE] text-white' : 'text-[#6B6B6B]'}`}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className={`w-[40px] h-[40px] ${number === currentPage ? 'border rounded bg-[#6200EE] text-white' : 'text-[#6B6B6B'}`}
className={`w-[40px] h-[40px] ${number === currentPage ? 'border rounded bg-[#6200EE] text-white' : 'text-[#6B6B6B]'}`}
🧰 Tools
🪛 eslint

[error] 78-78: Replace ························ with ············

(prettier/prettier)

onClick={() => typeof number === 'number' && paginate(number)}
disabled={number === '...'}
>
{number}
</button>
))}
</div>
<Button
className={`${currentPage === totalPages && 'opacity-50 cursor-not-allowed'} w-[120px] h-[35px] py-2 px-4 rounded-l-md`}
bgClassName='bg-white'
textClassName='text-[#212525] font-inter text-[14px] font-normal'
text='Next'
disabled={currentPage === totalPages}
onClick={() => paginate(currentPage + 1)}
icon={<IconArrowRight className='w-4 h-4 inline-block' />}
iconPosition={ButtonIconPosition.RIGHT}
/>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize Tailwind classes and improve accessibility.

  1. The pagination buttons need ARIA labels for screen readers
  2. Tailwind classes can be optimized
-        <div className='flex justify-center items-center p-4 gap-2 mt-8'>
+        <nav aria-label="Blog pagination" className='flex justify-center items-center p-4 gap-2 mt-8'>
             <Button
-                className={`${currentPage === 1 && 'opacity-50 cursor-not-allowed'} w-[120px] h-[35px] py-2 px-4 rounded-l-md`}
+                className={`${currentPage === 1 ? 'opacity-50 cursor-not-allowed' : ''} size-[120px] py-2 px-4 rounded-l-md`}
+                aria-label="Previous page"
                 // ... rest of the props
             />
             <div className='flex justify-center gap-3 w-[35vw]'>
                 {pageNumbers.map((number, index) => (
                     <button
                         key={index}
+                        aria-label={`${typeof number === 'number' ? `Go to page ${number}` : 'More pages'}`}
+                        aria-current={number === currentPage ? 'page' : undefined}
                         // ... rest of the props
                     >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className='flex justify-center items-center p-4 gap-2 mt-8'>
<Button
className={`${currentPage === 1 && 'opacity-50 cursor-not-allowed'} w-[120px] h-[35px] py-2 px-4 rounded-l-md`}
bgClassName='bg-white'
textClassName='text-[#212525] font-inter text-[14px] font-normal'
text='Previous'
disabled={currentPage === 1}
onClick={() => paginate(currentPage - 1)}
icon={<IconArrowLeft className='w-4 h-4 inline-block' />}
iconPosition={ButtonIconPosition.LEFT}
/>
<div className='flex justify-center gap-3 w-[35vw]'>
{pageNumbers.map((number, index) => (
<button
key={index}
className={`w-[40px] h-[40px] ${number === currentPage ? 'border rounded bg-[#6200EE] text-white' : 'text-[#6B6B6B'}`}
onClick={() => typeof number === 'number' && paginate(number)}
disabled={number === '...'}
>
{number}
</button>
))}
</div>
<Button
className={`${currentPage === totalPages && 'opacity-50 cursor-not-allowed'} w-[120px] h-[35px] py-2 px-4 rounded-l-md`}
bgClassName='bg-white'
textClassName='text-[#212525] font-inter text-[14px] font-normal'
text='Next'
disabled={currentPage === totalPages}
onClick={() => paginate(currentPage + 1)}
icon={<IconArrowRight className='w-4 h-4 inline-block' />}
iconPosition={ButtonIconPosition.RIGHT}
/>
</div>
<nav aria-label="Blog pagination" className='flex justify-center items-center p-4 gap-2 mt-8'>
<Button
className={`${currentPage === 1 ? 'opacity-50 cursor-not-allowed' : ''} size-[120px] py-2 px-4 rounded-l-md`}
aria-label="Previous page"
bgClassName='bg-white'
textClassName='text-[#212525] font-inter text-[14px] font-normal'
text='Previous'
disabled={currentPage === 1}
onClick={() => paginate(currentPage - 1)}
icon={<IconArrowLeft className='w-4 h-4 inline-block' />}
iconPosition={ButtonIconPosition.LEFT}
/>
<div className='flex justify-center gap-3 w-[35vw]'>
{pageNumbers.map((number, index) => (
<button
key={index}
aria-label={`${typeof number === 'number' ? `Go to page ${number}` : 'More pages'}`}
aria-current={number === currentPage ? 'page' : undefined}
className={`w-[40px] h-[40px] ${number === currentPage ? 'border rounded bg-[#6200EE] text-white' : 'text-[#6B6B6B'}`}
onClick={() => typeof number === 'number' && paginate(number)}
disabled={number === '...'}
>
{number}
</button>
))}
</div>
<Button
className={`${currentPage === totalPages && 'opacity-50 cursor-not-allowed'} w-[120px] h-[35px] py-2 px-4 rounded-l-md`}
bgClassName='bg-white'
textClassName='text-[#212525] font-inter text-[14px] font-normal'
text='Next'
disabled={currentPage === totalPages}
onClick={() => paginate(currentPage + 1)}
icon={<IconArrowRight className='w-4 h-4 inline-block' />}
iconPosition={ButtonIconPosition.RIGHT}
/>
</nav>
🧰 Tools
🪛 eslint

[error] 65-65: Delete ····

(prettier/prettier)


[error] 66-66: Replace ············ with ······

(prettier/prettier)


[error] 67-67: Delete ········

(prettier/prettier)


[error] 68-68: Replace ················ with ········

(prettier/prettier)


[error] 69-69: Delete ········

(prettier/prettier)


[error] 70-70: Replace ················ with ········

(prettier/prettier)


[error] 71-71: Delete ········

(prettier/prettier)


[error] 72-72: Delete ········

(prettier/prettier)


[error] 73-73: Delete ········

(prettier/prettier)


[error] 74-74: Delete ········

(prettier/prettier)


[error] 75-75: Replace ············ with ······

(prettier/prettier)


[error] 76-76: Delete ······

(prettier/prettier)


[error] 77-77: Replace ················ with ········

(prettier/prettier)


[error] 78-78: Delete ··········

(prettier/prettier)


[error] 79-79: Replace ························ with ············

(prettier/prettier)


[error] 80-80: Delete ············

(prettier/prettier)


[error] 81-81: Delete ············

(prettier/prettier)


[error] 82-82: Replace ························ with ············

(prettier/prettier)


[error] 83-83: Delete ··········

(prettier/prettier)


[error] 84-84: Delete ············

(prettier/prettier)


[error] 85-85: Replace ···················· with ··········

(prettier/prettier)


[error] 86-86: Replace ················ with ········

(prettier/prettier)


[error] 87-87: Replace ············ with ······

(prettier/prettier)


[error] 88-88: Delete ······

(prettier/prettier)


[error] 89-89: Delete ········

(prettier/prettier)


[error] 90-90: Replace ················ with ········

(prettier/prettier)


[error] 91-91: Delete ········

(prettier/prettier)


[error] 92-92: Replace ················ with ········

(prettier/prettier)


[error] 93-93: Delete ········

(prettier/prettier)


[error] 94-94: Replace ················ with ········

(prettier/prettier)


[error] 95-95: Delete ········

(prettier/prettier)


[error] 96-96: Replace ················ with ········

(prettier/prettier)


[error] 97-97: Delete ······

(prettier/prettier)


[error] 98-98: Replace ········ with ····

(prettier/prettier)

🪛 GitHub Actions: PR testing - if Node project

[warning] 65-95: Multiple Tailwind CSS issues: invalid classname order and non-usage of available shorthand properties (e.g., 'size-4' instead of 'w-4, h-4')

);
}

84 changes: 69 additions & 15 deletions pages/blog/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRouter } from 'next/router';
import React, { useContext, useEffect, useState } from 'react';
import React, { useContext, useEffect, useState, useRef } from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove unused useRef import.

The useRef hook is imported but never used in the component.

-import React, { useContext, useEffect, useState, useRef } from 'react';
+import React, { useContext, useEffect, useState } from 'react';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import React, { useContext, useEffect, useState, useRef } from 'react';
import React, { useContext, useEffect, useState } from 'react';
🧰 Tools
🪛 eslint

[error] 2-2: 'useRef' is defined but never used.

(unused-imports/no-unused-imports)


[error] 2-2: 'useRef' is defined but never used.

(no-unused-vars)

🪛 GitHub Actions: PR testing - if Node project

[error] 2-2: Unused import: 'useRef' is defined but never used


import Empty from '@/components/illustrations/Empty';
import GenericLayout from '@/components/layout/GenericLayout';
Expand All @@ -13,7 +13,7 @@ import BlogContext from '@/context/BlogContext';
import type { IBlogPost } from '@/types/post';
import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';
import { ParagraphTypeStyle } from '@/types/typography/Paragraph';

import BlogPagination from '@/components/navigation/BlogPagination';
/**
* @description The BlogIndexPage is the blog index page of the website.
*/
Expand All @@ -24,19 +24,21 @@ export default function BlogIndexPage() {
const [posts, setPosts] = useState<IBlogPost[]>(
navItems
? navItems.sort((i1: IBlogPost, i2: IBlogPost) => {
const i1Date = new Date(i1.date);
const i2Date = new Date(i2.date);
const i1Date = new Date(i1.date);
const i2Date = new Date(i2.date);

if (i1.featured && !i2.featured) return -1;
if (!i1.featured && i2.featured) return 1;
if (i1.featured && !i2.featured) return -1;
if (!i1.featured && i2.featured) return 1;

return i2Date.getTime() - i1Date.getTime();
})
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'
Expand All @@ -50,14 +52,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 [currentPosts, setCurrentPosts] = useState<IBlogPost[]>([]);

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){
const pageFromQuery = parseInt(router.query.page as string);
if(!pageFromQuery){
router.replace(
{
pathname: router.pathname,
query: { ...router.query, page: '1' },
},
undefined,
{ shallow: true }
);
}
const indexOfLastPost = pageFromQuery * blogsPerPage;
const indexOfFirstPost = indexOfLastPost - blogsPerPage;
setCurrentPosts(posts.slice(indexOfFirstPost, indexOfLastPost));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve pagination state management and performance.

  1. The posts slicing logic runs on every query change
  2. Missing error handling for invalid page numbers
  3. The pagination state could be managed more efficiently

Consider using a custom hook to manage pagination state:

function usePagination<T>(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
  };
}
🧰 Tools
🪛 eslint

[error] 75-91: Expected blank line after variable declarations.

(newline-after-var)


[error] 77-80: Expected blank line after variable declarations.

(newline-after-var)


[error] 81-90: Expected blank line before this statement.

(padding-line-between-statements)


[error] 86-86: Trailing spaces not allowed.

(no-trailing-spaces)


[error] 86-86: Delete ·

(prettier/prettier)


[error] 93-93: Expected space(s) after "if".

(keyword-spacing)


[error] 93-93: Replace (router.isReady) with ·(router.isReady)·

(prettier/prettier)


[error] 93-108: Missing space before opening brace.

(space-before-blocks)


[error] 94-94: Expected blank line after variable declarations.

(newline-after-var)


[error] 94-94: Missing radix parameter.

(radix)


[error] 95-104: Expected blank line before this statement.

(padding-line-between-statements)


[error] 95-95: Expected space(s) after "if".

(keyword-spacing)


[error] 95-95: Replace (!pageFromQuery) with ·(!pageFromQuery)·

(prettier/prettier)


[error] 95-104: Missing space before opening brace.

(space-before-blocks)


[error] 99-99: Delete ,

(prettier/prettier)


[error] 99-99: Unexpected trailing comma.

(comma-dangle)


[error] 106-106: Expected blank line after variable declarations.

(newline-after-var)


[error] 107-107: Expected blank line before this statement.

(padding-line-between-statements)

}, [router.isReady, router.query.page, posts]);

useEffect(() => {
setIsClient(true);
Expand Down Expand Up @@ -114,15 +163,15 @@ export default function BlogIndexPage() {
)}
</div>
<div>
{Object.keys(posts).length === 0 && (
{(Object.keys(posts).length === 0) && (
<div className='mt-16 flex flex-col items-center justify-center'>
<Empty />
<p className='mx-auto mt-3 max-w-2xl text-xl leading-7 text-gray-500'>No post matches your filter</p>
</div>
)}
{Object.keys(posts).length > 0 && isClient && (
<ul className='mx-auto mt-12 grid max-w-lg gap-5 lg:max-w-none lg:grid-cols-3'>
{posts.map((post, index) => (
{currentPosts.map((post, index) => (
<BlogPostItem key={index} post={post} />
))}
</ul>
Expand All @@ -132,6 +181,11 @@ export default function BlogIndexPage() {
<Loader loaderText='Loading Blogs' className='mx-auto my-60' pulsating />
</div>
)}
<BlogPagination
blogsPerPage={blogsPerPage}
totalBlogs={posts.length}
paginate={paginate}
/>
</div>
</div>
</div>
Expand Down
Loading