diff --git a/components/product/ProductView/ProductView.tsx b/components/product/ProductView/ProductView.tsx index 16c02ca51..d38da3528 100644 --- a/components/product/ProductView/ProductView.tsx +++ b/components/product/ProductView/ProductView.tsx @@ -49,10 +49,10 @@ const ProductView: FC = ({ product }) => { await addItem({ productId: String(product.id), - variantId: String(selectedVariant.sku), + variantId: String(selectedVariant.id), pricing: { amount: selectedVariant.price, - currencyCode: product.price.currencyCode, + currencyCode: product.price.currencyCode ?? 'USD', }, }) openSidebar() diff --git a/framework/reactioncommerce/auth/use-login.tsx b/framework/reactioncommerce/auth/use-login.tsx index fb1fd22fe..a479d929f 100644 --- a/framework/reactioncommerce/auth/use-login.tsx +++ b/framework/reactioncommerce/auth/use-login.tsx @@ -1,6 +1,6 @@ import { useCallback } from 'react' import type { MutationHook } from '@commerce/utils/types' -import { CommerceError, ValidationError } from '@commerce/utils/errors' +import { CommerceError } from '@commerce/utils/errors' import useCustomer from '../customer/use-customer' import authenticateMutation from '../utils/mutations/authenticate' import { diff --git a/framework/reactioncommerce/utils/normalize.ts b/framework/reactioncommerce/utils/normalize.ts index 296832e6e..c97eb6185 100644 --- a/framework/reactioncommerce/utils/normalize.ts +++ b/framework/reactioncommerce/utils/normalize.ts @@ -1,9 +1,8 @@ -import { Product, Customer } from '@commerce/types' +import {Product, Customer, ProductVariant, ProductOption, ProductOptionValues} from '@commerce/types' import { Account, Cart as ReactionCart, - ProductPricingInfo, CatalogProductVariant, CartItemEdge, CatalogItemProduct, @@ -12,20 +11,7 @@ import { Maybe, } from '../schema' -import type { Cart, LineItem } from '../types' - -type ProductOption = { - __typename?: string - id: string - displayName: string - values: any[] -} - -const money = ({ displayPrice }: ProductPricingInfo) => { - return { - displayPrice, - } -} +import type {Cart, LineItem} from '../types' const normalizeProductImages = (images: Maybe[], name: string) => images.map((image) => ({ @@ -33,83 +19,104 @@ const normalizeProductImages = (images: Maybe[], name: string) => alt: name, })) -const normalizeProductOption = ({ id, displayName, values }: ProductOption) => { - return { +const normalizeProductOption = (variant: CatalogProductVariant) => { + const option = { __typename: 'MultipleChoiceOption', - id, - displayName, - values: values.map((value) => { - let output: any = { - label: value, - } - if (displayName.toLowerCase() === 'color') { - output = { - ...output, - hexColors: [value], - } - } - return output - }), + id: variant._id, + displayName: variant.attributeLabel, + values: variant.optionTitle ? [{label: variant.optionTitle}] : [] } + option.values = option.values.map(value => colorizeProductOptionValue(value, option.displayName)) + + return option; } -const normalizeProductVariants = (variants: CatalogProductVariant[]) => { - return variants.map((variant) => { - const { _id, options, sku, title, pricing = [], variantId } = variant ?? {} +function colorizeProductOptionValue(value: ProductOptionValues, displayName: string): ProductOptionValues { + if (displayName.toLowerCase() === 'color') { + value.hexColors = [value.label] + } + return value; +} + +const normalizeProductVariants = (variants: CatalogProductVariant[]): ProductVariant[] => { + return variants.reduce((productVariants: ProductVariant[], variant: CatalogProductVariant) => { + + if (variantHasOptions(variant)) { + productVariants.push(...flatVariantOptions(variant)) + return productVariants + } + + const {sku, title, pricing = [], variantId} = variant ?? {} const variantPrice = pricing[0]?.price ?? pricing[0]?.minPrice ?? 0 - return { - id: _id ?? '', + productVariants.push({ + id: variantId ?? '', name: title, sku: sku ?? variantId, price: variantPrice, listPrice: pricing[0]?.compareAtPrice?.amount ?? variantPrice, requiresShipping: true, - options: options?.length - ? options.map((option) => { - return normalizeProductOption({ - id: option?._id ?? '', - displayName: option?.attributeLabel ?? '', - values: [option?.optionTitle], - }) - }) - : [], - } - }) + options: [normalizeProductOption(variant)] + }); + + return productVariants; + }, []) } -export function groupProductOptionsByAttributeLabel( - options: CatalogProductVariant[] -) { - return options.reduce((groupedOptions, currentOption) => { - const attributeLabelIndex = groupedOptions.findIndex((option) => { - return ( - option.displayName.toLowerCase() === - currentOption?.attributeLabel.toLowerCase() - ) - }) +function groupProductOptionsByAttributeLabel(variants: CatalogProductVariant[]): ProductOption[] { + return variants.reduce((groupedOptions: ProductOption[], currentVariant: CatalogProductVariant) => { - if (attributeLabelIndex !== -1) { - groupedOptions[attributeLabelIndex].values = [ - ...groupedOptions[attributeLabelIndex].values, - { - label: currentOption?.optionTitle ?? '', - hexColors: [currentOption?.optionTitle] ?? '', - }, - ] - } else { - groupedOptions = [ - ...groupedOptions, - normalizeProductOption({ - id: currentOption?._id ?? '', - displayName: currentOption?.attributeLabel ?? '', - values: [currentOption?.optionTitle ?? ''], - }), - ] + groupedOptions = mergeVariantOptionsWithExistingOptions(groupedOptions, currentVariant); + + if (variantHasOptions(currentVariant)) { + (currentVariant.options).forEach(variantOption => { + groupedOptions = mergeVariantOptionsWithExistingOptions(groupedOptions, variantOption) + }) } return groupedOptions - }, [] as ProductOption[]) + }, []) + + function mergeVariantOptionsWithExistingOptions( + groupedOptions: ProductOption[], + currentVariant: CatalogProductVariant): ProductOption[] { + + const matchingOptionIndex = findCurrentVariantOptionsInGroupedOptions(groupedOptions, currentVariant) + + return matchingOptionIndex !== -1 ? + mergeWithExistingOptions(groupedOptions, currentVariant, matchingOptionIndex) : + addNewProductOption(groupedOptions, currentVariant) + } + + function findCurrentVariantOptionsInGroupedOptions( + groupedOptions: ProductOption[], + currentVariant: CatalogProductVariant): number { + + return groupedOptions.findIndex(option => + (option.displayName.toLowerCase() === currentVariant.attributeLabel.toLowerCase()) + ); + } + + function mergeWithExistingOptions( + groupedOptions: ProductOption[], + currentVariant: CatalogProductVariant, + matchingOptionIndex: number) { + + const currentVariantOption = normalizeProductOption(currentVariant); + groupedOptions[matchingOptionIndex].values = [ + ...groupedOptions[matchingOptionIndex].values, + ...currentVariantOption.values + ] + + return groupedOptions; + } + + function addNewProductOption(groupedOptions: ProductOption[], currentVariant: CatalogProductVariant) { + return [ + ...groupedOptions, + normalizeProductOption(currentVariant), + ]; + } } export function normalizeProduct(productNode: CatalogItemProduct): Product { @@ -124,9 +131,7 @@ export function normalizeProduct(productNode: CatalogItemProduct): Product { sku, media, pricing, - vendor, - variants, - ...rest + variants } = product return { @@ -142,11 +147,8 @@ export function normalizeProduct(productNode: CatalogItemProduct): Product { value: pricing[0]?.minPrice ?? 0, currencyCode: pricing[0]?.currency.code, }, - variants: variants?.length ? normalizeProductVariants(variants) : [], - options: variants?.length - ? groupProductOptionsByAttributeLabel(variants) - : [], - ...rest, + variants: variants !== null ? normalizeProductVariants(variants) : [], + options: variants !== null ? groupProductOptionsByAttributeLabel(variants) : [] } } @@ -157,30 +159,32 @@ export function normalizeCart(cart: ReactionCart): Cart { email: '', createdAt: cart.createdAt, currency: { - code: cart.checkout?.summary?.total?.currency.code, + code: cart.checkout?.summary?.total?.currency.code ?? '', }, taxesIncluded: false, - lineItems: cart.items?.edges?.map(normalizeLineItem), - lineItemsSubtotalPrice: +cart.checkout?.summary?.itemTotal?.amount, - subtotalPrice: +cart.checkout?.summary?.itemTotal?.amount, - totalPrice: cart.checkout?.summary?.total?.amount, + lineItems: cart.items?.edges?.map(cartItem => normalizeLineItem(cartItem)) ?? [], + lineItemsSubtotalPrice: +(cart.checkout?.summary?.itemTotal?.amount ?? 0), + subtotalPrice: +(cart.checkout?.summary?.itemTotal?.amount ?? 0), + totalPrice: cart.checkout?.summary?.total?.amount ?? 0, discounts: [], } } -function normalizeLineItem({ - node: { - _id, - compareAtPrice, - imageURLs, - title, - productConfiguration, - priceWhenAdded, - optionTitle, - variantTitle, - quantity, - }, -}: CartItemEdge): LineItem { +function normalizeLineItem(cartItem: CartItemEdge): LineItem { + const { + node: { + _id, + compareAtPrice, + imageURLs, + title, + productConfiguration, + priceWhenAdded, + optionTitle, + variantTitle, + quantity, + } + } = cartItem + console.log('imageURLs', imageURLs) return { id: _id, @@ -193,7 +197,7 @@ function normalizeLineItem({ sku: String(productConfiguration?.productVariantId), name: String(optionTitle || variantTitle), image: { - url: imageURLs?.original ?? '/product-img-placeholder.svg', + url: imageURLs?.thumbnail ?? '/product-img-placeholder.svg', }, requiresShipping: true, price: priceWhenAdded?.amount, @@ -211,12 +215,26 @@ function normalizeLineItem({ export function normalizeCustomer(viewer: Account): Customer { if (!viewer) { - return {} + return {} } - return { + return { firstName: viewer.firstName ?? '', lastName: viewer.lastName ?? '', email: viewer.primaryEmailAddress, } } + +function flatVariantOptions(variant: CatalogProductVariant): ProductVariant[] { + const variantOptions = variant.options; + + return normalizeProductVariants(variantOptions) + .map(variantOption => { + variantOption.options.push(normalizeProductOption(variant)) + return variantOption + }); +} + +function variantHasOptions(variant: CatalogProductVariant) { + return !!variant.options && variant.options.length != 0; +}