mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 20:26:49 +00:00
Allow filtering variant images by option type
This commit is contained in:
@@ -17,3 +17,4 @@ NEXT_PUBLIC_SPREE_SHOW_SINGLE_VARIANT_OPTIONS=false
|
|||||||
NEXT_PUBLIC_SPREE_LAST_UPDATED_PRODUCTS_PRERENDER_COUNT=10
|
NEXT_PUBLIC_SPREE_LAST_UPDATED_PRODUCTS_PRERENDER_COUNT=10
|
||||||
NEXT_PUBLIC_SPREE_PRODUCT_PLACEHOLDER_IMAGE_URL=/product-img-placeholder.svg
|
NEXT_PUBLIC_SPREE_PRODUCT_PLACEHOLDER_IMAGE_URL=/product-img-placeholder.svg
|
||||||
NEXT_PUBLIC_SPREE_LINE_ITEM_PLACEHOLDER_IMAGE_URL=/product-img-placeholder.svg
|
NEXT_PUBLIC_SPREE_LINE_ITEM_PLACEHOLDER_IMAGE_URL=/product-img-placeholder.svg
|
||||||
|
NEXT_PUBLIC_SPREE_IMAGES_OPTION_FILTER=false
|
||||||
|
1
framework/spree/errors/MissingOptionTypeError.ts
Normal file
1
framework/spree/errors/MissingOptionTypeError.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default class MissingOptionTypeError extends Error {}
|
1
framework/spree/errors/MissingOptionValueError.ts
Normal file
1
framework/spree/errors/MissingOptionValueError.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default class MissingOptionValueError extends Error {}
|
@@ -1,9 +1,10 @@
|
|||||||
import forceIsomorphicConfigValues from './utils/force-isomorphic-config-values'
|
import forceIsomorphicConfigValues from './utils/force-isomorphic-config-values'
|
||||||
import requireConfig from './utils/require-config'
|
import requireConfig from './utils/require-config'
|
||||||
import validateAllProductsTaxonomyId from './utils/validate-all-products-taxonomy-id'
|
import validateAllProductsTaxonomyId from './utils/validations/validate-all-products-taxonomy-id'
|
||||||
import validateCookieExpire from './utils/validate-cookie-expire'
|
import validateCookieExpire from './utils/validations/validate-cookie-expire'
|
||||||
import validatePlaceholderImageUrl from './utils/validate-placeholder-image-url'
|
import validateImagesOptionFilter from './utils/validations/validate-images-option-filter'
|
||||||
import validateProductsPrerenderCount from './utils/validate-products-prerender-count'
|
import validatePlaceholderImageUrl from './utils/validations/validate-placeholder-image-url'
|
||||||
|
import validateProductsPrerenderCount from './utils/validations/validate-products-prerender-count'
|
||||||
|
|
||||||
const isomorphicConfig = {
|
const isomorphicConfig = {
|
||||||
apiHost: process.env.NEXT_PUBLIC_SPREE_API_HOST,
|
apiHost: process.env.NEXT_PUBLIC_SPREE_API_HOST,
|
||||||
@@ -31,6 +32,9 @@ const isomorphicConfig = {
|
|||||||
lineItemPlaceholderImageUrl: validatePlaceholderImageUrl(
|
lineItemPlaceholderImageUrl: validatePlaceholderImageUrl(
|
||||||
process.env.NEXT_PUBLIC_SPREE_LINE_ITEM_PLACEHOLDER_IMAGE_URL
|
process.env.NEXT_PUBLIC_SPREE_LINE_ITEM_PLACEHOLDER_IMAGE_URL
|
||||||
),
|
),
|
||||||
|
imagesOptionFilter: validateImagesOptionFilter(
|
||||||
|
process.env.NEXT_PUBLIC_SPREE_IMAGES_OPTION_FILTER
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
export default forceIsomorphicConfigValues(
|
export default forceIsomorphicConfigValues(
|
||||||
@@ -49,6 +53,7 @@ export default forceIsomorphicConfigValues(
|
|||||||
'lastUpdatedProductsPrerenderCount',
|
'lastUpdatedProductsPrerenderCount',
|
||||||
'productPlaceholderImageUrl',
|
'productPlaceholderImageUrl',
|
||||||
'lineItemPlaceholderImageUrl',
|
'lineItemPlaceholderImageUrl',
|
||||||
|
'imagesOptionFilter',
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -13,13 +13,20 @@ import expandOptions from './expand-options'
|
|||||||
import getMediaGallery from './get-media-gallery'
|
import getMediaGallery from './get-media-gallery'
|
||||||
import getProductPath from './get-product-path'
|
import getProductPath from './get-product-path'
|
||||||
import MissingPrimaryVariantError from '../errors/MissingPrimaryVariantError'
|
import MissingPrimaryVariantError from '../errors/MissingPrimaryVariantError'
|
||||||
|
import MissingOptionTypeError from '../errors/MissingOptionTypeError'
|
||||||
|
import MissingOptionValueError from '../errors/MissingOptionValueError'
|
||||||
import type { SpreeSdkResponse, VariantAttr } from '@framework/types'
|
import type { SpreeSdkResponse, VariantAttr } from '@framework/types'
|
||||||
import { jsonApi } from '@spree/storefront-api-v2-sdk'
|
import { jsonApi } from '@spree/storefront-api-v2-sdk'
|
||||||
|
import { JsonApiDocument } from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
|
||||||
|
|
||||||
const placeholderImage = requireConfigValue('productPlaceholderImageUrl') as
|
const placeholderImage = requireConfigValue('productPlaceholderImageUrl') as
|
||||||
| string
|
| string
|
||||||
| false
|
| false
|
||||||
|
|
||||||
|
const imagesOptionFilter = requireConfigValue('imagesOptionFilter') as
|
||||||
|
| string
|
||||||
|
| false
|
||||||
|
|
||||||
const normalizeProduct = (
|
const normalizeProduct = (
|
||||||
spreeSuccessResponse: SpreeSdkResponse,
|
spreeSuccessResponse: SpreeSdkResponse,
|
||||||
spreeProduct: ProductAttr
|
spreeProduct: ProductAttr
|
||||||
@@ -38,24 +45,6 @@ const normalizeProduct = (
|
|||||||
|
|
||||||
const sku = primaryVariant.attributes.sku
|
const sku = primaryVariant.attributes.sku
|
||||||
|
|
||||||
const spreeImageRecords = jsonApi.findRelationshipDocuments(
|
|
||||||
spreeSuccessResponse,
|
|
||||||
spreeProduct,
|
|
||||||
'images'
|
|
||||||
)
|
|
||||||
|
|
||||||
const productImages = getMediaGallery(
|
|
||||||
spreeImageRecords,
|
|
||||||
createGetAbsoluteImageUrl(requireConfigValue('imageHost') as string)
|
|
||||||
)
|
|
||||||
|
|
||||||
const images: ProductImage[] =
|
|
||||||
productImages.length === 0
|
|
||||||
? placeholderImage === false
|
|
||||||
? []
|
|
||||||
: [{ url: placeholderImage }]
|
|
||||||
: productImages
|
|
||||||
|
|
||||||
const price: ProductPrice = {
|
const price: ProductPrice = {
|
||||||
value: parseFloat(spreeProduct.attributes.price),
|
value: parseFloat(spreeProduct.attributes.price),
|
||||||
currencyCode: spreeProduct.attributes.currency,
|
currencyCode: spreeProduct.attributes.currency,
|
||||||
@@ -106,6 +95,113 @@ const normalizeProduct = (
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const spreePrimaryVariantImageRecords = jsonApi.findRelationshipDocuments(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
primaryVariant,
|
||||||
|
'images'
|
||||||
|
)
|
||||||
|
|
||||||
|
let spreeVariantImageRecords: JsonApiDocument[]
|
||||||
|
|
||||||
|
if (imagesOptionFilter === false) {
|
||||||
|
spreeVariantImageRecords = spreeVariantRecords.reduce<JsonApiDocument[]>(
|
||||||
|
(accumulatedImageRecords, spreeVariantRecord) => {
|
||||||
|
return [
|
||||||
|
...accumulatedImageRecords,
|
||||||
|
...jsonApi.findRelationshipDocuments(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
spreeVariantRecord,
|
||||||
|
'images'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const spreeOptionTypes = jsonApi.findRelationshipDocuments(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
spreeProduct,
|
||||||
|
'option_types'
|
||||||
|
)
|
||||||
|
|
||||||
|
const imagesFilterOptionType = spreeOptionTypes.find(
|
||||||
|
(spreeOptionType) =>
|
||||||
|
spreeOptionType.attributes.name === imagesOptionFilter
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!imagesFilterOptionType) {
|
||||||
|
throw new MissingOptionTypeError(
|
||||||
|
`Couldn't find option type having name ${imagesOptionFilter}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const imagesOptionTypeFilterId = imagesFilterOptionType.id
|
||||||
|
const includedOptionValuesImagesIds: string[] = []
|
||||||
|
|
||||||
|
spreeVariantImageRecords = spreeVariantRecords.reduce<JsonApiDocument[]>(
|
||||||
|
(accumulatedImageRecords, spreeVariantRecord) => {
|
||||||
|
const spreeVariantOptionValuesIdentifiers: RelationType[] =
|
||||||
|
spreeVariantRecord.relationships.option_values.data
|
||||||
|
|
||||||
|
const spreeOptionValueOfFilterTypeIdentifier =
|
||||||
|
spreeVariantOptionValuesIdentifiers.find(
|
||||||
|
(spreeVariantOptionValuesIdentifier: RelationType) =>
|
||||||
|
imagesFilterOptionType.relationships.option_values.data.some(
|
||||||
|
(filterOptionTypeValueIdentifier: RelationType) =>
|
||||||
|
filterOptionTypeValueIdentifier.id ===
|
||||||
|
spreeVariantOptionValuesIdentifier.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!spreeOptionValueOfFilterTypeIdentifier) {
|
||||||
|
throw new MissingOptionValueError(
|
||||||
|
`Couldn't find option value related to option type with id ${imagesOptionTypeFilterId}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const optionValueImagesAlreadyIncluded =
|
||||||
|
includedOptionValuesImagesIds.includes(
|
||||||
|
spreeOptionValueOfFilterTypeIdentifier.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (optionValueImagesAlreadyIncluded) {
|
||||||
|
return accumulatedImageRecords
|
||||||
|
}
|
||||||
|
|
||||||
|
includedOptionValuesImagesIds.push(
|
||||||
|
spreeOptionValueOfFilterTypeIdentifier.id
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
...accumulatedImageRecords,
|
||||||
|
...jsonApi.findRelationshipDocuments(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
spreeVariantRecord,
|
||||||
|
'images'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const spreeImageRecords = [
|
||||||
|
...spreePrimaryVariantImageRecords,
|
||||||
|
...spreeVariantImageRecords,
|
||||||
|
]
|
||||||
|
|
||||||
|
const productImages = getMediaGallery(
|
||||||
|
spreeImageRecords,
|
||||||
|
createGetAbsoluteImageUrl(requireConfigValue('imageHost') as string)
|
||||||
|
)
|
||||||
|
|
||||||
|
const images: ProductImage[] =
|
||||||
|
productImages.length === 0
|
||||||
|
? placeholderImage === false
|
||||||
|
? []
|
||||||
|
: [{ url: placeholderImage }]
|
||||||
|
: productImages
|
||||||
|
|
||||||
const slug = spreeProduct.attributes.slug
|
const slug = spreeProduct.attributes.slug
|
||||||
const path = getProductPath(spreeProduct)
|
const path = getProductPath(spreeProduct)
|
||||||
|
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
const validateImagesOptionFilter = (
|
||||||
|
optionTypeNameOrFalse: unknown
|
||||||
|
): string | false => {
|
||||||
|
if (!optionTypeNameOrFalse || optionTypeNameOrFalse === 'false') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof optionTypeNameOrFalse === 'string') {
|
||||||
|
return optionTypeNameOrFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TypeError('optionTypeNameOrFalse must be a string or falsy.')
|
||||||
|
}
|
||||||
|
|
||||||
|
export default validateImagesOptionFilter
|
Reference in New Issue
Block a user