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

Feature/clientside refactor #215

Merged
merged 11 commits into from
Dec 14, 2023
Merged
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
Prev Previous commit
Next Next commit
products page
nlkluth committed Dec 13, 2023
commit e4fa1424ed5446530696d26b2c7683addb0acec3
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";
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;
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">;

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">
@@ -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>
4 changes: 2 additions & 2 deletions packages/nextjs/app/migration/products/[slug].tsx
Original file line number Diff line number Diff line change
@@ -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";
Loading