mirror of
https://github.com/vercel/commerce.git
synced 2025-05-15 05:56:59 +00:00
feat(poc): add pagination to category page
This commit is contained in:
parent
7ca79902d3
commit
ef1fb57535
@ -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>
|
||||||
) : (
|
) : (
|
||||||
<Grid className="grid-cols-2 lg:grid-cols-3">
|
<div>
|
||||||
<ProductGridItems products={products} />
|
<Grid className="grid-cols-2 lg:grid-cols-3">
|
||||||
</Grid>
|
<ProductGridItems products={products} />
|
||||||
|
</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>
|
||||||
);
|
);
|
||||||
|
68
components/collection/pagination.tsx
Normal file
68
components/collection/pagination.tsx
Normal 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;
|
||||||
|
}
|
@ -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'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -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(
|
||||||
|
@ -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
14
pnpm-lock.yaml
generated
@ -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'}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user