From 7ef0e2273e088c4bb85d85c375144a0f7a75d2f3 Mon Sep 17 00:00:00 2001 From: Bel Curcio Date: Tue, 8 Jun 2021 11:31:48 -0300 Subject: [PATCH] Perf changes --- .../product/ProductOptions/ProductOptions.tsx | 77 ++++--- .../ProductSidebar/ProductSidebar.module.css | 84 +++++++ .../product/ProductSidebar/ProductSidebar.tsx | 86 +++++++ components/product/ProductSidebar/index.ts | 1 + .../product/ProductView/ProductView.tsx | 209 ++++++------------ components/product/Swatch/Swatch.tsx | 86 +++---- components/ui/Collapse/Collapse.tsx | 4 +- components/ui/Rating/Rating.tsx | 4 +- pages/index.tsx | 4 +- 9 files changed, 326 insertions(+), 229 deletions(-) create mode 100644 components/product/ProductSidebar/ProductSidebar.module.css create mode 100644 components/product/ProductSidebar/ProductSidebar.tsx create mode 100644 components/product/ProductSidebar/index.ts diff --git a/components/product/ProductOptions/ProductOptions.tsx b/components/product/ProductOptions/ProductOptions.tsx index 52d05744e..9261406bc 100644 --- a/components/product/ProductOptions/ProductOptions.tsx +++ b/components/product/ProductOptions/ProductOptions.tsx @@ -1,51 +1,50 @@ import { Swatch } from '@components/product' import type { ProductOption } from '@commerce/types/product' import { SelectedOptions } from '../helpers' - +import React from 'react' interface ProductOptionsProps { options: ProductOption[] selectedOptions: SelectedOptions setSelectedOptions: React.Dispatch> } -const ProductOptions: React.FC = ({ - options, - selectedOptions, - setSelectedOptions, -}) => { - return ( -
- {options.map((opt) => ( -
-

- {opt.displayName} -

-
- {opt.values.map((v, i: number) => { - const active = selectedOptions[opt.displayName.toLowerCase()] - return ( - { - setSelectedOptions((selectedOptions) => { - return { - ...selectedOptions, - [opt.displayName.toLowerCase()]: v.label.toLowerCase(), - } - }) - }} - /> - ) - })} +const ProductOptions: React.FC = React.memo( + ({ options, selectedOptions, setSelectedOptions }) => { + return ( +
+ {options.map((opt) => ( +
+

+ {opt.displayName} +

+
+ {opt.values.map((v, i: number) => { + const active = selectedOptions[opt.displayName.toLowerCase()] + return ( + { + setSelectedOptions((selectedOptions) => { + return { + ...selectedOptions, + [opt.displayName.toLowerCase()]: + v.label.toLowerCase(), + } + }) + }} + /> + ) + })} +
-
- ))} -
- ) -} + ))} +
+ ) + } +) export default ProductOptions diff --git a/components/product/ProductSidebar/ProductSidebar.module.css b/components/product/ProductSidebar/ProductSidebar.module.css new file mode 100644 index 000000000..b6ecc2b77 --- /dev/null +++ b/components/product/ProductSidebar/ProductSidebar.module.css @@ -0,0 +1,84 @@ +.root { + @apply relative grid items-start gap-1 grid-cols-1 overflow-x-hidden; + min-height: auto; +} + +.main { + @apply relative px-0 pb-0 box-border flex flex-col col-span-1; + min-height: 500px; +} + +.header { + @apply transition-colors ease-in-out duration-500 + absolute top-0 left-0 z-20 pr-16; +} + +.header .name { + @apply pt-0 max-w-full w-full leading-extra-loose; + font-size: 2rem; + letter-spacing: 0.4px; +} + +.header .name span { + @apply py-4 px-6 bg-primary text-primary font-bold; + font-size: inherit; + letter-spacing: inherit; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; +} + +.header .price { + @apply pt-2 px-6 pb-4 text-sm bg-primary text-accent-9 + font-semibold inline-block tracking-wide; +} + +.sidebar { + @apply flex flex-col col-span-1 mx-auto max-w-8xl px-6 py-6 w-full h-full; +} + +.sliderContainer { + @apply flex items-center justify-center overflow-x-hidden bg-violet; +} + +.imageContainer { + @apply text-center; +} + +.imageContainer > div, +.imageContainer > div > div { + @apply h-full; +} + +.sliderContainer .img { + @apply w-full h-auto max-h-full object-cover; +} + +.button { + width: 100%; +} + +.wishlistButton { + @apply absolute z-30 top-0 right-0; +} + +.relatedProductsGrid { + @apply grid grid-cols-2 py-2 gap-2 md:grid-cols-4 md:gap-7; +} + +@screen lg { + .root { + @apply grid-cols-12; + } + + .main { + @apply mx-0 col-span-8; + } + + .sidebar { + @apply col-span-4 py-6; + } + + .imageContainer { + max-height: 600px; + } +} diff --git a/components/product/ProductSidebar/ProductSidebar.tsx b/components/product/ProductSidebar/ProductSidebar.tsx new file mode 100644 index 000000000..1914f9bef --- /dev/null +++ b/components/product/ProductSidebar/ProductSidebar.tsx @@ -0,0 +1,86 @@ +import s from './ProductSidebar.module.css' +import { useAddItem } from '@framework/cart' +import { FC, useEffect, useState } from 'react' +import { ProductOptions } from '@components/product' +import type { Product } from '@commerce/types/product' +import { Button, Text, Rating, Collapse, useUI } from '@components/ui' +import { + getProductVariant, + selectDefaultOptionFromProduct, + SelectedOptions, +} from '../helpers' + +interface ProductSidebarProps { + product: Product +} + +const ProductSidebar: FC = ({ product }) => { + const addItem = useAddItem() + const { openSidebar } = useUI() + const [loading, setLoading] = useState(false) + const [selectedOptions, setSelectedOptions] = useState({}) + + useEffect(() => { + selectDefaultOptionFromProduct(product, setSelectedOptions) + }, []) + + const variant = getProductVariant(product, selectedOptions) + const addToCart = async () => { + setLoading(true) + try { + await addItem({ + productId: String(product.id), + variantId: String(variant ? variant.id : product.variants[0].id), + }) + openSidebar() + setLoading(false) + } catch (err) { + setLoading(false) + } + } + + return ( + <> + + +
+ +
36 reviews
+
+
+ +
+
+ + This is a limited edition production run. Printing starts when the + drop ends. + + + This is a limited edition production run. Printing starts when the + drop ends. Reminder: Bad Boys For Life. Shipping may take 10+ days due + to COVID-19. + +
+ + ) +} + +export default ProductSidebar diff --git a/components/product/ProductSidebar/index.ts b/components/product/ProductSidebar/index.ts new file mode 100644 index 000000000..7e00359c4 --- /dev/null +++ b/components/product/ProductSidebar/index.ts @@ -0,0 +1 @@ +export { default } from './ProductSidebar' diff --git a/components/product/ProductView/ProductView.tsx b/components/product/ProductView/ProductView.tsx index c78357eb7..5a61255ee 100644 --- a/components/product/ProductView/ProductView.tsx +++ b/components/product/ProductView/ProductView.tsx @@ -2,65 +2,27 @@ import cn from 'classnames' import Image from 'next/image' import { NextSeo } from 'next-seo' import s from './ProductView.module.css' -import { FC, useEffect, useState } from 'react' +import { FC } from 'react' import type { Product } from '@commerce/types/product' import usePrice from '@framework/product/use-price' -import { - getProductVariant, - selectDefaultOptionFromProduct, - SelectedOptions, -} from '../helpers' -import { useAddItem } from '@framework/cart' import { WishlistButton } from '@components/wishlist' -import { ProductSlider, ProductCard, ProductOptions } from '@components/product' -import { - Button, - Container, - Text, - useUI, - Rating, - Collapse, -} from '@components/ui' - +import { ProductSlider, ProductCard } from '@components/product' +import { Container, Text } from '@components/ui' +import ProductSidebar from '../ProductSidebar' interface ProductViewProps { product: Product - className?: string relatedProducts: Product[] - children?: React.ReactNode } const ProductView: FC = ({ product, relatedProducts }) => { - const { openSidebar } = useUI() - const [loading, setLoading] = useState(false) - const [selectedOptions, setSelectedOptions] = useState({}) - const addItem = useAddItem() const { price } = usePrice({ amount: product.price.value, baseAmount: product.price.retailPrice, currencyCode: product.price.currencyCode!, }) - useEffect(() => { - selectDefaultOptionFromProduct(product, setSelectedOptions) - }, []) - - const variant = getProductVariant(product, selectedOptions) - const addToCart = async () => { - setLoading(true) - try { - await addItem({ - productId: String(product.id), - variantId: String(variant ? variant.id : product.variants[0].id), - }) - openSidebar() - setLoading(false) - } catch (err) { - setLoading(false) - } - } - return ( - + <> = ({ product, relatedProducts }) => { ], }} /> -
-
-
-

