Skip to content

Commit

Permalink
products page
Browse files Browse the repository at this point in the history
  • Loading branch information
nlkluth committed Dec 13, 2023
1 parent dd27683 commit e4fa142
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 152 deletions.
84 changes: 84 additions & 0 deletions packages/nextjs/app/components/ModalFiltersProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use client";

import React from "react";
import { ModalFiltersMobile } from "views/ModalFiltersMobile";
import { useDeviceSize } from "utils/useDeviceSize";
import { CategoryFilterItem, FlavourFilterItem, StyleFilterItem } from "utils/groqTypes/ProductList";

// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};

const initialValues = {
handleOpenModal: noop,
handleCloseModal: noop,
isModalOpen: false,
};

const ModalFiltersContext = React.createContext(initialValues);

export const useModalFilters = () => {
const context = React.useContext(ModalFiltersContext);

if (!context) {
throw new Error("useModalFilters must be used within a ModalFiltersContext.Provider");
}

return context;
};

type Props = {
categoryFilters: CategoryFilterItem[];
flavourFilters: FlavourFilterItem[];
styleFilters: StyleFilterItem[];
};

const ModalFiltersProvider = ({
children,
categoryFilters,
flavourFilters,
styleFilters,
}: React.PropsWithChildren<Props>) => {
const { isSm } = useDeviceSize();
const [isModalOpen, setIsModalOpen] = React.useState(false);

const handleOpenModal = React.useCallback(() => {
setIsModalOpen(true);
}, []);

const handleCloseModal = React.useCallback(() => {
setIsModalOpen(false);
}, []);

React.useEffect(() => {
// If modal is open and the window size changes to tablet/desktop viewport,
// then closes the modal
if (!isSm) {
setIsModalOpen(false);
}
}, [isSm]);

const value = React.useMemo(
() => ({
handleOpenModal,
handleCloseModal,
isModalOpen,
}),
[handleCloseModal, handleOpenModal, isModalOpen]
);

return (
<ModalFiltersContext.Provider value={value}>
{children}
{/* Modal UI for filters (mobile only) */}
<ModalFiltersMobile
flavourFilters={flavourFilters}
styleFilters={styleFilters}
categoryFilters={categoryFilters}
isOpen={isModalOpen}
onClose={handleCloseModal}
/>
</ModalFiltersContext.Provider>
);
};

export default ModalFiltersProvider;
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use client";

import * as React from "react";
import { Pagination as BasePagination } from "shared-ui";
import { Pagination as BasePagination } from "../ui/shared-ui";
import Link from "next/link";
import classNames from "classnames";
import { usePathname, useSearchParams } from "next/navigation";
Expand Down
35 changes: 35 additions & 0 deletions packages/nextjs/app/components/PaginationFade.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";

import classNames from "classnames";
import { FadeInOut } from "../ui/shared-ui";
import { useSearchParams } from "next/navigation";
import { pluralize } from "utils/pluralize";
import { PLPVariant } from "utils/groqTypes/ProductList";

type Props = {
variants: PLPVariant[];
};

const PaginationFade = ({ children, variants }: React.PropsWithChildren<Props>) => {
const query = useSearchParams();
const productNames = pluralize(variants.map((prod) => prod.name));

return (
<FadeInOut
className={classNames(
"w-full grid grid-cols-2 sm:grid-cols-3 md:grid-cols-2 lg:grid-cols-3 gap-x-3 gap-y-9 mb-9",
+(query?.get("page") || 1) > 1 && "grid-rows-2"
)}
key={productNames}
>
{children}
{/* Add padder items when on page > 1 so pagination bar isn't moving around */}
{+(query?.get("page") || 1) > 1 &&
Array.from({ length: 6 - variants.length })
.fill(undefined)
.map((_, i) => <div key={i} className="invisible" />)}
</FadeInOut>
);
};

export default PaginationFade;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Link from "next/link";
import { Price } from "shared-ui";
import { Price } from "../ui/shared-ui";
import { PLPVariant } from "utils/groqTypes/ProductList";
import { Image } from "./Image";
import { Image } from "../../components/Image";

