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,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<React.SetStateAction<SelectedOptions>>
}
const ProductOptions: React.FC<ProductOptionsProps> = ({
options,
selectedOptions,
setSelectedOptions,
}) => {
return (
<div>
{options.map((opt) => (
<div className="pb-4" key={opt.displayName}>
<h2 className="uppercase font-medium text-sm tracking-wide">
{opt.displayName}
</h2>
<div className="flex flex-row py-4">
{opt.values.map((v, i: number) => {
const active = selectedOptions[opt.displayName.toLowerCase()]
return (
<Swatch
key={`${opt.id}-${i}`}
active={v.label.toLowerCase() === active}
variant={opt.displayName}
color={v.hexColors ? v.hexColors[0] : ''}
label={v.label}
onClick={() => {
setSelectedOptions((selectedOptions) => {
return {
...selectedOptions,
[opt.displayName.toLowerCase()]: v.label.toLowerCase(),
}
})
}}
/>
)
})}
const ProductOptions: React.FC<ProductOptionsProps> = React.memo(
({ options, selectedOptions, setSelectedOptions }) => {
return (
<div>
{options.map((opt) => (
<div className="pb-4" key={opt.displayName}>
<h2 className="uppercase font-medium text-sm tracking-wide">
{opt.displayName}
</h2>
<div className="flex flex-row py-4">
{opt.values.map((v, i: number) => {
const active = selectedOptions[opt.displayName.toLowerCase()]
return (
<Swatch
key={`${opt.id}-${i}`}
active={v.label.toLowerCase() === active}
variant={opt.displayName}
color={v.hexColors ? v.hexColors[0] : ''}
label={v.label}
onClick={() => {
setSelectedOptions((selectedOptions) => {
return {
...selectedOptions,
[opt.displayName.toLowerCase()]:
v.label.toLowerCase(),
}
})
}}
/>
)
})}
</div>
</div>
</div>
))}
</div>
)
}
))}
</div>
)
}
)
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 { 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<ProductViewProps> = ({ product, relatedProducts }) => {
const { openSidebar } = useUI()
const [loading, setLoading] = useState(false)
const [selectedOptions, setSelectedOptions] = useState<SelectedOptions>({})
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 (
<Container className="max-w-none w-full" clean>
<>
<NextSeo
title={product.name}
description={product.description}
@ -78,110 +40,73 @@ const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => {
],
}}
/>
<div className={cn(s.root, 'fit')}>
<div className={cn(s.main, 'fit')}>
<div className={s.header}>
<h3 className={s.name}>
<span>{product.name}</span>
</h3>
<div className={s.price}>
{`${price} ${product.price?.currencyCode}`}
<Container className="max-w-none w-full" clean>
<div className={cn(s.root, 'fit')}>
<div className={cn(s.main, 'fit')}>
<div className={s.header}>
<h3 className={s.name}>
<span>{product.name}</span>
</h3>
<div className={s.price}>
{`${price} ${product.price?.currencyCode}`}
</div>
</div>
</div>
<div className={s.sliderContainer}>
<ProductSlider key={product.id}>
{product.images.map((image, i) => (
<div key={image.url} className={s.imageContainer}>
<Image
className={s.img}
src={image.url!}
alt={image.alt || 'Product Image'}
width={600}
height={600}
priority={i === 0}
quality="85"
/>
</div>
))}
</ProductSlider>
</div>
{process.env.COMMERCE_WISHLIST_ENABLED && (
<WishlistButton
className={s.wishlistButton}
productId={product.id}
variant={product.variants[0]}
/>
)}
</div>
<div className={s.sidebar}>
<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 className={s.sliderContainer}>
<ProductSlider key={product.id}>
{product.images.map((image, i) => (
<div key={image.url} className={s.imageContainer}>
<Image
className={s.img}
src={image.url!}
alt={image.alt || 'Product Image'}
width={600}
height={600}
priority={i === 0}
quality="85"
/>
</div>
))}
</ProductSlider>
</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>
<hr className="mt-7 border-accent-2" />
<section className="py-12 px-6 mb-10">
<Text variant="sectionHeading">Related Products</Text>
<div className={s.relatedProductsGrid}>
{relatedProducts.map((p) => (
<div
key={p.path}
className="animated fadeIn bg-accent-0 border border-accent-2"
>
<ProductCard
noNameTag
product={p}
key={p.path}
variant="simple"
className="animated fadeIn"
imgProps={{
width: 300,
height: 300,
}}
{process.env.COMMERCE_WISHLIST_ENABLED && (
<WishlistButton
className={s.wishlistButton}
productId={product.id}
variant={product.variants[0]}
/>
</div>
))}
)}
</div>
<div className={s.sidebar}>
<ProductSidebar product={product} />
</div>
</div>
</section>
</Container>
<hr className="mt-7 border-accent-2" />
<section className="py-12 px-6 mb-10">
<Text variant="sectionHeading">Related Products</Text>
<div className={s.relatedProductsGrid}>
{relatedProducts.map((p) => (
<div
key={p.path}
className="animated fadeIn bg-accent-0 border border-accent-2"
>
<ProductCard
noNameTag
product={p}
key={p.path}
variant="simple"
className="animated fadeIn"
imgProps={{
width: 300,
height: 300,
}}
/>
</div>
))}
</div>
</section>
</Container>
</>
)
}

View File

@ -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<Omit<ButtonProps, 'variant'> & SwatchProps> = ({
className,
color = '',
label = null,
variant = 'size',
active,
...props
}) => {
variant = variant?.toLowerCase()
const Swatch: React.FC<Omit<ButtonProps, 'variant'> & 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 (
<Button
aria-label="Variant Swatch"
className={swatchClassName}
{...(label && color && { title: label })}
style={color ? { backgroundColor: color } : {}}
{...props}
>
{color && active && (
<span>
<Check />
</span>
)}
{!color ? label : null}
</Button>
)
}
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 (
<Button
className={swatchClassName}
style={color ? { backgroundColor: color } : {}}
aria-label="Variant Swatch"
{...(label && color && { title: label })}
{...props}
>
{color && active && (
<span>
<Check />
</span>
)}
{!color ? label : null}
</Button>
)
}
)
export default Swatch

View File

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

View File

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

View File

@ -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({
))}
</Grid>
<Marquee>
{products.slice(0, 3).map((product, i) => (
{products.slice(3).map((product, i) => (
<ProductCard key={product.id} product={product} variant="slim" />
))}
</Marquee>