From 287e6904954e6ea8c8b501d1c04f8f38d57332ab Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 11 Jan 2021 16:16:00 -0300 Subject: [PATCH] Correct Variant Added to Cart --- .../product/ProductView/ProductView.tsx | 23 ++++++----- components/product/helpers.ts | 38 +++--------------- .../wishlist/WishlistCard/WishlistCard.tsx | 29 +++++++------- framework/bigcommerce/api/catalog/products.ts | 2 +- framework/bigcommerce/lib/normalize.ts | 40 ++++++++++++------- .../bigcommerce/wishlist/use-wishlist.tsx | 2 +- framework/types.d.ts | 7 +--- 7 files changed, 63 insertions(+), 78 deletions(-) diff --git a/components/product/ProductView/ProductView.tsx b/components/product/ProductView/ProductView.tsx index 2c8a456d9..7151208ac 100644 --- a/components/product/ProductView/ProductView.tsx +++ b/components/product/ProductView/ProductView.tsx @@ -11,7 +11,7 @@ import { Button, Container, Text } from '@components/ui' import usePrice from '@framework/product/use-price' import { useAddItem } from '@framework/cart' -import { getCurrentVariant, SelectedOptions } from '../helpers' +import { getVariant, SelectedOptions } from '../helpers' import WishlistButton from '@components/wishlist/WishlistButton' interface Props { @@ -27,23 +27,24 @@ const ProductView: FC = ({ product }) => { baseAmount: product.price.retailValue, currencyCode: product.price.currencyCode!, }) - const { openSidebar } = useUI() - const [loading, setLoading] = useState(false) const [choices, setChoices] = useState({ size: null, color: null, }) - // const variant = getCurrentVariant(product, choices) || product.variants[0] - console.log('PRODUCT VIEW', product) + // Select the correct variant based on choices + const variant = getVariant(product, choices) + const addToCart = async () => { setLoading(true) try { await addItem({ productId: Number(product.id), - variantId: Number(product.variants[0].id), // TODO(bc) send the correct variant + variantId: variant + ? Number(variant.id) + : Number(product.variants[0].id), }) openSidebar() setLoading(false) @@ -108,11 +109,14 @@ const ProductView: FC = ({ product }) => {

{opt.displayName}