type Props = {
item: PLPVariant;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use client";

import * as React from "react";
import { useRouterQueryParams } from "utils/useRouterQueryParams";
import { ProductSort as BaseProductSort, ProductSortProps as BaseProps } from "shared-ui";
import { ProductSort as BaseProductSort, ProductSortProps as BaseProps } from "../ui/shared-ui";

type ProductSortProps = Pick<BaseProps, "as" | "showTitle" | "title" | "selectClassName">;

Expand Down
87 changes: 87 additions & 0 deletions packages/nextjs/app/components/ProductsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as React from "react";
import { AnimatePresence } from "../ui/framer";

import { H6, WeDontSellBreadBanner } from "../ui/shared-ui";
import { CategoryFilterItem, FlavourFilterItem, PLPVariant, StyleFilterItem } from "utils/groqTypes/ProductList";

import { ProductFilters } from "components/ProductFilters/ProductFilters";
import { Product } from "app/components/Product";
import { Pagination } from "app/components/Pagination";
import { Breadcrumbs } from "components/Breadcrumbs";
import { SortAndFiltersToolbarMobile } from "app/components/SortAndFiltersToolbarMobile";
import { ProductSort } from "app/components/ProductSort";
import PaginationFade from "./PaginationFade";

interface ProductListProps {
variants: PLPVariant[];
itemCount: number;
pageSize: number;
pageCount: number;
currentPage?: number;
categoryFilters: CategoryFilterItem[];
flavourFilters: FlavourFilterItem[];
styleFilters: StyleFilterItem[];
}

const ProductList = ({
variants,
pageCount,
currentPage,
categoryFilters,
flavourFilters,
styleFilters,
}: ProductListProps) => {
return (
<>
<div>
<WeDontSellBreadBanner />
<div className="py-9 container">
<h1 className="text-h1 text-primary mb-9">Products</h1>
<section className="flex gap-9 flex-col md:flex-row">
<div className="hidden w-full md:w-72 order-2 md:order-1 md:flex flex-col gap-9">
<ProductSort showTitle />
<ProductFilters
flavourFilters={flavourFilters}
styleFilters={styleFilters}
categoryFilters={categoryFilters}
/>
</div>

<div className="flex-1 order-1 md:order-2">
<div className="mb-4">
<Breadcrumbs />
</div>

{/**
*
* Product Sort (select) and product filters (mobile only).
* See Modal component below
*
*/}
<SortAndFiltersToolbarMobile />

<AnimatePresence mode="wait">
{variants.length > 0 && (
<PaginationFade variants={variants}>
{variants.map((variant, index) => (
<Product key={variant._id} item={variant} priorityImage={index === 0} />
))}
</PaginationFade>
)}

{variants.length === 0 && (
<div className="flex-1 flex flex-col justify-center items-center">
<H6 className="text-center">No products found</H6>
</div>
)}
</AnimatePresence>
{variants.length > 0 && <Pagination key="pagination" pageCount={pageCount} currentPage={currentPage} />}
</div>
</section>
</div>
</div>
</>
);
};

export default ProductList;
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Button } from "shared-ui";
"use client";

import { Button } from "../ui/shared-ui";
import React from "react";
import { MdOutlineFilterList } from "react-icons/md";
import { ProductSort } from "components/ProductSort";
import { ProductSort } from "app/components/ProductSort";
import { useGetFiltersCount } from "utils/getFiltersCount";
import { useModalFilters } from "app/components/ModalFiltersProvider";

interface SortAndFiltersToolbarMobileProps {
onFiltersClick?: React.MouseEventHandler;
}

export const SortAndFiltersToolbarMobile: React.FC<SortAndFiltersToolbarMobileProps> = ({ onFiltersClick }) => {
export const SortAndFiltersToolbarMobile: React.FC = () => {
const total = useGetFiltersCount();
const { handleOpenModal } = useModalFilters();

return (
<div className="my-6 mb-8 w-full inline-flex items-end justify-between md:hidden">
Expand All @@ -20,7 +20,7 @@ export const SortAndFiltersToolbarMobile: React.FC<SortAndFiltersToolbarMobilePr
type="button"
variant="primary"
leftIcon={<MdOutlineFilterList className="w-5 h-5" />}
onClick={onFiltersClick}
onClick={handleOpenModal}
>
Filters {total > 0 ? `(${total})` : ""}
</Button>
Expand Down
4 changes: 2 additions & 2 deletions packages/nextjs/app/migration/products/[slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { useState } from "react";
import { NextPage } from "next";
import { AnimatePresence } from "framer-motion";

import { H6, FadeInOut, BlockContent, Price, QuantityInput, useCart } from "shared-ui";
import { H6, FadeInOut, BlockContent, Price, QuantityInput, useCart } from "../../ui/shared-ui";
import { getRecommendations } from "utils/getRecommendationsQuery";

import { ImageCarousel } from "components/ImageCarousel";
import { StyleOptions } from "components/ProductPage/StyleOptions";
import { ProductVariantSelector } from "components/ProductPage/ProductVariantSelector";
import { Product } from "components/Product";
import { Product } from "app/components/Product";
import { Breadcrumbs } from "components/Breadcrumbs";
import { useSearchParams, useRouter } from "next/navigation";
import { ProductDetail, ProductDetailVariants } from "utils/groqTypes/ProductDetail";
Expand Down
Loading

0 comments on commit e4fa142

Please sign in to comment.