diff --git a/framework/spree/.env.template b/framework/spree/.env.template index 665fefba0..7d2b52026 100644 --- a/framework/spree/.env.template +++ b/framework/spree/.env.template @@ -14,3 +14,5 @@ NEXT_PUBLIC_SPREE_CATEGORIES_TAXONOMY_ID=1 NEXT_PUBLIC_SPREE_BRANDS_TAXONOMY_ID=27 NEXT_PUBLIC_SPREE_SHOW_SINGLE_VARIANT_OPTIONS=false NEXT_PUBLIC_SPREE_LAST_UPDATED_PRODUCTS_PRERENDER_COUNT=10 +NEXT_PUBLIC_SPREE_PRODUCT_PLACEHOLDER_IMAGE_URL=/product-img-placeholder.svg +NEXT_PUBLIC_SPREE_LINE_ITEM_PLACEHOLDER_IMAGE_URL=/product-img-placeholder.svg diff --git a/framework/spree/isomorphic-config.ts b/framework/spree/isomorphic-config.ts index 6255b1535..b665fcd4f 100644 --- a/framework/spree/isomorphic-config.ts +++ b/framework/spree/isomorphic-config.ts @@ -1,6 +1,7 @@ import forceIsomorphicConfigValues from './utils/force-isomorphic-config-values' import requireConfig from './utils/require-config' import validateCookieExpire from './utils/validate-cookie-expire' +import validatePlaceholderImageUrl from './utils/validate-placeholder-image-url' import validateProductsPrerenderCount from './utils/validate-products-prerender-count' const isomorphicConfig = { @@ -18,6 +19,12 @@ const isomorphicConfig = { lastUpdatedProductsPrerenderCount: validateProductsPrerenderCount( process.env.NEXT_PUBLIC_SPREE_LAST_UPDATED_PRODUCTS_PRERENDER_COUNT ), + productPlaceholderImageUrl: validatePlaceholderImageUrl( + process.env.NEXT_PUBLIC_SPREE_PRODUCT_PLACEHOLDER_IMAGE_URL + ), + lineItemPlaceholderImageUrl: validatePlaceholderImageUrl( + process.env.NEXT_PUBLIC_SPREE_LINE_ITEM_PLACEHOLDER_IMAGE_URL + ), } export default forceIsomorphicConfigValues( @@ -33,6 +40,8 @@ export default forceIsomorphicConfigValues( 'brandsTaxonomyId', 'showSingleVariantOptions', 'lastUpdatedProductsPrerenderCount', + 'productPlaceholderImageUrl', + 'lineItemPlaceholderImageUrl', ] ) diff --git a/framework/spree/utils/normalize-cart.ts b/framework/spree/utils/normalize-cart.ts index 47489193c..3d0f8f9eb 100644 --- a/framework/spree/utils/normalize-cart.ts +++ b/framework/spree/utils/normalize-cart.ts @@ -21,6 +21,11 @@ import type { OptionTypeAttr, VariantAttr, } from '@framework/types' +import type { Image } from '@commerce/types/common' + +const placeholderImage = requireConfigValue('lineItemPlaceholderImageUrl') as + | string + | false const isColorProductOption = (productOptionType: OptionTypeAttr) => { return productOptionType.attributes.presentation === 'Color' @@ -74,6 +79,10 @@ const normalizeVariant = ( lineItemImage = productImage } + const image: Image = + lineItemImage ?? + (placeholderImage === false ? undefined : { url: placeholderImage }) + return { id: spreeVariant.id, sku: spreeVariant.attributes.sku, @@ -81,7 +90,7 @@ const normalizeVariant = ( requiresShipping: true, price: parseFloat(spreeVariant.attributes.price), listPrice: parseFloat(spreeVariant.attributes.price), - image: lineItemImage, + image, isInStock: spreeVariant.attributes.in_stock, availableForSale: spreeVariant.attributes.purchasable, ...(spreeVariant.attributes.weight === '0.0' diff --git a/framework/spree/utils/normalize-product.ts b/framework/spree/utils/normalize-product.ts index d390e8d9a..28325943e 100644 --- a/framework/spree/utils/normalize-product.ts +++ b/framework/spree/utils/normalize-product.ts @@ -1,5 +1,6 @@ import type { Product, + ProductImage, ProductOption, ProductPrice, ProductVariant, @@ -18,6 +19,10 @@ import { findIncluded, findIncludedOfType } from './find-json-api-documents' import getProductPath from './get-product-path' import MissingPrimaryVariantError from '@framework/errors/MissingPrimaryVariantError' +const placeholderImage = requireConfigValue('productPlaceholderImageUrl') as + | string + | false + const normalizeProduct = ( spreeSuccessResponse: JsonApiSingleResponse | JsonApiListResponse, spreeProduct: ProductAttr @@ -44,11 +49,18 @@ const normalizeProduct = ( 'images' ) - const images = getMediaGallery( + const productImages = getMediaGallery( spreeImageRecords, createGetAbsoluteImageUrl(requireConfigValue('imageHost') as string) ) + const images: ProductImage[] = + productImages.length === 0 + ? placeholderImage === false + ? [] + : [{ url: placeholderImage }] + : productImages + const price: ProductPrice = { value: parseFloat(spreeProduct.attributes.price), currencyCode: spreeProduct.attributes.currency, diff --git a/framework/spree/utils/validate-placeholder-image-url.ts b/framework/spree/utils/validate-placeholder-image-url.ts new file mode 100644 index 000000000..cce2e27da --- /dev/null +++ b/framework/spree/utils/validate-placeholder-image-url.ts @@ -0,0 +1,15 @@ +const validatePlaceholderImageUrl = ( + placeholderUrlOrFalse: unknown +): string | false => { + if (!placeholderUrlOrFalse || placeholderUrlOrFalse === 'false') { + return false + } + + if (typeof placeholderUrlOrFalse === 'string') { + return placeholderUrlOrFalse + } + + throw new TypeError('placeholderUrlOrFalse must be a string or falsy.') +} + +export default validatePlaceholderImageUrl