Use Spree SDK's JSON:API helpers

This commit is contained in:
tniezg
2021-09-13 16:06:15 +02:00
parent 5b241d036b
commit c8ee3ef293
6 changed files with 70 additions and 129 deletions

View File

@@ -6,9 +6,10 @@ import type {
JsonApiDocument, JsonApiDocument,
JsonApiResponse, JsonApiResponse,
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi' } from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
import { jsonApi } from '@spree/storefront-api-v2-sdk'
import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships' import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
import SpreeResponseContentError from '../errors/SpreeResponseContentError' import SpreeResponseContentError from '../errors/SpreeResponseContentError'
import { findIncluded } from './find-json-api-documents' import type { OptionTypeAttr } from '@framework/types'
const isColorProductOption = (productOption: ProductOption) => { const isColorProductOption = (productOption: ProductOption) => {
return productOption.displayName === 'Color' return productOption.displayName === 'Color'
@@ -29,10 +30,9 @@ const expandOptions = (
let option: ProductOption let option: ProductOption
if (existingOptionIndex === -1) { if (existingOptionIndex === -1) {
const spreeOptionType = findIncluded( const spreeOptionType = jsonApi.findDocument<OptionTypeAttr>(
spreeSuccessResponse, spreeSuccessResponse,
spreeOptionTypeIdentifier.type, spreeOptionTypeIdentifier
spreeOptionTypeIdentifier.id
) )
if (!spreeOptionType) { if (!spreeOptionType) {

View File

@@ -1,46 +0,0 @@
// Based on https://github.com/spark-solutions/spree2vuestorefront
import type {
JsonApiResponse,
JsonApiDocument,
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
export const findIncluded = <T extends JsonApiDocument>(
response: JsonApiResponse,
objectType: string,
objectId: string
): T | null => {
if (!response.included) {
return null
}
return (
(response.included.find(
(includedObject) =>
includedObject.type === objectType && includedObject.id === objectId
) as T) || null
)
}
export const findIncludedOfType = <T extends JsonApiDocument>(
response: JsonApiResponse,
singlePrimaryRecord: JsonApiDocument,
objectRelationshipType: string
): T[] => {
if (!response.included) {
return []
}
const typeRelationships =
singlePrimaryRecord.relationships[objectRelationshipType]
if (!typeRelationships) {
return []
}
return typeRelationships.data
.map((typeObject: JsonApiDocument) =>
findIncluded(response, typeObject.type, typeObject.id)
)
.filter((typeRecord: JsonApiDocument | null) => !!typeRecord)
}

View File

@@ -6,23 +6,19 @@ import type {
} from '@commerce/types/cart' } from '@commerce/types/cart'
import MissingLineItemVariantError from '../errors/MissingLineItemVariantError' import MissingLineItemVariantError from '../errors/MissingLineItemVariantError'
import { requireConfigValue } from '../isomorphic-config' import { requireConfigValue } from '../isomorphic-config'
import type {
JsonApiListResponse,
JsonApiSingleResponse,
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
import type { OrderAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Order' 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 { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
import createGetAbsoluteImageUrl from './create-get-absolute-image-url' import createGetAbsoluteImageUrl from './create-get-absolute-image-url'
import getMediaGallery from './get-media-gallery' import getMediaGallery from './get-media-gallery'
import { findIncluded, findIncludedOfType } from './find-json-api-documents'
import type { import type {
LineItemAttr, LineItemAttr,
OptionTypeAttr, OptionTypeAttr,
SpreeProductImage,
SpreeSdkResponse, SpreeSdkResponse,
VariantAttr, VariantAttr,
} from '../types' } from '../types'
import type { Image } from '@commerce/types/common' import type { Image } from '@commerce/types/common'
import { jsonApi } from '@spree/storefront-api-v2-sdk'
const placeholderImage = requireConfigValue('lineItemPlaceholderImageUrl') as const placeholderImage = requireConfigValue('lineItemPlaceholderImageUrl') as
| string | string
@@ -36,25 +32,24 @@ const normalizeVariant = (
spreeSuccessResponse: SpreeSdkResponse, spreeSuccessResponse: SpreeSdkResponse,
spreeVariant: VariantAttr spreeVariant: VariantAttr
): ProductVariant => { ): ProductVariant => {
const productIdentifier = spreeVariant.relationships.product const spreeProduct = jsonApi.findSingleRelationshipDocument<ProductAttr>(
.data as RelationType
const spreeProduct = findIncluded<ProductAttr>(
spreeSuccessResponse, spreeSuccessResponse,
productIdentifier.type, spreeVariant,
productIdentifier.id 'product'
) )
if (spreeProduct === null) { if (spreeProduct === null) {
throw new MissingLineItemVariantError( throw new MissingLineItemVariantError(
`Couldn't find product with id ${productIdentifier.id}.` `Couldn't find product for variant with id ${spreeVariant.id}.`
) )
} }
const spreeVariantImageRecords = findIncludedOfType( const spreeVariantImageRecords =
spreeSuccessResponse, jsonApi.findRelationshipDocuments<SpreeProductImage>(
spreeVariant, spreeSuccessResponse,
'images' spreeVariant,
) 'images'
)
let lineItemImage let lineItemImage
@@ -66,11 +61,12 @@ const normalizeVariant = (
if (variantImage) { if (variantImage) {
lineItemImage = variantImage lineItemImage = variantImage
} else { } else {
const spreeProductImageRecords = findIncludedOfType( const spreeProductImageRecords =
spreeSuccessResponse, jsonApi.findRelationshipDocuments<SpreeProductImage>(
spreeProduct, spreeSuccessResponse,
'images' spreeProduct,
) 'images'
)
const productImage = getMediaGallery( const productImage = getMediaGallery(
spreeProductImageRecords, spreeProductImageRecords,
@@ -110,36 +106,33 @@ const normalizeLineItem = (
spreeSuccessResponse: SpreeSdkResponse, spreeSuccessResponse: SpreeSdkResponse,
spreeLineItem: LineItemAttr spreeLineItem: LineItemAttr
): LineItem => { ): LineItem => {
const variantIdentifier = spreeLineItem.relationships.variant const variant = jsonApi.findSingleRelationshipDocument<VariantAttr>(
.data as RelationType
const variant = findIncluded(
spreeSuccessResponse, spreeSuccessResponse,
variantIdentifier.type, spreeLineItem,
variantIdentifier.id 'variant'
) )
if (variant === null) { if (variant === null) {
throw new MissingLineItemVariantError( throw new MissingLineItemVariantError(
`Couldn't find variant with id ${variantIdentifier.id}.` `Couldn't find variant for line item with id ${spreeLineItem.id}.`
) )
} }
const productIdentifier = variant.relationships.product.data as RelationType const product = jsonApi.findSingleRelationshipDocument<ProductAttr>(
const product = findIncluded<ProductAttr>(
spreeSuccessResponse, spreeSuccessResponse,
productIdentifier.type, variant,
productIdentifier.id 'product'
) )
if (product === null) { if (product === null) {
throw new MissingLineItemVariantError( throw new MissingLineItemVariantError(
`Couldn't find product with id ${productIdentifier.id}.` `Couldn't find product for variant with id ${variant.id}.`
) )
} }
const path = `/${product.attributes.slug}` const path = `/${product.attributes.slug}`
const spreeOptionValues = findIncludedOfType( const spreeOptionValues = jsonApi.findRelationshipDocuments(
spreeSuccessResponse, spreeSuccessResponse,
variant, variant,
'option_values' 'option_values'
@@ -147,18 +140,16 @@ const normalizeLineItem = (
const options: SelectedOption[] = spreeOptionValues.map( const options: SelectedOption[] = spreeOptionValues.map(
(spreeOptionValue) => { (spreeOptionValue) => {
const spreeOptionTypeIdentifier = spreeOptionValue.relationships const spreeOptionType =
.option_type.data as RelationType jsonApi.findSingleRelationshipDocument<OptionTypeAttr>(
spreeSuccessResponse,
const spreeOptionType = findIncluded( spreeOptionValue,
spreeSuccessResponse, 'option_type'
spreeOptionTypeIdentifier.type, )
spreeOptionTypeIdentifier.id
)
if (spreeOptionType === null) { if (spreeOptionType === null) {
throw new MissingLineItemVariantError( throw new MissingLineItemVariantError(
`Couldn't find option type with id ${spreeOptionTypeIdentifier.id}.` `Couldn't find option type of option value with id ${spreeOptionValue.id}.`
) )
} }
@@ -177,7 +168,7 @@ const normalizeLineItem = (
return { return {
id: spreeLineItem.id, id: spreeLineItem.id,
variantId: variant.id, variantId: variant.id,
productId: productIdentifier.id, productId: product.id,
name: spreeLineItem.attributes.name, name: spreeLineItem.attributes.name,
quantity: spreeLineItem.attributes.quantity, quantity: spreeLineItem.attributes.quantity,
discounts: [], // TODO: Implement when the template starts displaying them. discounts: [], // TODO: Implement when the template starts displaying them.
@@ -191,11 +182,13 @@ const normalizeCart = (
spreeSuccessResponse: SpreeSdkResponse, spreeSuccessResponse: SpreeSdkResponse,
spreeCart: OrderAttr spreeCart: OrderAttr
): Cart => { ): Cart => {
const lineItems = findIncludedOfType( const lineItems = jsonApi
spreeSuccessResponse, .findRelationshipDocuments<LineItemAttr>(
spreeCart, spreeSuccessResponse,
'line_items' spreeCart,
).map((lineItem) => normalizeLineItem(spreeSuccessResponse, lineItem)) 'line_items'
)
.map((lineItem) => normalizeLineItem(spreeSuccessResponse, lineItem))
return { return {
id: spreeCart.id, id: spreeCart.id,

View File

@@ -5,20 +5,16 @@ import type {
ProductPrice, ProductPrice,
ProductVariant, ProductVariant,
} from '@commerce/types/product' } from '@commerce/types/product'
import type {
JsonApiListResponse,
JsonApiSingleResponse,
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
import type { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product' import type { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships' import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
import { requireConfigValue } from '../isomorphic-config' import { requireConfigValue } from '../isomorphic-config'
import createGetAbsoluteImageUrl from './create-get-absolute-image-url' import createGetAbsoluteImageUrl from './create-get-absolute-image-url'
import expandOptions from './expand-options' import expandOptions from './expand-options'
import getMediaGallery from './get-media-gallery' import getMediaGallery from './get-media-gallery'
import { findIncluded, findIncludedOfType } from './find-json-api-documents'
import getProductPath from './get-product-path' import getProductPath from './get-product-path'
import MissingPrimaryVariantError from '../errors/MissingPrimaryVariantError' import MissingPrimaryVariantError from '../errors/MissingPrimaryVariantError'
import type { SpreeSdkResponse } from '@framework/types' import type { SpreeSdkResponse, VariantAttr } from '@framework/types'
import { jsonApi } from '@spree/storefront-api-v2-sdk'
const placeholderImage = requireConfigValue('productPlaceholderImageUrl') as const placeholderImage = requireConfigValue('productPlaceholderImageUrl') as
| string | string
@@ -28,23 +24,21 @@ const normalizeProduct = (
spreeSuccessResponse: SpreeSdkResponse, spreeSuccessResponse: SpreeSdkResponse,
spreeProduct: ProductAttr spreeProduct: ProductAttr
): Product => { ): Product => {
const primaryVariantIdentifier = spreeProduct.relationships.primary_variant const primaryVariant = jsonApi.findSingleRelationshipDocument<VariantAttr>(
.data as RelationType
const primaryVariant = findIncluded(
spreeSuccessResponse, spreeSuccessResponse,
primaryVariantIdentifier.type, spreeProduct,
primaryVariantIdentifier.id 'primary_variant'
) )
if (primaryVariant === null) { if (primaryVariant === null) {
throw new MissingPrimaryVariantError( throw new MissingPrimaryVariantError(
`Couldn't find primary variant with id ${primaryVariantIdentifier.id}.` `Couldn't find primary variant for product with id ${spreeProduct.id}.`
) )
} }
const sku = primaryVariant.attributes.sku const sku = primaryVariant.attributes.sku
const spreeImageRecords = findIncludedOfType( const spreeImageRecords = jsonApi.findRelationshipDocuments(
spreeSuccessResponse, spreeSuccessResponse,
spreeProduct, spreeProduct,
'images' 'images'
@@ -77,7 +71,7 @@ const normalizeProduct = (
let variants: ProductVariant[] let variants: ProductVariant[]
let options: ProductOption[] = [] let options: ProductOption[] = []
const spreeVariantRecords = findIncludedOfType( const spreeVariantRecords = jsonApi.findRelationshipDocuments(
spreeSuccessResponse, spreeSuccessResponse,
spreeProduct, spreeProduct,
'variants' 'variants'
@@ -87,7 +81,7 @@ const normalizeProduct = (
let variantOptions: ProductOption[] = [] let variantOptions: ProductOption[] = []
if (showOptions) { if (showOptions) {
const spreeOptionValues = findIncludedOfType( const spreeOptionValues = jsonApi.findRelationshipDocuments(
spreeSuccessResponse, spreeSuccessResponse,
spreeVariantRecord, spreeVariantRecord,
'option_values' 'option_values'

View File

@@ -21,7 +21,7 @@
}, },
"dependencies": { "dependencies": {
"@react-spring/web": "^9.2.1", "@react-spring/web": "^9.2.1",
"@spree/storefront-api-v2-sdk": "^4.7.1", "@spree/storefront-api-v2-sdk": "^4.9.0",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@vercel/fetch": "^6.1.0", "@vercel/fetch": "^6.1.0",
"autoprefixer": "^10.2.6", "autoprefixer": "^10.2.6",

View File

@@ -1066,12 +1066,12 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
"@spree/storefront-api-v2-sdk@^4.7.1": "@spree/storefront-api-v2-sdk@^4.9.0":
version "4.7.1" version "4.9.0"
resolved "https://registry.yarnpkg.com/@spree/storefront-api-v2-sdk/-/storefront-api-v2-sdk-4.7.1.tgz#af83c0d0de9ed9d3dade89ef96b251161c789b64" resolved "https://registry.yarnpkg.com/@spree/storefront-api-v2-sdk/-/storefront-api-v2-sdk-4.9.0.tgz#57aa40b5b880e039c6810c4be17043b6b6ec2a2a"
integrity sha512-Z4vt1UwTf7rYfo6UIpfnsidyOsiw043FTwLfyVHDqFnXKXaTG4E6uV75gKrM7+d5SScTWj5qlq6YRAfwL/WacA== integrity sha512-2PrwXx9VKnrB3G6lNbLJlHvwuQn52IxShys3wN413dHQ742w8uOh5yPWvnG/EifJTZs2ejB41Yb0XhqmPHAeXA==
dependencies: dependencies:
axios "^0.21.1" axios "^0.21.4"
lodash "^4.17.21" lodash "^4.17.21"
qs "^6.10.1" qs "^6.10.1"
optionalDependencies: optionalDependencies:
@@ -1636,12 +1636,12 @@ axe-core@^4.0.2:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.2.tgz#fcf8777b82c62cfc69c7e9f32c0d2226287680e7" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.2.tgz#fcf8777b82c62cfc69c7e9f32c0d2226287680e7"
integrity sha512-5LMaDRWm8ZFPAEdzTYmgjjEdj1YnQcpfrVajO/sn/LhbpGp0Y0H64c2hLZI1gRMxfA+w1S71Uc/nHaOXgcCvGg== integrity sha512-5LMaDRWm8ZFPAEdzTYmgjjEdj1YnQcpfrVajO/sn/LhbpGp0Y0H64c2hLZI1gRMxfA+w1S71Uc/nHaOXgcCvGg==
axios@^0.21.1: axios@^0.21.4:
version "0.21.1" version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies: dependencies:
follow-redirects "^1.10.0" follow-redirects "^1.14.0"
axobject-query@^2.2.0: axobject-query@^2.2.0:
version "2.2.0" version "2.2.0"
@@ -3197,10 +3197,10 @@ flatten@^1.0.2:
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
follow-redirects@^1.10.0: follow-redirects@^1.14.0:
version "1.14.2" version "1.14.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.2.tgz#cecb825047c00f5e66b142f90fed4f515dec789b" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e"
integrity sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA== integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==
foreach@^2.0.5: foreach@^2.0.5:
version "2.0.5" version "2.0.5"