Skip to content

Commit

Permalink
add pagination feature on blog page
Browse files Browse the repository at this point in the history
  • Loading branch information
priyanshuxkumar committed Jan 21, 2025
1 parent cf3ca64 commit af0e6a1
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 2 deletions.
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
30 changes: 30 additions & 0 deletions components/helpers/usePagination.ts
Original file line number Diff line number Diff line change
@@ -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<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
};
}
122 changes: 122 additions & 0 deletions components/pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='font-inter flex items-center justify-center gap-8'>
{/* Previous button */}
<button
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
className={`
font-normal flex h-[34px] items-center justify-center gap-2 rounded bg-white px-4
py-[7px] text-sm leading-[17px] tracking-[-0.01em]
${currentPage === 1 ? 'cursor-not-allowed text-gray-300' : 'text-[#141717] hover:bg-gray-50'}
`}
>
<svg
width='20'
height='20'
viewBox='0 0 24 24'
fill='none'
xmlns='http://www.w3.org/2000/svg'
className='stroke-current'
>
<path d='M15 18L9 12L15 6' strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' />
</svg>
<span>Previous</span>
</button>

{/* Page numbers */}
<div className='flex gap-2'>
{getPageNumbers().map((page) =>
typeof page === 'number' ? (
<PaginationItem
key={page}
pageNumber={page}
isActive={page === currentPage}
onPageChange={handlePageChange}
/>
) : (
<span
key={page}
className='font-inter flex size-10 items-center justify-center text-sm font-semibold text-[#6B6B6B]'
>
...
</span>
)
)}
</div>

{/* Next button */}
<button
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
className={`
font-normal flex h-[34px] items-center justify-center gap-2 rounded bg-white px-4
py-[7px] text-sm leading-[17px] tracking-[-0.01em]
${currentPage === totalPages ? 'cursor-not-allowed text-gray-300' : 'text-[#141717] hover:bg-gray-50'}
`}
>
<span>Next</span>
<svg
width='20'
height='20'
viewBox='0 0 24 24'
fill='none'
xmlns='http://www.w3.org/2000/svg'
className='stroke-current'
>
<path d='M9 6L15 12L9 18' strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' />
</svg>
</button>
</div>
);
}
32 changes: 32 additions & 0 deletions components/pagination/PaginationItem.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<button
onClick={() => onPageChange(pageNumber)}
className={`font-inter font-normal relative flex size-10 items-center
justify-center rounded-full text-sm leading-[26px]
${isActive ? 'bg-[#6200EE] text-white' : 'bg-transparent text-[#141717] hover:bg-gray-50'}
`}
aria-current={isActive ? 'page' : undefined}
>
{pageNumber}
</button>
);
}
38 changes: 36 additions & 2 deletions pages/blog/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -122,16 +151,21 @@ export default function BlogIndexPage() {
)}
{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) => (
{currentItems.map((post, index) => (
<BlogPostItem key={index} post={post} />
))}
</ul>
)}
{Object.keys(posts).length > 0 && !isClient && (
{Object.keys(currentItems).length > 0 && !isClient && (
<div className='h-screen w-full'>
<Loader loaderText='Loading Blogs' className='mx-auto my-60' pulsating />
</div>
)}
{maxPage > 1 && (
<div className='mt-8 w-full'>
<Pagination totalPages={maxPage} currentPage={currentPage} onPageChange={handlePageChange} />
</div>
)}
</div>
</div>
</div>
Expand Down

0 comments on commit af0e6a1

Please sign in to comment.