{opt.values.map((v, i: number) => { - const active = (choices as any)[opt.displayName] + const active = (choices as any)[ + opt.displayName.toLowerCase() + ] + return ( = ({ product }) => { setChoices((choices) => { return { ...choices, - [opt.displayName]: v.label, + [opt.displayName.toLowerCase()]: v.label.toLowerCase(), } }) }} @@ -142,6 +146,7 @@ const ProductView: FC = ({ product }) => { 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 eda8d434a..57c37dbc8 100644 --- a/components/product/helpers.ts +++ b/components/product/helpers.ts @@ -1,5 +1,3 @@ -import type { ProductNode } from '@framework/api/operations/get-product' - export type SelectedOptions = { size: string | null color: string | null @@ -10,42 +8,18 @@ export type ProductOption = { values: any } -// Returns the available options of a product -export function getProductOptions(product: ProductNode) { - 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 ?? {} - +export function getVariant(product: Product, opts: SelectedOptions) { + const variant = product.variants.find((variant) => { return Object.entries(opts).every(([key, value]) => - node?.productOptions.edges?.find((edge) => { + variant.options.find((option) => { if ( - edge?.node.__typename === 'MultipleChoiceOption' && - edge.node.displayName.toLowerCase() === key + option.__typename === 'MultipleChoiceOption' && + option.displayName.toLowerCase() === key.toLowerCase() ) { - return edge.node.values.edges?.find( - (valueEdge) => valueEdge?.node.label === value - ) + return option.values.find((v) => v.label.toLowerCase() === value) } }) ) }) - return variant } diff --git a/components/wishlist/WishlistCard/WishlistCard.tsx b/components/wishlist/WishlistCard/WishlistCard.tsx index b9b1a2f3e..82147f575 100644 --- a/components/wishlist/WishlistCard/WishlistCard.tsx +++ b/components/wishlist/WishlistCard/WishlistCard.tsx @@ -2,21 +2,20 @@ import { FC, useState } from 'react' import cn from 'classnames' import Link from 'next/link' import Image from 'next/image' -import type { WishlistItem } from '@framework/api/wishlist' -import usePrice from '@framework/product/use-price' -import useRemoveItem from '@framework/wishlist/use-remove-item' -import useAddItem from '@framework/cart/use-add-item' -import { useUI } from '@components/ui/context' -import { Button, Text } from '@components/ui' -import { Trash } from '@components/icons' import s from './WishlistCard.module.css' +import { Trash } from '@components/icons' +import { Button, Text } from '@components/ui' + +import { useUI } from '@components/ui/context' +import usePrice from '@framework/product/use-price' +import useAddItem from '@framework/cart/use-add-item' +import useRemoveItem from '@framework/wishlist/use-remove-item' interface Props { - item: WishlistItem + product: Product } -const WishlistCard: FC = ({ item }) => { - const product = item.product! +const WishlistCard: FC = ({ product }) => { const { price } = usePrice({ amount: product.prices?.price?.value, baseAmount: product.prices?.retailPrice?.value, @@ -34,7 +33,7 @@ const WishlistCard: FC = ({ item }) => { try { // If this action succeeds then there's no need to do `setRemoving(true)` // because the component will be removed from the view - await removeItem({ id: item.id! }) + await removeItem({ id: product.id! }) } catch (error) { setRemoving(false) } @@ -43,8 +42,8 @@ const WishlistCard: FC = ({ item }) => { setLoading(true) try { await addItem({ - productId: product.entityId, - variantId: product.variants.edges?.[0]?.node.entityId!, + productId: Number(product.id), + variantId: Number(product.variants[0].id), }) openSidebar() setLoading(false) @@ -57,10 +56,10 @@ const WishlistCard: FC = ({ item }) => {
{product.images.edges?.[0]?.node.altText
diff --git a/framework/bigcommerce/api/catalog/products.ts b/framework/bigcommerce/api/catalog/products.ts index 0e3690e55..7456fc3ac 100644 --- a/framework/bigcommerce/api/catalog/products.ts +++ b/framework/bigcommerce/api/catalog/products.ts @@ -21,7 +21,7 @@ export type ProductsHandlers = { const METHODS = ['GET'] -// TODO: a complete implementation should have schema validation for `req.body` +// TODO(lf): a complete implementation should have schema validation for `req.body` const productsApi: BigcommerceApiHandler< SearchProductsData, ProductsHandlers diff --git a/framework/bigcommerce/lib/normalize.ts b/framework/bigcommerce/lib/normalize.ts index ad6302c2e..5e3992d4b 100644 --- a/framework/bigcommerce/lib/normalize.ts +++ b/framework/bigcommerce/lib/normalize.ts @@ -1,5 +1,19 @@ import { Product as BCProduct } from '@framework/schema' +function productOptionNormalize({ + node: { + entityId, + values: { edges }, + ...rest + }, +}: any) { + return { + id: entityId, + values: edges.map(({ node }: any) => node), + ...rest, + } +} + export function normalizeProduct(productNode: BCProduct): Product { const { entityId: id, @@ -10,6 +24,7 @@ export function normalizeProduct(productNode: BCProduct): Product { path, id: _, options: _0, + brand, ...rest } = productNode @@ -27,26 +42,21 @@ export function normalizeProduct(productNode: BCProduct): Product { ) : [], variants: variants.edges - ? variants.edges.map(({ node: { entityId, ...rest } }: any) => ({ - id: entityId, - ...rest, - })) - : [], - options: productOptions.edges - ? productOptions.edges.map( - ({ - node: { - entityId, - values: { edges }, - ...rest - }, - }: any) => ({ + ? variants.edges.map( + ({ node: { entityId, productOptions, ...rest } }: any) => ({ id: entityId, - values: edges.map(({ node }: any) => node), + options: productOptions.edges.map(productOptionNormalize), ...rest, }) ) : [], + options: productOptions.edges + ? productOptions.edges.map(productOptionNormalize) + : [], + brand: { + id: brand?.entityId, + ...brand, + }, price: { value: prices?.price.value, currencyCode: prices?.price.currencyCode, diff --git a/framework/bigcommerce/wishlist/use-wishlist.tsx b/framework/bigcommerce/wishlist/use-wishlist.tsx index 6ebc8459d..0e1671039 100644 --- a/framework/bigcommerce/wishlist/use-wishlist.tsx +++ b/framework/bigcommerce/wishlist/use-wishlist.tsx @@ -46,7 +46,7 @@ export function extendHook( const response = useCommerceWishlist( defaultOpts, [ - ['customerId', customer?.entityId], + ['customerId', customer?.id], ['includeProducts', includeProducts], ], customFetcher, diff --git a/framework/types.d.ts b/framework/types.d.ts index e44f23bbb..e020ba893 100644 --- a/framework/types.d.ts +++ b/framework/types.d.ts @@ -31,6 +31,7 @@ interface ProductImage { interface ProductVariant { id: string | number + options: ProductOption[] } interface ProductPrice { @@ -51,21 +52,17 @@ interface Wishlist extends Entity { interface Order {} -interface Customer extends Entity { - [prop: string]: any -} +interface Customer extends Entity {} type UseCustomerResponse = { customer: Customer } | null interface Category extends Entity { - id: string name: string } interface Brand extends Entity { - id: string name: string }