Perf changes

This commit is contained in:
Bel Curcio 2021-06-08 11:31:48 -03:00
parent 1a729f9e72
commit 7ef0e2273e
9 changed files with 326 additions and 229 deletions

View File

@ -1,18 +1,15 @@
import { Swatch } from '@components/product' import { Swatch } from '@components/product'
import type { ProductOption } from '@commerce/types/product' import type { ProductOption } from '@commerce/types/product'
import { SelectedOptions } from '../helpers' import { SelectedOptions } from '../helpers'
import React from 'react'
interface ProductOptionsProps { interface ProductOptionsProps {
options: ProductOption[] options: ProductOption[]
selectedOptions: SelectedOptions selectedOptions: SelectedOptions
setSelectedOptions: React.Dispatch<React.SetStateAction<SelectedOptions>> setSelectedOptions: React.Dispatch<React.SetStateAction<SelectedOptions>>
} }
const ProductOptions: React.FC<ProductOptionsProps> = ({ const ProductOptions: React.FC<ProductOptionsProps> = React.memo(
options, ({ options, selectedOptions, setSelectedOptions }) => {
selectedOptions,
setSelectedOptions,
}) => {
return ( return (
<div> <div>
{options.map((opt) => ( {options.map((opt) => (
@ -34,7 +31,8 @@ const ProductOptions: React.FC<ProductOptionsProps> = ({
setSelectedOptions((selectedOptions) => { setSelectedOptions((selectedOptions) => {
return { return {
...selectedOptions, ...selectedOptions,
[opt.displayName.toLowerCase()]: v.label.toLowerCase(), [opt.displayName.toLowerCase()]:
v.label.toLowerCase(),
} }
}) })
}} }}
@ -47,5 +45,6 @@ const ProductOptions: React.FC<ProductOptionsProps> = ({
</div> </div>
) )
} }
)
export default ProductOptions export default ProductOptions

View File

@ -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;
}
}

View File

@ -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<ProductSidebarProps> = ({ product }) => {
const addItem = useAddItem()
const { openSidebar } = useUI()
const [loading, setLoading] = useState(false)
const [selectedOptions, setSelectedOptions] = useState<SelectedOptions>({})
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 (
<>
<ProductOptions
options={product.options}
selectedOptions={selectedOptions}
setSelectedOptions={setSelectedOptions}
/>
<Text
className="pb-4 break-words w-full max-w-xl"
html={product.descriptionHtml || product.description}
/>
<div className="flex flex-row justify-between items-center">
<Rating value={2} />
<div className="text-accent-6 pr-1 font-medium text-sm">36 reviews</div>
</div>
<div>
<Button
aria-label="Add to Cart"
type="button"
className={s.button}
onClick={addToCart}
loading={loading}
disabled={variant?.availableForSale === false}
>
{variant?.availableForSale === false
? 'Not Available'
: 'Add To Cart'}
</Button>
</div>
<div className="mt-6">
<Collapse title="Care">
This is a limited edition production run. Printing starts when the
drop ends.
</Collapse>
<Collapse title="Details">
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.
</Collapse>
</div>
</>
)
}
export default ProductSidebar

View File

@ -0,0 +1 @@
export { default } from './ProductSidebar'

View File

@ -2,65 +2,27 @@ import cn from 'classnames'
import Image from 'next/image' import Image from 'next/image'
import { NextSeo } from 'next-seo' import { NextSeo } from 'next-seo'
import s from './ProductView.module.css' import s from './ProductView.module.css'
import { FC, useEffect, useState } from 'react' import { FC } from 'react'
import type { Product } from '@commerce/types/product' import type { Product } from '@commerce/types/product'
import usePrice from '@framework/product/use-price' import usePrice from '@framework/product/use-price'
import {
getProductVariant,
selectDefaultOptionFromProduct,
SelectedOptions,
} from '../helpers'
import { useAddItem } from '@framework/cart'
import { WishlistButton } from '@components/wishlist' import { WishlistButton } from '@components/wishlist'
import { ProductSlider, ProductCard, ProductOptions } from '@components/product' import { ProductSlider, ProductCard } from '@components/product'
import { import { Container, Text } from '@components/ui'
Button, import ProductSidebar from '../ProductSidebar'
Container,
Text,
useUI,
Rating,
Collapse,
} from '@components/ui'
interface ProductViewProps { interface ProductViewProps {
product: Product product: Product
className?: string
relatedProducts: Product[] relatedProducts: Product[]
children?: React.ReactNode
} }
const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => { const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => {
const { openSidebar } = useUI()
const [loading, setLoading] = useState(false)
const [selectedOptions, setSelectedOptions] = useState<SelectedOptions>({})
const addItem = useAddItem()
const { price } = usePrice({ const { price } = usePrice({
amount: product.price.value, amount: product.price.value,
baseAmount: product.price.retailPrice, baseAmount: product.price.retailPrice,
currencyCode: product.price.currencyCode!, 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 ( return (
<Container className="max-w-none w-full" clean> <>
<NextSeo <NextSeo
title={product.name} title={product.name}
description={product.description} description={product.description}
@ -78,6 +40,7 @@ const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => {
], ],
}} }}
/> />
<Container className="max-w-none w-full" clean>
<div className={cn(s.root, 'fit')}> <div className={cn(s.root, 'fit')}>
<div className={cn(s.main, 'fit')}> <div className={cn(s.main, 'fit')}>
<div className={s.header}> <div className={s.header}>
@ -115,46 +78,7 @@ const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => {
)} )}
</div> </div>
<div className={s.sidebar}> <div className={s.sidebar}>
<ProductOptions <ProductSidebar product={product} />
options={product.options}
selectedOptions={selectedOptions}
setSelectedOptions={setSelectedOptions}
/>
<Text
className="pb-4 break-words w-full max-w-xl"
html={product.descriptionHtml || product.description}
/>
<div className="flex flex-row justify-between items-center">
<Rating value={2} />
<div className="text-accent-6 pr-1 font-medium text-sm">
36 reviews
</div>
</div>
<div>
<Button
aria-label="Add to Cart"
type="button"
className={s.button}
onClick={addToCart}
loading={loading}
disabled={variant?.availableForSale === false}
>
{variant?.availableForSale === false
? 'Not Available'
: 'Add To Cart'}
</Button>
</div>
<div className="mt-6">
<Collapse title="Care">
This is a limited edition production run. Printing starts when the
drop ends.
</Collapse>
<Collapse title="Details">
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.
</Collapse>
</div>
</div> </div>
</div> </div>
<hr className="mt-7 border-accent-2" /> <hr className="mt-7 border-accent-2" />
@ -182,6 +106,7 @@ const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => {
</div> </div>
</section> </section>
</Container> </Container>
</>
) )
} }

View File

@ -1,5 +1,5 @@
import cn from 'classnames' import cn from 'classnames'
import { FC } from 'react' import React from 'react'
import s from './Swatch.module.css' import s from './Swatch.module.css'
import { Check } from '@components/icons' import { Check } from '@components/icons'
import Button, { ButtonProps } from '@components/ui/Button' import Button, { ButtonProps } from '@components/ui/Button'
@ -13,12 +13,13 @@ interface SwatchProps {
label?: string | null label?: string | null
} }
const Swatch: FC<Omit<ButtonProps, 'variant'> & SwatchProps> = ({ const Swatch: React.FC<Omit<ButtonProps, 'variant'> & SwatchProps> = React.memo(
({
active,
className, className,
color = '', color = '',
label = null, label = null,
variant = 'size', variant = 'size',
active,
...props ...props
}) => { }) => {
variant = variant?.toLowerCase() variant = variant?.toLowerCase()
@ -30,9 +31,9 @@ const Swatch: FC<Omit<ButtonProps, 'variant'> & SwatchProps> = ({
const swatchClassName = cn( const swatchClassName = cn(
s.swatch, s.swatch,
{ {
[s.color]: color,
[s.active]: active, [s.active]: active,
[s.size]: variant === 'size', [s.size]: variant === 'size',
[s.color]: color,
[s.dark]: color ? isDark(color) : false, [s.dark]: color ? isDark(color) : false,
[s.textLabel]: !color && label && label.length > 3, [s.textLabel]: !color && label && label.length > 3,
}, },
@ -41,10 +42,10 @@ const Swatch: FC<Omit<ButtonProps, 'variant'> & SwatchProps> = ({
return ( return (
<Button <Button
className={swatchClassName}
style={color ? { backgroundColor: color } : {}}
aria-label="Variant Swatch" aria-label="Variant Swatch"
className={swatchClassName}
{...(label && color && { title: label })} {...(label && color && { title: label })}
style={color ? { backgroundColor: color } : {}}
{...props} {...props}
> >
{color && active && ( {color && active && (
@ -56,5 +57,6 @@ const Swatch: FC<Omit<ButtonProps, 'variant'> & SwatchProps> = ({
</Button> </Button>
) )
} }
)
export default Swatch export default Swatch

View File

@ -10,7 +10,7 @@ export interface CollapseProps {
children: ReactNode children: ReactNode
} }
const Collapse: FC<CollapseProps> = ({ title, children }) => { const Collapse: FC<CollapseProps> = React.memo(({ title, children }) => {
const [isActive, setActive] = useState(false) const [isActive, setActive] = useState(false)
const [ref, { height: viewHeight }] = useMeasure() const [ref, { height: viewHeight }] = useMeasure()
@ -41,6 +41,6 @@ const Collapse: FC<CollapseProps> = ({ title, children }) => {
</a.div> </a.div>
</div> </div>
) )
} })
export default Collapse export default Collapse

View File

@ -7,7 +7,7 @@ export interface RatingProps {
value: number value: number
} }
const Quantity: FC<RatingProps> = ({ value = 5 }) => { const Quantity: React.FC<RatingProps> = React.memo(({ value = 5 }) => {
return ( return (
<div className="flex flex-row py-6 text-accent-9"> <div className="flex flex-row py-6 text-accent-9">
{rangeMap(5, (i) => ( {rangeMap(5, (i) => (
@ -22,6 +22,6 @@ const Quantity: FC<RatingProps> = ({ value = 5 }) => {
))} ))}
</div> </div>
) )
} })
export default Quantity export default Quantity

View File

@ -12,7 +12,7 @@ export async function getStaticProps({
}: GetStaticPropsContext) { }: GetStaticPropsContext) {
const config = { locale, locales } const config = { locale, locales }
const { products } = await commerce.getAllProducts({ const { products } = await commerce.getAllProducts({
variables: { first: 12 }, variables: { first: 6 },
config, config,
preview, preview,
}) })
@ -69,7 +69,7 @@ export default function Home({
))} ))}
</Grid> </Grid>
<Marquee> <Marquee>
{products.slice(0, 3).map((product, i) => ( {products.slice(3).map((product, i) => (
<ProductCard key={product.id} product={product} variant="slim" /> <ProductCard key={product.id} product={product} variant="slim" />
))} ))}
</Marquee> </Marquee>