From 2f37fa46a949af6f184bd2fdd90e8a2a2d6e09cc Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 26 Oct 2020 22:31:49 -0500 Subject: [PATCH] Select variant in product page --- .../product/ProductView/ProductView.tsx | 17 ++- components/product/helpers.ts | 49 +++++++- lib/bigcommerce/api/fragments/product.ts | 33 +++-- lib/bigcommerce/api/operations/get-product.ts | 32 +++++ lib/bigcommerce/schema.d.ts | 118 ++++++++++++++++-- 5 files changed, 214 insertions(+), 35 deletions(-) diff --git a/components/product/ProductView/ProductView.tsx b/components/product/ProductView/ProductView.tsx index 8c15785d8..a57d88a2d 100644 --- a/components/product/ProductView/ProductView.tsx +++ b/components/product/ProductView/ProductView.tsx @@ -4,7 +4,6 @@ import Image from 'next/image' import { NextSeo } from 'next-seo' import s from './ProductView.module.css' -import { Heart } from '@components/icons' import { useUI } from '@components/ui/context' import { Swatch, ProductSlider } from '@components/product' import { Button, Container } from '@components/ui' @@ -12,7 +11,11 @@ import { HTMLContent } from '@components/core' import useAddItem from '@lib/bigcommerce/cart/use-add-item' import type { ProductNode } from '@lib/bigcommerce/api/operations/get-product' -import { getProductOptions } from '../helpers' +import { + getCurrentVariant, + getProductOptions, + ProductOptions, +} from '../helpers' import WishlistButton from '@components/wishlist/WishlistButton' interface Props { @@ -21,16 +24,17 @@ interface Props { product: ProductNode } -const ProductView: FC = ({ product, className }) => { +const ProductView: FC = ({ product }) => { const addItem = useAddItem() const { openSidebar } = useUI() const options = getProductOptions(product) const [loading, setLoading] = useState(false) - - const [choices, setChoices] = useState>({ + const [choices, setChoices] = useState({ size: null, color: null, }) + const variant = + getCurrentVariant(product, choices) || product.variants.edges?.[0] const addToCart = async () => { setLoading(true) @@ -102,7 +106,7 @@ const ProductView: FC = ({ product, className }) => {

{opt.displayName}

{opt.values.map((v: any, i: number) => { - const active = choices[opt.displayName] + const active = (choices as any)[opt.displayName] return ( = ({ product, className }) => { className={s.button} onClick={addToCart} loading={loading} + disabled={!variant} > Add to Cart diff --git a/components/product/helpers.ts b/components/product/helpers.ts index 892138160..910c7386f 100644 --- a/components/product/helpers.ts +++ b/components/product/helpers.ts @@ -1,10 +1,51 @@ import type { ProductNode } from '@lib/bigcommerce/api/operations/get-product' +export type SelectedOptions = { + size: string | null + color: string | null +} + +export type ProductOption = { + displayName: string + values: any +} + +// Returns the available options of a product export function getProductOptions(product: ProductNode) { - const options = product.productOptions.edges?.map(({ node }: any) => ({ - displayName: node.displayName.toLowerCase(), - values: node.values.edges?.map(({ node }: any) => node), - })) + const options = product.productOptions.edges?.reduce( + (arr, edge) => { + if (edge?.node.__typename === 'MultipleChoiceOption') { + arr.push({ + displayName: edge.node.displayName.toLowerCase(), + values: edge.node.values.edges?.map((edge) => edge?.node), + }) + } + return arr + }, + [] + ) return options } + +// Finds a variant in the product that matches the selected options +export function getCurrentVariant(product: ProductNode, opts: SelectedOptions) { + const variant = product.variants.edges?.find((edge) => { + const { node } = edge ?? {} + + return Object.entries(opts).every(([key, value]) => + node?.productOptions.edges?.find((edge) => { + if ( + edge?.node.__typename === 'MultipleChoiceOption' && + edge.node.displayName.toLowerCase() === key + ) { + return edge.node.values.edges?.find( + (valueEdge) => valueEdge?.node.label === value + ) + } + }) + ) + }) + + return variant +} diff --git a/lib/bigcommerce/api/fragments/product.ts b/lib/bigcommerce/api/fragments/product.ts index 73856ca2c..d266b8c92 100644 --- a/lib/bigcommerce/api/fragments/product.ts +++ b/lib/bigcommerce/api/fragments/product.ts @@ -1,3 +1,20 @@ +export const productPrices = /* GraphQL */ ` + fragment productPrices on Prices { + price { + value + currencyCode + } + salePrice { + value + currencyCode + } + retailPrice { + value + currencyCode + } + } +` + export const swatchOptionFragment = /* GraphQL */ ` fragment swatchOption on SwatchOptionValue { isDefault @@ -7,7 +24,6 @@ export const swatchOptionFragment = /* GraphQL */ ` export const multipleChoiceOptionFragment = /* GraphQL */ ` fragment multipleChoiceOption on MultipleChoiceOption { - entityId values { edges { node { @@ -31,18 +47,7 @@ export const productInfoFragment = /* GraphQL */ ` } description prices { - price { - value - currencyCode - } - salePrice { - value - currencyCode - } - retailPrice { - value - currencyCode - } + ...productPrices } images { edges { @@ -68,6 +73,7 @@ export const productInfoFragment = /* GraphQL */ ` productOptions { edges { node { + __typename entityId displayName ...multipleChoiceOption @@ -85,6 +91,7 @@ export const productInfoFragment = /* GraphQL */ ` } } + ${productPrices} ${multipleChoiceOptionFragment} ` diff --git a/lib/bigcommerce/api/operations/get-product.ts b/lib/bigcommerce/api/operations/get-product.ts index 5071f68ec..ab03a2eab 100644 --- a/lib/bigcommerce/api/operations/get-product.ts +++ b/lib/bigcommerce/api/operations/get-product.ts @@ -16,6 +16,38 @@ export const getProductQuery = /* GraphQL */ ` __typename ... on Product { ...productInfo + variants { + edges { + node { + entityId + defaultImage { + urlOriginal + altText + isDefault + } + prices { + ...productPrices + } + inventory { + aggregated { + availableToSell + warningLevel + } + isInStock + } + productOptions { + edges { + node { + __typename + entityId + displayName + ...multipleChoiceOption + } + } + } + } + } + } } } } diff --git a/lib/bigcommerce/schema.d.ts b/lib/bigcommerce/schema.d.ts index aaafbe312..cdc77bd5c 100644 --- a/lib/bigcommerce/schema.d.ts +++ b/lib/bigcommerce/schema.d.ts @@ -1684,6 +1684,16 @@ export type CategoryTreeItemFragment = { 'entityId' | 'name' | 'path' | 'description' | 'productCount' > +export type ProductPricesFragment = { __typename?: 'Prices' } & { + price: { __typename?: 'Money' } & Pick + salePrice?: Maybe< + { __typename?: 'Money' } & Pick + > + retailPrice?: Maybe< + { __typename?: 'Money' } & Pick + > +} + export type SwatchOptionFragment = { __typename?: 'SwatchOptionValue' } & Pick< SwatchOptionValue, 'isDefault' | 'hexColors' @@ -1723,17 +1733,7 @@ export type ProductInfoFragment = { __typename?: 'Product' } & Pick< 'entityId' | 'name' | 'path' | 'description' > & { brand?: Maybe<{ __typename?: 'Brand' } & Pick> - prices?: Maybe< - { __typename?: 'Prices' } & { - price: { __typename?: 'Money' } & Pick - salePrice?: Maybe< - { __typename?: 'Money' } & Pick - > - retailPrice?: Maybe< - { __typename?: 'Money' } & Pick - > - } - > + prices?: Maybe<{ __typename?: 'Prices' } & ProductPricesFragment> images: { __typename?: 'ImageConnection' } & { edges?: Maybe< Array< @@ -1904,7 +1904,101 @@ export type GetProductQuery = { __typename?: 'Query' } & { node?: Maybe< | { __typename: 'Brand' } | { __typename: 'Category' } - | ({ __typename: 'Product' } & ProductInfoFragment) + | ({ __typename: 'Product' } & { + variants: { __typename?: 'VariantConnection' } & { + edges?: Maybe< + Array< + Maybe< + { __typename?: 'VariantEdge' } & { + node: { __typename?: 'Variant' } & Pick< + Variant, + 'entityId' + > & { + defaultImage?: Maybe< + { __typename?: 'Image' } & Pick< + Image, + 'urlOriginal' | 'altText' | 'isDefault' + > + > + prices?: Maybe< + { __typename?: 'Prices' } & ProductPricesFragment + > + inventory?: Maybe< + { __typename?: 'VariantInventory' } & Pick< + VariantInventory, + 'isInStock' + > & { + aggregated?: Maybe< + { __typename?: 'Aggregated' } & Pick< + Aggregated, + 'availableToSell' | 'warningLevel' + > + > + } + > + productOptions: { + __typename?: 'ProductOptionConnection' + } & { + edges?: Maybe< + Array< + Maybe< + { __typename?: 'ProductOptionEdge' } & { + node: + | ({ + __typename?: 'CheckboxOption' + } & Pick< + CheckboxOption, + 'entityId' | 'displayName' + >) + | ({ + __typename?: 'DateFieldOption' + } & Pick< + DateFieldOption, + 'entityId' | 'displayName' + >) + | ({ + __typename?: 'FileUploadFieldOption' + } & Pick< + FileUploadFieldOption, + 'entityId' | 'displayName' + >) + | ({ + __typename?: 'MultiLineTextFieldOption' + } & Pick< + MultiLineTextFieldOption, + 'entityId' | 'displayName' + >) + | ({ + __typename?: 'MultipleChoiceOption' + } & Pick< + MultipleChoiceOption, + 'entityId' | 'displayName' + > & + MultipleChoiceOptionFragment) + | ({ + __typename?: 'NumberFieldOption' + } & Pick< + NumberFieldOption, + 'entityId' | 'displayName' + >) + | ({ + __typename?: 'TextFieldOption' + } & Pick< + TextFieldOption, + 'entityId' | 'displayName' + >) + } + > + > + > + } + } + } + > + > + > + } + } & ProductInfoFragment) | { __typename: 'Variant' } > }