4
0
forked from crowetic/commerce

Match product options with variants

This commit is contained in:
Greg Hoskin 2021-04-21 19:56:00 -05:00
parent 8a8ef7dbba
commit dd40b8c604
4 changed files with 196 additions and 143 deletions

View File

@ -1,5 +1,4 @@
import { getConfig, SwellConfig } from '../api' import { getConfig, SwellConfig } from '../api'
import getPageQuery from '../utils/queries/get-page-query'
import { Page } from './get-all-pages' import { Page } from './get-all-pages'
type Variables = { type Variables = {

View File

@ -321,96 +321,113 @@ export enum CardBrand {
} }
/** A container for all the information required to checkout items and pay. */ /** A container for all the information required to checkout items and pay. */
export type Checkout = Node & { export type Checkout = {
__typename?: 'Checkout' name: string
/** The gift cards used on the checkout. */ currency: string
appliedGiftCards: Array<AppliedGiftCard> support_email: string
/** fields: any[]
* The available shipping rates for this Checkout. scripts: any[]
* Should only be used when checkout `requiresShipping` is `true` and accounts: string
* the shipping address is valid. email_optin: boolean
*/ terms_policy?: string
availableShippingRates?: Maybe<AvailableShippingRates> refund_policy?: string
/** The date and time when the checkout was completed. */ privacy_policy?: string
completedAt?: Maybe<Scalars['DateTime']> theme?: stirng
/** The date and time when the checkout was created. */ countries: any[]
createdAt: Scalars['DateTime'] currencies: any[]
/** The currency code for the Checkout. */ payment_methods: any[]
currencyCode: CurrencyCode coupons: boolean
/** A list of extra information that is added to the checkout. */ giftcards: boolean
customAttributes: Array<Attribute>
/** // __typename?: 'Checkout'
* The customer associated with the checkout. // /** The gift cards used on the checkout. */
* @deprecated This field will always return null. If you have an authentication token for the customer, you can use the `customer` field on the query root to retrieve it. // appliedGiftCards: Array<AppliedGiftCard>
*/ // /**
customer?: Maybe<Customer> // * The available shipping rates for this Checkout.
/** Discounts that have been applied on the checkout. */ // * Should only be used when checkout `requiresShipping` is `true` and
discountApplications: DiscountApplicationConnection // * the shipping address is valid.
/** The email attached to this checkout. */ // */
email?: Maybe<Scalars['String']> // availableShippingRates?: Maybe<AvailableShippingRates>
/** Globally unique identifier. */ // /** The date and time when the checkout was completed. */
id: Scalars['ID'] // completedAt?: Maybe<Scalars['DateTime']>
/** A list of line item objects, each one containing information about an item in the checkout. */ // /** The date and time when the checkout was created. */
lineItems: CheckoutLineItemConnection // createdAt: Scalars['DateTime']
/** The sum of all the prices of all the items in the checkout. Duties, taxes, shipping and discounts excluded. */ // /** The currency code for the Checkout. */
lineItemsSubtotalPrice: MoneyV2 // currencyCode: CurrencyCode
/** The note associated with the checkout. */ // /** A list of extra information that is added to the checkout. */
note?: Maybe<Scalars['String']> // customAttributes: Array<Attribute>
/** The resulting order from a paid checkout. */ // /**
order?: Maybe<Order> // * The customer associated with the checkout.
/** The Order Status Page for this Checkout, null when checkout is not completed. */ // * @deprecated This field will always return null. If you have an authentication token for the customer, you can use the `customer` field on the query root to retrieve it.
orderStatusUrl?: Maybe<Scalars['URL']> // */
/** // customer?: Maybe<Customer>
* The amount left to be paid. This is equal to the cost of the line items, taxes and shipping minus discounts and gift cards. // /** Discounts that have been applied on the checkout. */
* @deprecated Use `paymentDueV2` instead // discountApplications: DiscountApplicationConnection
*/ // /** The email attached to this checkout. */
paymentDue: Scalars['Money'] // email?: Maybe<Scalars['String']>
/** The amount left to be paid. This is equal to the cost of the line items, duties, taxes and shipping minus discounts and gift cards. */ // /** Globally unique identifier. */
paymentDueV2: MoneyV2 // id: Scalars['ID']
/** // /** A list of line item objects, each one containing information about an item in the checkout. */
* Whether or not the Checkout is ready and can be completed. Checkouts may // lineItems: CheckoutLineItemConnection
* have asynchronous operations that can take time to finish. If you want // /** The sum of all the prices of all the items in the checkout. Duties, taxes, shipping and discounts excluded. */
* to complete a checkout or ensure all the fields are populated and up to // lineItemsSubtotalPrice: MoneyV2
* date, polling is required until the value is true. // /** The note associated with the checkout. */
*/ // note?: Maybe<Scalars['String']>
ready: Scalars['Boolean'] // /** The resulting order from a paid checkout. */
/** States whether or not the fulfillment requires shipping. */ // order?: Maybe<Order>
requiresShipping: Scalars['Boolean'] // /** The Order Status Page for this Checkout, null when checkout is not completed. */
/** The shipping address to where the line items will be shipped. */ // orderStatusUrl?: Maybe<Scalars['URL']>
shippingAddress?: Maybe<MailingAddress> // /**
/** The discounts that have been allocated onto the shipping line by discount applications. */ // * The amount left to be paid. This is equal to the cost of the line items, taxes and shipping minus discounts and gift cards.
shippingDiscountAllocations: Array<DiscountAllocation> // * @deprecated Use `paymentDueV2` instead
/** Once a shipping rate is selected by the customer it is transitioned to a `shipping_line` object. */ // */
shippingLine?: Maybe<ShippingRate> // paymentDue: Scalars['Money']
/** // /** The amount left to be paid. This is equal to the cost of the line items, duties, taxes and shipping minus discounts and gift cards. */
* Price of the checkout before shipping and taxes. // paymentDueV2: MoneyV2
* @deprecated Use `subtotalPriceV2` instead // /**
*/ // * Whether or not the Checkout is ready and can be completed. Checkouts may
subtotalPrice: Scalars['Money'] // * have asynchronous operations that can take time to finish. If you want
/** Price of the checkout before duties, shipping and taxes. */ // * to complete a checkout or ensure all the fields are populated and up to
subtotalPriceV2: MoneyV2 // * date, polling is required until the value is true.
/** Specifies if the Checkout is tax exempt. */ // */
taxExempt: Scalars['Boolean'] // ready: Scalars['Boolean']
/** Specifies if taxes are included in the line item and shipping line prices. */ // /** States whether or not the fulfillment requires shipping. */
taxesIncluded: Scalars['Boolean'] // requiresShipping: Scalars['Boolean']
/** // /** The shipping address to where the line items will be shipped. */
* The sum of all the prices of all the items in the checkout, taxes and discounts included. // shippingAddress?: Maybe<MailingAddress>
* @deprecated Use `totalPriceV2` instead // /** The discounts that have been allocated onto the shipping line by discount applications. */
*/ // shippingDiscountAllocations: Array<DiscountAllocation>
totalPrice: Scalars['Money'] // /** Once a shipping rate is selected by the customer it is transitioned to a `shipping_line` object. */
/** The sum of all the prices of all the items in the checkout, duties, taxes and discounts included. */ // shippingLine?: Maybe<ShippingRate>
totalPriceV2: MoneyV2 // /**
/** // * Price of the checkout before shipping and taxes.
* The sum of all the taxes applied to the line items and shipping lines in the checkout. // * @deprecated Use `subtotalPriceV2` instead
* @deprecated Use `totalTaxV2` instead // */
*/ // subtotalPrice: Scalars['Money']
totalTax: Scalars['Money'] // /** Price of the checkout before duties, shipping and taxes. */
/** The sum of all the taxes applied to the line items and shipping lines in the checkout. */ // subtotalPriceV2: MoneyV2
totalTaxV2: MoneyV2 // /** Specifies if the Checkout is tax exempt. */
/** The date and time when the checkout was last updated. */ // taxExempt: Scalars['Boolean']
updatedAt: Scalars['DateTime'] // /** Specifies if taxes are included in the line item and shipping line prices. */
/** The url pointing to the checkout accessible from the web. */ // taxesIncluded: Scalars['Boolean']
webUrl: Scalars['URL'] // /**
// * The sum of all the prices of all the items in the checkout, taxes and discounts included.
// * @deprecated Use `totalPriceV2` instead
// */
// totalPrice: Scalars['Money']
// /** The sum of all the prices of all the items in the checkout, duties, taxes and discounts included. */
// totalPriceV2: MoneyV2
// /**
// * The sum of all the taxes applied to the line items and shipping lines in the checkout.
// * @deprecated Use `totalTaxV2` instead
// */
// totalTax: Scalars['Money']
// /** The sum of all the taxes applied to the line items and shipping lines in the checkout. */
// totalTaxV2: MoneyV2
// /** The date and time when the checkout was last updated. */
// updatedAt: Scalars['DateTime']
// /** The url pointing to the checkout accessible from the web. */
// webUrl: Scalars['URL']
} }
/** A container for all the information required to checkout items and pay. */ /** A container for all the information required to checkout items and pay. */

View File

@ -1,6 +1,15 @@
import * as Core from '@commerce/types' import * as Core from '@commerce/types'
import { CheckoutLineItem } from './schema' import { CheckoutLineItem } from './schema'
export type SwellImage = {
file: {
url: String
height: Number
width: Number
}
id: string
}
export type SwellCart = { export type SwellCart = {
id: string id: string
account_id: number account_id: number
@ -21,9 +30,28 @@ export type SwellCart = {
// TODO: add missing fields // TODO: add missing fields
} }
export type VariantResult = { export type SwellVariant = {
id: string id: string
option_value_ids: string[] option_value_ids: string[]
name: string
price?: number
stock_status?: string
}
export interface ProductOptionValue {
label: string
hexColors?: string[]
id: string
}
export type ProductOptions = {
id: string
name: string
variant: boolean
values: ProductOptionValue[]
required: boolean
active: boolean
attribute_id: string
} }
export interface SwellProduct { export interface SwellProduct {

View File

@ -1,19 +1,22 @@
import { Product } from '@commerce/types' import { Product, Customer } from '@commerce/types'
import { Customer } from '@commerce/types'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { import {
Product as ShopifyProduct,
Checkout, Checkout,
CheckoutLineItemEdge, CheckoutLineItemEdge,
SelectedOption,
ImageConnection,
ProductVariantConnection,
MoneyV2, MoneyV2,
ProductOption, ProductOption,
} from '../schema' } from '../schema'
import type { Cart, LineItem, SwellCustomer, SwellProduct } from '../types' import type {
Cart,
LineItem,
SwellCustomer,
SwellProduct,
SwellImage,
SwellVariant,
ProductOptionValue,
} from '../types'
const money = ({ amount, currencyCode }: MoneyV2) => { const money = ({ amount, currencyCode }: MoneyV2) => {
return { return {
@ -22,15 +25,22 @@ const money = ({ amount, currencyCode }: MoneyV2) => {
} }
} }
type normalizedProductOption = {
__typename?: string
id: string
displayName: string
values: ProductOptionValue[]
}
const normalizeProductOption = ({ const normalizeProductOption = ({
id, id,
name: displayName, name: displayName = '',
values, values,
}: ProductOption) => { }: ProductOption) => {
let returnValues = values.map((value) => { let returnValues = values.map((value) => {
let output: any = { let output: any = {
displayName,
label: value.name, label: value.name,
id: value?.id || id,
} }
if (displayName === 'Color') { if (displayName === 'Color') {
output = { output = {
@ -48,50 +58,52 @@ const normalizeProductOption = ({
} }
} }
type SwellImage = { const normalizeProductImages = (images: SwellImage[]) => {
file: {
url: String
height: Number
width: Number
}
id: string
}
const normalizeProductImages = (images) => {
if (!images) { if (!images) {
return [{ url: '/' }] return [{ url: '/' }]
} }
return images?.map(({ file, ...rest }: SwellImage) => ({ return images?.map(({ file, ...rest }: SwellImage) => ({
url: file?.url, url: file?.url + '',
height: file?.height, height: Number(file?.height),
width: file?.width, width: Number(file?.width),
...rest, ...rest,
})) }))
} }
const normalizeProductVariants = (variants) => { const normalizeProductVariants = (
return variants?.map(({ id, name, price, sku }) => { variants: SwellVariant[],
const values = name productOptions: normalizedProductOption[]
.split(',') ) => {
.map((i) => ({ name: i.trim(), label: i.trim() })) return variants?.map(
({ id, name, price, option_value_ids: optionValueIds }) => {
const values = name
.split(',')
.map((i) => ({ name: i.trim(), label: i.trim() }))
const options = values.map((value) => { const options = optionValueIds.map((id) => {
return normalizeProductOption({ const matchingOption = productOptions.find((option) => {
return option.values.find(
(value: ProductOptionValue) => value.id == id
)
})
return normalizeProductOption({
id,
name: matchingOption?.displayName ?? '',
values,
})
})
return {
id, id,
name, name,
values: [value], // sku: sku ?? id,
}) price: price ?? null,
}) listPrice: price ?? null,
// requiresShipping: true,
return { options,
id, }
name,
sku: sku ?? id,
price: price ?? null,
listPrice: price ?? null,
// requiresShipping: true,
options,
} }
}) )
} }
export function normalizeProduct(swellProduct: SwellProduct): Product { export function normalizeProduct(swellProduct: SwellProduct): Product {
@ -108,10 +120,10 @@ export function normalizeProduct(swellProduct: SwellProduct): Product {
const productOptions = options const productOptions = options
? options.map((o) => normalizeProductOption(o)) ? options.map((o) => normalizeProductOption(o))
: [] : []
const productVariants = variants ? normalizeProductVariants(variants) : [] const productVariants = variants
? normalizeProductVariants(variants, productOptions)
: []
// ProductView.tsx assumes the existence of at least one product variant
const emptyVariants = [{ options: [{ id: 123 }] }]
const productImages = normalizeProductImages(images) const productImages = normalizeProductImages(images)
const product = { const product = {
...swellProduct, ...swellProduct,
@ -120,10 +132,7 @@ export function normalizeProduct(swellProduct: SwellProduct): Product {
vendor: '', vendor: '',
path: `/${slug}`, path: `/${slug}`,
images: productImages, images: productImages,
variants: variants: productVariants,
productVariants && productVariants.length
? productVariants
: emptyVariants,
options: productOptions, options: productOptions,
price: { price: {
value, value,
@ -167,12 +176,12 @@ function normalizeLineItem({
}: CheckoutLineItemEdge): LineItem { }: CheckoutLineItemEdge): LineItem {
const item = { const item = {
id, id,
variantId: String(variant?.id), variantId: variant?.id ?? '',
productId: String(product?.id), productId: product?.id ?? '',
name: product?.name ?? '', name: product?.name ?? '',
quantity, quantity,
variant: { variant: {
id: String(variant?.id), id: variant?.id ?? '',
sku: variant?.sku ?? '', sku: variant?.sku ?? '',
name: variant?.name!, name: variant?.name!,
image: { image: {