import type { Cart, LineItem, ProductVariant, SelectedOption, } from '@commerce/types/cart' import MissingLineItemVariantError from '../../errors/MissingLineItemVariantError' import { requireConfigValue } from '../../isomorphic-config' import type { OrderAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Order' import type { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product' import type { Image } from '@commerce/types/common' import { jsonApi } from '@spree/storefront-api-v2-sdk' import createGetAbsoluteImageUrl from '../create-get-absolute-image-url' import getMediaGallery from '../get-media-gallery' import type { LineItemAttr, OptionTypeAttr, SpreeProductImage, SpreeSdkResponse, VariantAttr, } from '../../types' const placeholderImage = requireConfigValue('lineItemPlaceholderImageUrl') as | string | false const isColorProductOption = (productOptionType: OptionTypeAttr) => { return productOptionType.attributes.presentation === 'Color' } const normalizeVariant = ( spreeSuccessResponse: SpreeSdkResponse, spreeVariant: VariantAttr ): ProductVariant => { const spreeProduct = jsonApi.findSingleRelationshipDocument( spreeSuccessResponse, spreeVariant, 'product' ) if (spreeProduct === null) { throw new MissingLineItemVariantError( `Couldn't find product for variant with id ${spreeVariant.id}.` ) } const spreeVariantImageRecords = jsonApi.findRelationshipDocuments( spreeSuccessResponse, spreeVariant, 'images' ) let lineItemImage const variantImage = getMediaGallery( spreeVariantImageRecords, createGetAbsoluteImageUrl(requireConfigValue('imageHost') as string) )[0] if (variantImage) { lineItemImage = variantImage } else { const spreeProductImageRecords = jsonApi.findRelationshipDocuments( spreeSuccessResponse, spreeProduct, 'images' ) const productImage = getMediaGallery( spreeProductImageRecords, createGetAbsoluteImageUrl(requireConfigValue('imageHost') as string) )[0] lineItemImage = productImage } const image: Image = lineItemImage ?? (placeholderImage === false ? undefined : { url: placeholderImage }) return { id: spreeVariant.id, sku: spreeVariant.attributes.sku, name: spreeProduct.attributes.name, requiresShipping: true, price: parseFloat(spreeVariant.attributes.price), listPrice: parseFloat(spreeVariant.attributes.price), image, isInStock: spreeVariant.attributes.in_stock, availableForSale: spreeVariant.attributes.purchasable, ...(spreeVariant.attributes.weight === '0.0' ? {} : { weight: { value: parseFloat(spreeVariant.attributes.weight), unit: 'KILOGRAMS', }, }), // TODO: Add height, width and depth when Measurement type allows distance measurements. } } const normalizeLineItem = ( spreeSuccessResponse: SpreeSdkResponse, spreeLineItem: LineItemAttr ): LineItem => { const variant = jsonApi.findSingleRelationshipDocument( spreeSuccessResponse, spreeLineItem, 'variant' ) if (variant === null) { throw new MissingLineItemVariantError( `Couldn't find variant for line item with id ${spreeLineItem.id}.` ) } const product = jsonApi.findSingleRelationshipDocument( spreeSuccessResponse, variant, 'product' ) if (product === null) { throw new MissingLineItemVariantError( `Couldn't find product for variant with id ${variant.id}.` ) } // CartItem.tsx expects path without a '/' prefix unlike pages/product/[slug].tsx and others. const path = `${product.attributes.slug}` const spreeOptionValues = jsonApi.findRelationshipDocuments( spreeSuccessResponse, variant, 'option_values' ) const options: SelectedOption[] = spreeOptionValues.map( (spreeOptionValue) => { const spreeOptionType = jsonApi.findSingleRelationshipDocument( spreeSuccessResponse, spreeOptionValue, 'option_type' ) if (spreeOptionType === null) { throw new MissingLineItemVariantError( `Couldn't find option type of option value with id ${spreeOptionValue.id}.` ) } const label = isColorProductOption(spreeOptionType) ? spreeOptionValue.attributes.name : spreeOptionValue.attributes.presentation return { id: spreeOptionValue.id, name: spreeOptionType.attributes.presentation, value: label, } } ) return { id: spreeLineItem.id, variantId: variant.id, productId: product.id, name: spreeLineItem.attributes.name, quantity: spreeLineItem.attributes.quantity, discounts: [], // TODO: Implement when the template starts displaying them. path, variant: normalizeVariant(spreeSuccessResponse, variant), options, } } const normalizeCart = ( spreeSuccessResponse: SpreeSdkResponse, spreeCart: OrderAttr ): Cart => { const lineItems = jsonApi .findRelationshipDocuments( spreeSuccessResponse, spreeCart, 'line_items' ) .map((lineItem) => normalizeLineItem(spreeSuccessResponse, lineItem)) return { id: spreeCart.id, createdAt: spreeCart.attributes.created_at.toString(), currency: { code: spreeCart.attributes.currency }, taxesIncluded: true, lineItems, lineItemsSubtotalPrice: parseFloat(spreeCart.attributes.item_total), subtotalPrice: parseFloat(spreeCart.attributes.item_total), totalPrice: parseFloat(spreeCart.attributes.total), customerId: spreeCart.attributes.token, email: spreeCart.attributes.email, discounts: [], // TODO: Implement when the template starts displaying them. } } export { normalizeLineItem } export default normalizeCart