feat(poc): add pagination to category page

This commit is contained in:
Björn Meyer 2023-07-12 10:02:08 +02:00
parent 7ca79902d3
commit ef1fb57535
7 changed files with 99 additions and 10 deletions

View File

@ -4,6 +4,7 @@ import { notFound } from 'next/navigation';
import Grid from 'components/grid'; import Grid from 'components/grid';
import ProductGridItems from 'components/layout/product-grid-items'; import ProductGridItems from 'components/layout/product-grid-items';
import Pagination from 'components/collection/pagination';
import { defaultSort, sorting } from 'lib/constants'; import { defaultSort, sorting } from 'lib/constants';
export const runtime = 'edge'; export const runtime = 'edge';
@ -34,7 +35,7 @@ export default async function CategoryPage({
const { sort, page } = searchParams as { [key: string]: string }; const { sort, page } = searchParams as { [key: string]: string };
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort; const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
const products = await getCollectionProducts({ const { products, total, limit } = await getCollectionProducts({
collection: params.collection, collection: params.collection,
page: page ? parseInt(page) : 1, page: page ? parseInt(page) : 1,
sortKey, sortKey,
@ -46,9 +47,14 @@ export default async function CategoryPage({
{products.length === 0 ? ( {products.length === 0 ? (
<p className="py-3 text-lg">{`No products found in this collection`}</p> <p className="py-3 text-lg">{`No products found in this collection`}</p>
) : ( ) : (
<div>
<Grid className="grid-cols-2 lg:grid-cols-3"> <Grid className="grid-cols-2 lg:grid-cols-3">
<ProductGridItems products={products} /> <ProductGridItems products={products} />
</Grid> </Grid>
<nav aria-label="Collection pagination" className='block sm:flex items-center'>
<Pagination itemsPerPage={limit} itemsTotal={total} currentPage={page ? parseInt(page) - 1 : 0} />
</nav>
</div>
)} )}
</section> </section>
); );

View File

@ -0,0 +1,68 @@
'use client'
import ReactPaginate from 'react-paginate';
import { createUrl } from 'lib/utils';
import { usePathname, useSearchParams, useRouter } from 'next/navigation';
export default function Pagination({ itemsPerPage, itemsTotal, currentPage }: { itemsPerPage: number, itemsTotal: number, currentPage: number }) {
const router = useRouter();
const pathname = usePathname();
const currentParams = useSearchParams();
const q = currentParams.get('q');
const sort = currentParams.get('sort');
const pageCount = Math.ceil(itemsTotal / itemsPerPage);
// Invoke when user click to request another page.
const handlePageClick = (event: clickEvent) => {
const page = event.selected;
const newPage = page + 1;
let newUrl = createUrl(pathname, new URLSearchParams({
...(q && { q }),
...(sort && { sort }),
}));
if (page !== 0) {
newUrl = createUrl(pathname, new URLSearchParams({
...(q && { q }),
...(sort && { sort }),
page: newPage.toString(),
}));
}
router.replace(newUrl);
};
return (
<>
<ReactPaginate
onPageChange={handlePageClick}
pageRangeDisplayed={itemsPerPage}
initialPage={currentPage}
pageCount={pageCount}
breakLabel="..."
nextLabel=">>"
previousLabel="<<"
renderOnZeroPageCount={null}
containerClassName="inline sm:flex text-base h-10 mx-auto"
activeClassName="active"
pageClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white [&.active]:bg-gray-100"
pageLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight"
previousClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white [&.disabled]:hidden"
previousLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight"
nextClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white [&.disabled]:hidden"
nextLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight"
breakClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
breakLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight"
/>
</>
);
}
type clickEvent = {
index: number | null;
selected: number;
nextSelectedPage: number | undefined;
event: object;
isPrevious: boolean;
isNext: boolean;
isBreak: boolean;
isActive: boolean;
}

View File

@ -37,7 +37,7 @@ function ThreeItemGridItem({
export async function ThreeItemGrid() { export async function ThreeItemGrid() {
// Collections that start with `hidden-*` are hidden from the search page. // Collections that start with `hidden-*` are hidden from the search page.
const homepageItems = await getCollectionProducts({ const { products: homepageItems } = await getCollectionProducts({
collection: 'Summer-BBQ/Hidden-Category' collection: 'Summer-BBQ/Hidden-Category'
}); });

View File

@ -40,6 +40,7 @@ function SortFilterItem({ item }: { item: SortFilterItem }) {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [active, setActive] = useState(searchParams.get('sort') === item.slug); const [active, setActive] = useState(searchParams.get('sort') === item.slug);
const q = searchParams.get('q'); const q = searchParams.get('q');
const page = searchParams.get('page');
useEffect(() => { useEffect(() => {
setActive(searchParams.get('sort') === item.slug); setActive(searchParams.get('sort') === item.slug);
@ -51,6 +52,7 @@ function SortFilterItem({ item }: { item: SortFilterItem }) {
pathname, pathname,
new URLSearchParams({ new URLSearchParams({
...(q && { q }), ...(q && { q }),
...(page && { page }),
sort: item.slug sort: item.slug
}) })
) )

View File

@ -109,7 +109,7 @@ export async function getCollectionProducts(params?: {
sortKey?: string; sortKey?: string;
categoryId?: string; categoryId?: string;
defaultSearchCriteria?: Partial<ProductListingCriteria>; defaultSearchCriteria?: Partial<ProductListingCriteria>;
}): Promise<Product[]> { }): Promise<{ products: Product[]; total: number; limit: number }> {
let res; let res;
let category = params?.categoryId; let category = params?.categoryId;
const collectionName = transformHandle(params?.collection ?? ''); const collectionName = transformHandle(params?.collection ?? '');
@ -136,7 +136,9 @@ export async function getCollectionProducts(params?: {
res = await requestCategoryProductsCollection(category, productsCriteria); res = await requestCategoryProductsCollection(category, productsCriteria);
} }
return res ? transformProducts(res) : []; return res
? { products: transformProducts(res), total: res.total ?? 0, limit: res.limit ?? 0 }
: { products: [], total: 0, limit: 0 };
} }
export async function getCategory( export async function getCategory(

View File

@ -30,7 +30,8 @@
"next": "13.4.9", "next": "13.4.9",
"react": "18.2.0", "react": "18.2.0",
"react-cookie": "^4.1.1", "react-cookie": "^4.1.1",
"react-dom": "18.2.0" "react-dom": "18.2.0",
"react-paginate": "^8.2.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.35.1", "@playwright/test": "^1.35.1",

14
pnpm-lock.yaml generated
View File

@ -28,6 +28,9 @@ dependencies:
react-dom: react-dom:
specifier: 18.2.0 specifier: 18.2.0
version: 18.2.0(react@18.2.0) version: 18.2.0(react@18.2.0)
react-paginate:
specifier: ^8.2.0
version: 8.2.0(react@18.2.0)
devDependencies: devDependencies:
'@playwright/test': '@playwright/test':
@ -2411,7 +2414,6 @@ packages:
/object-assign@4.1.1: /object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true
/object-hash@3.0.0: /object-hash@3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
@ -2811,7 +2813,6 @@ packages:
loose-envify: 1.4.0 loose-envify: 1.4.0
object-assign: 4.1.1 object-assign: 4.1.1
react-is: 16.13.1 react-is: 16.13.1
dev: true
/punycode@2.3.0: /punycode@2.3.0:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
@ -2846,6 +2847,15 @@ packages:
/react-is@16.13.1: /react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
/react-paginate@8.2.0(react@18.2.0):
resolution: {integrity: sha512-sJCz1PW+9PNIjUSn919nlcRVuleN2YPoFBOvL+6TPgrH/3lwphqiSOgdrLafLdyLDxsgK+oSgviqacF4hxsDIw==}
peerDependencies:
react: ^16 || ^17 || ^18
dependencies:
prop-types: 15.8.1
react: 18.2.0
dev: false
/react@18.2.0: /react@18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}