- {product.name} -

-
- {`${price} ${product.price?.currencyCode}`} + +
+
+
+

+ {product.name} +

+
+ {`${price} ${product.price?.currencyCode}`} +
-
-
- - {product.images.map((image, i) => ( -
- {image.alt -
- ))} -
-
- {process.env.COMMERCE_WISHLIST_ENABLED && ( - - )} -
-
- - -
- -
- 36 reviews +
+ + {product.images.map((image, i) => ( +
+ {image.alt +
+ ))} +
-
-
- -
-
- - This is a limited edition production run. Printing starts when the - drop ends. - - - This is a limited edition production run. Printing starts when the - drop ends. Reminder: Bad Boys For Life. Shipping may take 10+ days - due to COVID-19. - -
-
-
-
-
- Related Products -
- {relatedProducts.map((p) => ( -
- -
- ))} + )} +
+
+ +
- - +
+
+ Related Products +
+ {relatedProducts.map((p) => ( +
+ +
+ ))} +
+
+ + ) } diff --git a/components/product/Swatch/Swatch.tsx b/components/product/Swatch/Swatch.tsx index bb8abf3d1..d04e77a78 100644 --- a/components/product/Swatch/Swatch.tsx +++ b/components/product/Swatch/Swatch.tsx @@ -1,5 +1,5 @@ import cn from 'classnames' -import { FC } from 'react' +import React from 'react' import s from './Swatch.module.css' import { Check } from '@components/icons' import Button, { ButtonProps } from '@components/ui/Button' @@ -13,48 +13,50 @@ interface SwatchProps { label?: string | null } -const Swatch: FC & SwatchProps> = ({ - className, - color = '', - label = null, - variant = 'size', - active, - ...props -}) => { - variant = variant?.toLowerCase() +const Swatch: React.FC & SwatchProps> = React.memo( + ({ + active, + className, + color = '', + label = null, + variant = 'size', + ...props + }) => { + variant = variant?.toLowerCase() - if (label) { - label = label?.toLowerCase() + if (label) { + label = label?.toLowerCase() + } + + const swatchClassName = cn( + s.swatch, + { + [s.color]: color, + [s.active]: active, + [s.size]: variant === 'size', + [s.dark]: color ? isDark(color) : false, + [s.textLabel]: !color && label && label.length > 3, + }, + className + ) + + return ( + + ) } - - const swatchClassName = cn( - s.swatch, - { - [s.active]: active, - [s.size]: variant === 'size', - [s.color]: color, - [s.dark]: color ? isDark(color) : false, - [s.textLabel]: !color && label && label.length > 3, - }, - className - ) - - return ( - - ) -} +) export default Swatch diff --git a/components/ui/Collapse/Collapse.tsx b/components/ui/Collapse/Collapse.tsx index 8e6686db5..b2f9525ac 100644 --- a/components/ui/Collapse/Collapse.tsx +++ b/components/ui/Collapse/Collapse.tsx @@ -10,7 +10,7 @@ export interface CollapseProps { children: ReactNode } -const Collapse: FC = ({ title, children }) => { +const Collapse: FC = React.memo(({ title, children }) => { const [isActive, setActive] = useState(false) const [ref, { height: viewHeight }] = useMeasure() @@ -41,6 +41,6 @@ const Collapse: FC = ({ title, children }) => {
) -} +}) export default Collapse diff --git a/components/ui/Rating/Rating.tsx b/components/ui/Rating/Rating.tsx index e9ae65833..259e642ea 100644 --- a/components/ui/Rating/Rating.tsx +++ b/components/ui/Rating/Rating.tsx @@ -7,7 +7,7 @@ export interface RatingProps { value: number } -const Quantity: FC = ({ value = 5 }) => { +const Quantity: React.FC = React.memo(({ value = 5 }) => { return (
{rangeMap(5, (i) => ( @@ -22,6 +22,6 @@ const Quantity: FC = ({ value = 5 }) => { ))}
) -} +}) export default Quantity diff --git a/pages/index.tsx b/pages/index.tsx index 0393b599b..6f907f8f5 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -12,7 +12,7 @@ export async function getStaticProps({ }: GetStaticPropsContext) { const config = { locale, locales } const { products } = await commerce.getAllProducts({ - variables: { first: 12 }, + variables: { first: 6 }, config, preview, }) @@ -69,7 +69,7 @@ export default function Home({ ))} - {products.slice(0, 3).map((product, i) => ( + {products.slice(3).map((product, i) => ( ))}