diff --git a/packages/bigcommerce/src/lib/normalize.ts b/packages/bigcommerce/src/lib/normalize.ts index e83394c91..ee3715d89 100644 --- a/packages/bigcommerce/src/lib/normalize.ts +++ b/packages/bigcommerce/src/lib/normalize.ts @@ -8,6 +8,17 @@ import type { definitions } from '../api/definitions/store-content' import getSlug from './get-slug' +const normalizePrice = (prices: ProductNode['prices']) => ({ + value: prices?.price.value || 0, + currencyCode: prices?.price.currencyCode || 'USD', + ...(prices?.salePrice?.value && { + salePrice: prices.salePrice.value, + }), + ...(prices?.retailPrice?.value && { + retailPrice: prices.retailPrice.value, + }), +}) + function normalizeProductOption(productOption: any) { const { node: { entityId, values: { edges = [] } = {}, ...rest }, @@ -20,43 +31,64 @@ function normalizeProductOption(productOption: any) { } } +const normalizeImages = (productNode: ProductNode) => { + const output = + productNode.images.edges?.map( + ({ node: { urlOriginal, altText, ...rest } }: any) => ({ + url: urlOriginal, + alt: altText, + ...rest, + }) + ) || [] + + /** + * Add the variants images to the product images, because the variants images are not included in the product images + */ + productNode.variants.edges?.forEach(({ node: variant }: any) => { + if (variant.defaultImage?.urlOriginal) { + output.push({ + url: variant.defaultImage.urlOriginal, + alt: variant.defaultImage.altText, + }) + } + }) + + return output +} + +const normalizeVariants = (variants: any) => + variants.edges?.map( + ({ + node: { entityId, productOptions, prices, defaultImage, ...rest }, + }: any) => ({ + id: String(entityId), + ...(defaultImage && { + image: { + url: defaultImage.urlOriginal, + alt: defaultImage.altText, + }, + }), + ...(prices && { price: normalizePrice(prices) }), + options: productOptions?.edges + ? productOptions.edges.map(normalizeProductOption) + : [], + ...rest, + }) + ) || [] + export function normalizeProduct(productNode: ProductNode): Product { - const { - entityId: id, - productOptions, - prices, - path, - images, - variants, - } = productNode + const { entityId: id, productOptions, prices, path, variants } = productNode return { id: String(id), name: productNode.name, description: productNode.description, - images: - images.edges?.map(({ node: { urlOriginal, altText, ...rest } }: any) => ({ - url: urlOriginal, - alt: altText, - ...rest, - })) || [], + images: normalizeImages(productNode), path: `/${getSlug(path)}`, - variants: - variants.edges?.map( - ({ node: { entityId, productOptions, ...rest } }: any) => ({ - id: String(entityId), - options: productOptions?.edges - ? productOptions.edges.map(normalizeProductOption) - : [], - ...rest, - }) - ) || [], + variants: normalizeVariants(variants), options: productOptions?.edges?.map(normalizeProductOption) || [], slug: path?.replace(/^\/+|\/+$/g, ''), - price: { - value: prices?.price.value, - currencyCode: prices?.price.currencyCode, - }, + price: normalizePrice(prices), } } diff --git a/packages/commerce/src/api/utils/index.ts b/packages/commerce/src/api/utils/index.ts index 3fd50d00e..082029d0c 100644 --- a/packages/commerce/src/api/utils/index.ts +++ b/packages/commerce/src/api/utils/index.ts @@ -44,7 +44,9 @@ export const transformRequest = (req: NextApiRequest, path: string) => { body = JSON.stringify(req.body) } - return new NextRequest(`https://${req.headers.host}/api/commerce/${path}`, { + const url = new URL(req.url || '/', `https://${req.headers.host}`) + + return new NextRequest(url, { headers, method: req.method, body, diff --git a/packages/shopify/src/utils/normalize.ts b/packages/shopify/src/utils/normalize.ts index 066daff33..177a9a18f 100644 --- a/packages/shopify/src/utils/normalize.ts +++ b/packages/shopify/src/utils/normalize.ts @@ -19,7 +19,9 @@ import type { import { colorMap } from './colors' -const money = ({ amount, currencyCode }: MoneyV2) => { +type MoneyProps = MoneyV2 & { retailPrice?: string | number } + +const money = ({ amount, currencyCode }: MoneyProps) => { return { value: +amount, currencyCode, @@ -67,6 +69,7 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => { selectedOptions, sku, title, + image, priceV2, compareAtPriceV2, requiresShipping, @@ -77,7 +80,8 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => { id, name: title, sku, - price: +priceV2.amount, + image, + price: money({ ...priceV2, retailPrice: compareAtPriceV2?.amount }), listPrice: +compareAtPriceV2?.amount, requiresShipping, availableForSale, diff --git a/packages/shopify/src/utils/queries/get-product-query.ts b/packages/shopify/src/utils/queries/get-product-query.ts index 1c4458432..7dfeb79e1 100644 --- a/packages/shopify/src/utils/queries/get-product-query.ts +++ b/packages/shopify/src/utils/queries/get-product-query.ts @@ -34,6 +34,13 @@ const getProductQuery = /* GraphQL */ ` id title sku + image { + id + altText + url + width + height + } availableForSale requiresShipping selectedOptions { diff --git a/site/components/product/ProductCard/ProductCard.module.css b/site/components/product/ProductCard/ProductCard.module.css index d5d441fea..a34b338be 100644 --- a/site/components/product/ProductCard/ProductCard.module.css +++ b/site/components/product/ProductCard/ProductCard.module.css @@ -7,7 +7,7 @@ .root:hover { & .productImage { - transform: scale(1.2625); + transform: scale(1.1); } & .header .name span, @@ -47,23 +47,25 @@ } .header .name { - @apply pt-0 max-w-full w-full leading-extra-loose + @apply pt-1 max-w-full w-full leading-extra-loose transition-colors ease-in-out duration-500; - font-size: 2rem; + font-size: 1rem; + line-height: 1; letter-spacing: 0.4px; } .header .name span { - @apply py-4 px-6 bg-primary text-primary font-bold + @apply py-2 px-3 bg-primary text-primary font-bold transition-colors ease-in-out duration-500; font-size: inherit; letter-spacing: inherit; box-decoration-break: clone; -webkit-box-decoration-break: clone; + line-height: 1 !important; } .header .price { - @apply pt-2 px-6 pb-4 text-sm bg-primary text-accent-9 + @apply pt-1.5 pb-2 px-3 text-sm bg-primary text-accent-9 font-semibold inline-block tracking-wide transition-colors ease-in-out duration-500; } @@ -77,8 +79,7 @@ } .imageContainer .productImage { - @apply transform transition-transform duration-500 - object-cover scale-120; + @apply transform transition-transform duration-500 object-cover; } .root .wishlistButton { @@ -87,7 +88,7 @@ /* Variant Simple */ .simple .header .name { - @apply pt-2 text-lg leading-10 -mt-1; + @apply pt-1 text-base leading-6; } .simple .header .price { @@ -101,11 +102,11 @@ } .slim .header { - @apply absolute inset-0 flex items-center justify-end mr-8 z-20; + @apply absolute inset-0 flex text-center items-center justify-end mr-8 z-20; } .slim span { - @apply bg-accent-9 text-accent-0 inline-block p-3 + @apply bg-accent-9 text-accent-0 inline-block px-3 py-2.5 font-bold text-xl break-words; } diff --git a/site/components/product/ProductDetails/ProductDetails.module.css b/site/components/product/ProductDetails/ProductDetails.module.css new file mode 100644 index 000000000..988c78440 --- /dev/null +++ b/site/components/product/ProductDetails/ProductDetails.module.css @@ -0,0 +1,56 @@ +.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; +} + +.sidebar { + @apply flex flex-col col-span-1 mx-auto max-w-8xl px-6 py-6 w-full h-full; +} + +.sliderContainer { + @apply flex flex-1 overflow-x-hidden bg-violet items-center justify-center; +} + +.imageContainer { + @apply text-center h-full relative flex-shrink-0; +} + +.imageContainer > img { + @apply mx-auto; +} + +.sliderContainer .img { + @apply w-full h-full max-h-full object-cover transition duration-500 ease-in-out; +} + +.button { + width: 100%; +} + +.wishlistButton { + @apply absolute z-30 top-0 right-0; +} + +@screen lg { + .root { + @apply grid-cols-12; + } + + .main { + @apply mx-0 col-span-8; + min-height: 782px; + } + + .sidebar { + @apply col-span-4 py-6; + } + + .imageContainer { + max-height: 600px; + } +} diff --git a/site/components/product/ProductDetails/ProductDetails.tsx b/site/components/product/ProductDetails/ProductDetails.tsx new file mode 100644 index 000000000..336e4f1b5 --- /dev/null +++ b/site/components/product/ProductDetails/ProductDetails.tsx @@ -0,0 +1,47 @@ +import cn from 'clsx' +import { useProduct } from '../context' +import { WishlistButton } from '@components/wishlist' +import Image from 'next/image' +import ProductTag from '../ProductTag' +import ProductSlider from '../ProductSlider' +import ProductSidebar from '../ProductSidebar' + +import s from './ProductDetails.module.css' + +const ProductDetails = () => { + const { product, variant, price } = useProduct() + + return ( +
+
+ +
+ + {product.images.map((image, i) => ( +
+ {image.alt +
+ ))} +
+
+ {process.env.COMMERCE_WISHLIST_ENABLED && variant && ( + + )} +
+ +
+ ) +} + +export default ProductDetails diff --git a/site/components/product/ProductDetails/index.ts b/site/components/product/ProductDetails/index.ts new file mode 100644 index 000000000..cf6d80139 --- /dev/null +++ b/site/components/product/ProductDetails/index.ts @@ -0,0 +1 @@ +export { default } from './ProductDetails' diff --git a/site/components/product/ProductOptions/ProductOptions.tsx b/site/components/product/ProductOptions/ProductOptions.tsx index 15c229edb..35998a279 100644 --- a/site/components/product/ProductOptions/ProductOptions.tsx +++ b/site/components/product/ProductOptions/ProductOptions.tsx @@ -1,27 +1,18 @@ import { memo } from 'react' import { Swatch } from '@components/product' -import type { ProductOption } from '@commerce/types/product' -import { SelectedOptions } from '../helpers' +import { useProduct } from '../context' -interface ProductOptionsProps { - options: ProductOption[] - selectedOptions: SelectedOptions - setSelectedOptions: React.Dispatch> -} +const ProductOptions: React.FC = () => { + const { product, selectedOptions, setSelectedOptions } = useProduct() -const ProductOptions: React.FC = ({ - options, - selectedOptions, - setSelectedOptions, -}) => { return (
- {options.map((opt) => ( + {product.options.map((opt) => (

{opt.displayName}

-
+
{opt.values.map((v, i: number) => { const active = selectedOptions[opt.displayName.toLowerCase()] return ( diff --git a/site/components/product/ProductSidebar/ProductSidebar.tsx b/site/components/product/ProductSidebar/ProductSidebar.tsx index 67c3dab9f..0624e7e99 100644 --- a/site/components/product/ProductSidebar/ProductSidebar.tsx +++ b/site/components/product/ProductSidebar/ProductSidebar.tsx @@ -1,63 +1,49 @@ import s from './ProductSidebar.module.css' import { useAddItem } from '@framework/cart' -import { FC, useEffect, useState } from 'react' +import { FC, 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' -import ErrorMessage from '@components/ui/ErrorMessage' +import { useProduct } from '../context' interface ProductSidebarProps { - product: Product className?: string } -const ProductSidebar: FC = ({ product, className }) => { +const ProductSidebar: FC = ({ className }) => { const addItem = useAddItem() + + const { product, variant, price } = useProduct() const { openSidebar, setSidebarView } = useUI() const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - const [selectedOptions, setSelectedOptions] = useState({}) + const [error, setError] = useState(null) - useEffect(() => { - selectDefaultOptionFromProduct(product, setSelectedOptions) - }, [product]) - - const variant = getProductVariant(product, selectedOptions) const addToCart = async () => { setLoading(true) setError(null) try { - await addItem({ - productId: String(product.id), - variantId: String(variant ? variant.id : product.variants[0]?.id), - }) - setSidebarView('CART_VIEW') - openSidebar() + if (variant) { + await addItem({ + productId: String(product.id), + variantId: String(variant.id), + }) + setSidebarView('CART_VIEW') + openSidebar() + } else { + throw new Error('The variant selected is not available') + } setLoading(false) } catch (err) { + console.error(err) setLoading(false) if (err instanceof Error) { - console.error(err) - setError({ - ...err, - message: 'Could not add item to cart. Please try again.', - }) + setError(err.message) } } } return (
- + = ({ product, className }) => {
36 reviews
- {error && } {process.env.COMMERCE_CART_ENABLED && ( )} + + {error && ( +
{error}
+ )}
diff --git a/site/components/product/ProductSlider/ProductSlider.module.css b/site/components/product/ProductSlider/ProductSlider.module.css index b95bffdd0..f80f574a2 100644 --- a/site/components/product/ProductSlider/ProductSlider.module.css +++ b/site/components/product/ProductSlider/ProductSlider.module.css @@ -1,29 +1,24 @@ .root { - @apply relative w-full h-full select-none; + @apply flex flex-col relative select-none w-full; overflow: hidden; } .slider { - @apply relative h-full transition-opacity duration-150; - opacity: 0; -} - -.slider.show { - opacity: 1; + @apply flex-1; } .thumb { - @apply overflow-hidden inline-block cursor-pointer h-full; + @apply overflow-hidden inline-block cursor-pointer h-full transition duration-300 md:hover:scale-105; width: 125px; width: calc(100% / 3); } .thumb.selected { - @apply bg-white; + @apply bg-white/30; } .thumb img { - height: 85% !important; + @apply w-full h-full max-h-full object-cover; } .album { @@ -44,10 +39,6 @@ } @screen md { - .thumb:hover { - transform: scale(1.02); - } - .album { height: 182px; } diff --git a/site/components/product/ProductSlider/ProductSlider.tsx b/site/components/product/ProductSlider/ProductSlider.tsx index bd0e2db87..46f02ecbd 100644 --- a/site/components/product/ProductSlider/ProductSlider.tsx +++ b/site/components/product/ProductSlider/ProductSlider.tsx @@ -10,6 +10,8 @@ import cn from 'clsx' import { a } from '@react-spring/web' import s from './ProductSlider.module.css' import ProductSliderControl from '../ProductSliderControl' +import { useProduct } from '../context' +import { Image as ProductImage } from '@commerce/types/common' interface ProductSliderProps { children?: React.ReactNode[] @@ -20,19 +22,18 @@ const ProductSlider: React.FC = ({ children, className = '', }) => { + const { product, variant } = useProduct() const [currentSlide, setCurrentSlide] = useState(0) - const [isMounted, setIsMounted] = useState(false) + const sliderContainerRef = useRef(null) const thumbsContainerRef = useRef(null) const [ref, slider] = useKeenSlider({ loop: true, slides: { perView: 1 }, - created: () => setIsMounted(true), slideChanged(s) { const slideNumber = s.track.details.rel setCurrentSlide(slideNumber) - if (thumbsContainerRef.current) { const $el = document.getElementById(`thumb-${slideNumber}`) if (slideNumber >= 3) { @@ -44,6 +45,18 @@ const ProductSlider: React.FC = ({ }, }) + useEffect(() => { + const index = product.images.findIndex((image: ProductImage) => { + return image.url === variant?.image?.url + }) + + if (index !== -1) { + slider.current?.moveToIdx(index, false, { + duration: 0, + }) + } + }, [variant, product, slider]) + // Stop the history navigation gesture on touch devices useEffect(() => { const preventNavigation = (event: TouchEvent) => { @@ -74,15 +87,17 @@ const ProductSlider: React.FC = ({ } }, []) - const onPrev = React.useCallback(() => slider.current?.prev(), [slider]) - const onNext = React.useCallback(() => slider.current?.next(), [slider]) + const onPrev = React.useCallback(() => { + slider.current?.prev() + }, [slider]) + + const onNext = React.useCallback(() => { + slider.current?.next() + }, [slider]) return (
-
+
{slider && } {Children.map(children, (child) => { // Add the keen-slider__slide className to children @@ -109,6 +124,8 @@ const ProductSlider: React.FC = ({ ...child, props: { ...child.props, + width: 132, + height: 82, className: cn(child.props.className, s.thumb, { [s.selected]: currentSlide === idx, }), diff --git a/site/components/product/ProductTag/ProductTag.module.css b/site/components/product/ProductTag/ProductTag.module.css index c36b43aa6..8c735506d 100644 --- a/site/components/product/ProductTag/ProductTag.module.css +++ b/site/components/product/ProductTag/ProductTag.module.css @@ -4,10 +4,10 @@ } .root .name { - @apply pt-0 max-w-full w-full leading-extra-loose; + @apply pt-0 max-w-full w-full; font-size: 2rem; letter-spacing: 0.4px; - line-height: 2.1em; + line-height: 1.5em; } .root .name span { diff --git a/site/components/product/ProductView/ProductView.module.css b/site/components/product/ProductView/ProductView.module.css index e68c76f21..c040a31a4 100644 --- a/site/components/product/ProductView/ProductView.module.css +++ b/site/components/product/ProductView/ProductView.module.css @@ -1,59 +1,3 @@ -.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; -} - -.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 h-full relative; -} - -.imageContainer > span { - height: 100% !important; -} - -.sliderContainer .img { - @apply w-full h-full 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; - } + @apply grid grid-cols-2 py-2 gap-2 md:grid-cols-4 md:gap-7 m-0; } diff --git a/site/components/product/ProductView/ProductView.tsx b/site/components/product/ProductView/ProductView.tsx index 31cbcd577..b1167a01d 100644 --- a/site/components/product/ProductView/ProductView.tsx +++ b/site/components/product/ProductView/ProductView.tsx @@ -1,88 +1,44 @@ -import cn from 'clsx' -import Image from 'next/image' import s from './ProductView.module.css' -import { FC } from 'react' + +import type { FC } from 'react' import type { Product } from '@commerce/types/product' -import usePrice from '@framework/product/use-price' -import { WishlistButton } from '@components/wishlist' -import { ProductSlider, ProductCard } from '@components/product' -import { Container, Text } from '@components/ui' + import { SEO } from '@components/common' -import ProductSidebar from '../ProductSidebar' -import ProductTag from '../ProductTag' +import { ProductProvider } from '../context' +import { Container, Text } from '@components/ui' +import { ProductCard } from '@components/product' + +import ProductDetails from '../ProductDetails/ProductDetails' + interface ProductViewProps { product: Product relatedProducts: Product[] } const ProductView: FC = ({ product, relatedProducts }) => { - const { price } = usePrice({ - amount: product.price.value, - baseAmount: product.price.retailPrice, - currencyCode: product.price.currencyCode!, - }) - return ( <> -
-
- -
- - {product.images.map((image, i) => ( -
- {image.alt -
- ))} -
-
- {process.env.COMMERCE_WISHLIST_ENABLED && ( - - )} -
+ + + - -

+
Related Products
{relatedProducts.map((p) => (
))} @@ -99,7 +55,7 @@ const ProductView: FC = ({ product, relatedProducts }) => { images: [ { url: product.images[0]?.url!, - width: '800', + width: '600', height: '600', alt: product.name, }, diff --git a/site/components/product/Swatch/Swatch.module.css b/site/components/product/Swatch/Swatch.module.css index 79a69e548..619ac8331 100644 --- a/site/components/product/Swatch/Swatch.module.css +++ b/site/components/product/Swatch/Swatch.module.css @@ -1,9 +1,9 @@ .swatch { box-sizing: border-box; composes: root from '@components/ui/Button/Button.module.css'; - @apply h-10 w-10 bg-primary text-primary rounded-full mr-3 inline-flex + @apply h-10 w-10 bg-primary text-primary rounded-full inline-flex items-center justify-center cursor-pointer transition duration-150 ease-in-out - p-0 shadow-none border-accent-3 border box-border select-none; + p-0 shadow-none border-accent-3 border box-border select-none !mr-0; margin-right: calc(0.75rem - 1px); overflow: hidden; width: 48px; diff --git a/site/components/product/Swatch/Swatch.tsx b/site/components/product/Swatch/Swatch.tsx index 865f43398..5ac8c2ae9 100644 --- a/site/components/product/Swatch/Swatch.tsx +++ b/site/components/product/Swatch/Swatch.tsx @@ -18,7 +18,7 @@ const Swatch: React.FC & SwatchProps> = ({ className, color = '', label = null, - variant = 'size', + variant, ...props }) => { variant = variant?.toLowerCase() diff --git a/site/components/product/context.tsx b/site/components/product/context.tsx new file mode 100644 index 000000000..0ad91620f --- /dev/null +++ b/site/components/product/context.tsx @@ -0,0 +1,71 @@ +import { useMemo, useState, useEffect, useContext, createContext } from 'react' + +import type { FC, ReactNode, Dispatch, SetStateAction } from 'react' +import type { SelectedOptions } from './helpers' +import type { Product, ProductVariant } from '@commerce/types/product' + +import usePrice from '@framework/product/use-price' +import { getProductVariant, selectDefaultOptionFromProduct } from './helpers' + +export interface ProductContextValue { + product: Product + price: string + variant?: ProductVariant + selectedOptions: SelectedOptions + setSelectedOptions: Dispatch> +} + +export const ProductContext = createContext(null) + +ProductContext.displayName = 'ProductContext' + +type ProductProviderProps = { + product: Product + children?: ReactNode +} + +export const ProductProvider: FC = ({ + product, + children, +}) => { + const [selectedOptions, setSelectedOptions] = useState({}) + + useEffect( + () => selectDefaultOptionFromProduct(product, setSelectedOptions), + [product] + ) + + const variant = useMemo( + () => getProductVariant(product, selectedOptions), + [product, selectedOptions] + ) + + const { price } = usePrice({ + amount: variant?.price?.value || product.price.value, + baseAmount: variant?.price?.retailPrice || product.price.retailPrice, + currencyCode: variant?.price?.currencyCode || product.price.currencyCode!, + }) + + const value = useMemo( + () => ({ + price, + product, + variant, + selectedOptions, + setSelectedOptions, + }), + [price, product, selectedOptions, variant] + ) + + return ( + {children} + ) +} + +export const useProduct = () => { + const context = useContext(ProductContext) as ProductContextValue + if (context === undefined) { + throw new Error(`useProduct must be used within a ProductProvider`) + } + return context +} diff --git a/site/components/product/helpers.ts b/site/components/product/helpers.ts index 77e385bb8..8447b4f88 100644 --- a/site/components/product/helpers.ts +++ b/site/components/product/helpers.ts @@ -1,4 +1,4 @@ -import type { Product } from '@commerce/types/product' +import type { Product } from '@vercel/commerce/types/product' export type SelectedOptions = Record import { Dispatch, SetStateAction } from 'react' @@ -22,11 +22,19 @@ export function selectDefaultOptionFromProduct( product: Product, updater: Dispatch> ) { - // Selects the default option - product.variants[0]?.options?.forEach((v) => { - updater((choices) => ({ - ...choices, - [v.displayName.toLowerCase()]: v.values[0].label.toLowerCase(), - })) - }) + // Get the first available option or the first option + const variant = + product.variants.find((variant) => variant.availableForSale) || + product.variants[0] + + // Reset the selectedOptions and set the default option from the available variant + const newValue: SelectedOptions = {} + + if (variant) { + for (const c of variant.options) { + newValue[c.displayName.toLowerCase()] = c.values[0].label.toLowerCase() + } + } + + updater(newValue) } diff --git a/site/components/search.tsx b/site/components/search.tsx index 92146628c..5b4386d55 100644 --- a/site/components/search.tsx +++ b/site/components/search.tsx @@ -273,7 +273,7 @@ export default function Search({ categories, brands }: SearchPropsType) { {/* Products */}
{(q || activeCategory || activeBrand) && ( -
+
{data ? ( <> )} {data ? ( -
+
{data.products.map((product: Product) => ( ) : ( -
+
{rangeMap(12, (i) => (
diff --git a/site/pages/product/[slug].tsx b/site/pages/product/[slug].tsx index 9d8d153e4..bef23ef40 100644 --- a/site/pages/product/[slug].tsx +++ b/site/pages/product/[slug].tsx @@ -14,37 +14,44 @@ export async function getStaticProps({ locales, preview, }: GetStaticPropsContext<{ slug: string }>) { - const config = { locale, locales } - const pagesPromise = commerce.getAllPages({ config, preview }) - const siteInfoPromise = commerce.getSiteInfo({ config, preview }) - const productPromise = commerce.getProduct({ - variables: { slug: params!.slug }, - config, - preview, - }) - const allProductsPromise = commerce.getAllProducts({ - variables: { first: 4 }, - config, - preview, - }) + try { + const config = { locale, locales } + const pagesPromise = commerce.getAllPages({ config, preview }) + const siteInfoPromise = commerce.getSiteInfo({ config, preview }) + const productPromise = commerce.getProduct({ + variables: { slug: params!.slug }, + config, + preview, + }) + const allProductsPromise = commerce.getAllProducts({ + variables: { first: 4 }, + config, + preview, + }) - const { pages } = await pagesPromise - const { categories } = await siteInfoPromise - const { product } = await productPromise - const { products: relatedProducts } = await allProductsPromise + const { pages } = await pagesPromise + const { categories } = await siteInfoPromise + const { product } = await productPromise + const { products: relatedProducts } = await allProductsPromise - if (!product) { - throw new Error(`Product with slug '${params!.slug}' not found`) - } + if (!product) { + throw new Error(`Product with slug '${params!.slug}' not found`) + } - return { - props: { - pages, - product, - relatedProducts, - categories, - }, - revalidate: 200, + return { + props: { + pages, + product, + relatedProducts, + categories, + }, + revalidate: 200, + } + } catch (error) { + console.log(error) + return { + notFound: true, + } } } diff --git a/site/tsconfig.json b/site/tsconfig.json index 7c91afd6f..2de809a44 100644 --- a/site/tsconfig.json +++ b/site/tsconfig.json @@ -23,8 +23,8 @@ "@components/*": ["components/*"], "@commerce": ["../packages/commerce/src"], "@commerce/*": ["../packages/commerce/src/*"], - "@framework": ["../packages/local/src"], - "@framework/*": ["../packages/local/src/*"] + "@framework": ["../packages/shopify/src"], + "@framework/*": ["../packages/shopify/src/*"] } }, "include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],