mirror of
https://github.com/vercel/commerce.git
synced 2025-07-03 19:51:22 +00:00
Fetch single product during build time
This commit is contained in:
parent
24e635b048
commit
a7a75e7f69
@ -6,6 +6,8 @@ export type GetAllProductPathsResult = {
|
||||
|
||||
export default function getAllProductPathsOperation() {
|
||||
function getAllProductPaths(): Promise<GetAllProductPathsResult> {
|
||||
console.log('getAllProductPaths called.')
|
||||
|
||||
return Promise.resolve({
|
||||
// products: data.products.map(({ path }) => ({ path })),
|
||||
// TODO: Return Storefront [{ path: '/long-sleeve-shirt' }, ...] from Spree products. Paths using product IDs are fine too.
|
||||
|
@ -1,33 +1,38 @@
|
||||
import type {
|
||||
Product,
|
||||
ProductOption,
|
||||
ProductOptionValues,
|
||||
ProductPrice,
|
||||
ProductVariant,
|
||||
} from '@commerce/types/product'
|
||||
import type { Product } from '@commerce/types/product'
|
||||
import type { GetAllProductsOperation } from '@commerce/types/product'
|
||||
import type { OperationContext } from '@commerce/api/operations'
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@commerce/api/operations'
|
||||
import type { IProducts } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
|
||||
import { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
|
||||
import type { SpreeApiConfig, SpreeApiProvider } from '../index'
|
||||
import type { SpreeSdkVariables } from 'framework/spree/types'
|
||||
import { findIncluded, findIncludedOfType } from 'framework/spree/utils/jsonApi'
|
||||
import getMediaGallery from 'framework/spree/utils/getMediaGallery'
|
||||
import createGetAbsoluteImageUrl from 'framework/spree/utils/createGetAbsoluteImageUrl'
|
||||
import { requireConfigValue } from 'framework/spree/isomorphicConfig'
|
||||
import SpreeResponseContentError from 'framework/spree/errors/SpreeResponseContentError'
|
||||
import expandOptions from 'framework/spree/utils/expandOptions'
|
||||
import normalizeProduct from 'framework/spree/utils/normalizeProduct'
|
||||
|
||||
export default function getAllProductsOperation({
|
||||
commerce,
|
||||
}: OperationContext<SpreeApiProvider>) {
|
||||
async function getAllProducts<T extends GetAllProductsOperation>(opts?: {
|
||||
variables?: T['variables']
|
||||
config?: Partial<SpreeApiConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getAllProducts<T extends GetAllProductsOperation>(
|
||||
opts: {
|
||||
variables?: T['variables']
|
||||
config?: Partial<SpreeApiConfig>
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getAllProducts<T extends GetAllProductsOperation>({
|
||||
variables: getAllProductsVariables = {},
|
||||
config: userConfig,
|
||||
}: {
|
||||
variables?: T['variables']
|
||||
config?: Partial<SpreeApiConfig>
|
||||
} = {}): Promise<{ products: Product[] | any[] }> {
|
||||
} = {}): Promise<{ products: Product[] }> {
|
||||
console.info(
|
||||
'getAllProducts called. Configuration: ',
|
||||
'getAllProductsVariables: ',
|
||||
@ -56,89 +61,7 @@ export default function getAllProductsOperation({
|
||||
)
|
||||
|
||||
const normalizedProducts: Product[] = spreeSuccessResponse.data.map(
|
||||
(spreeProduct) => {
|
||||
const spreeImageRecords = findIncludedOfType(
|
||||
spreeSuccessResponse,
|
||||
spreeProduct,
|
||||
'images'
|
||||
)
|
||||
|
||||
const images = getMediaGallery(
|
||||
spreeImageRecords,
|
||||
createGetAbsoluteImageUrl(requireConfigValue('spreeImageHost'))
|
||||
)
|
||||
|
||||
const price: ProductPrice = {
|
||||
value: parseFloat(spreeProduct.attributes.price),
|
||||
currencyCode: spreeProduct.attributes.currency,
|
||||
}
|
||||
|
||||
// TODO: Add sku to product object equal to master SKU from Spree.
|
||||
// Currently, the Spree API doesn't return it.
|
||||
|
||||
const hasNonMasterVariants =
|
||||
(spreeProduct.relationships.variants.data as RelationType[]).length >
|
||||
0
|
||||
|
||||
let variants: ProductVariant[]
|
||||
let options: ProductOption[] = []
|
||||
|
||||
if (hasNonMasterVariants) {
|
||||
const spreeVariantRecords = findIncludedOfType(
|
||||
spreeSuccessResponse,
|
||||
spreeProduct,
|
||||
'variants'
|
||||
)
|
||||
|
||||
variants = spreeVariantRecords.map((spreeVariantRecord) => {
|
||||
const spreeOptionValues = findIncludedOfType(
|
||||
spreeSuccessResponse,
|
||||
spreeVariantRecord,
|
||||
'option_values'
|
||||
)
|
||||
|
||||
let variantOptions: ProductOption[] = []
|
||||
|
||||
// Only include options which are used by variants.
|
||||
|
||||
spreeOptionValues.forEach((spreeOptionValue) => {
|
||||
variantOptions = expandOptions(
|
||||
spreeSuccessResponse,
|
||||
spreeOptionValue,
|
||||
variantOptions
|
||||
)
|
||||
|
||||
options = expandOptions(
|
||||
spreeSuccessResponse,
|
||||
spreeOptionValue,
|
||||
options
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
id: spreeVariantRecord.id,
|
||||
options: variantOptions,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
variants = []
|
||||
}
|
||||
|
||||
const slug = spreeProduct.attributes.slug
|
||||
const path = `/${spreeProduct.attributes.slug}`
|
||||
|
||||
return {
|
||||
id: spreeProduct.id,
|
||||
name: spreeProduct.attributes.name,
|
||||
description: spreeProduct.attributes.description,
|
||||
images,
|
||||
variants,
|
||||
options,
|
||||
price,
|
||||
slug,
|
||||
path,
|
||||
}
|
||||
}
|
||||
(spreeProduct) => normalizeProduct(spreeSuccessResponse, spreeProduct)
|
||||
)
|
||||
|
||||
return { products: normalizedProducts }
|
||||
|
@ -1,26 +1,76 @@
|
||||
import type { LocalConfig } from '../index'
|
||||
import { Product } from '@commerce/types/product'
|
||||
import { GetProductOperation } from '@commerce/types/product'
|
||||
import data from '../../../local/data.json'
|
||||
import type { OperationContext } from '@commerce/api/operations'
|
||||
import type { SpreeApiConfig, SpreeApiProvider } from '../index'
|
||||
import type { GetProductOperation } from '@commerce/types/product'
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@commerce/api/operations'
|
||||
import type { IProduct } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
|
||||
import type { SpreeSdkVariables } from 'framework/spree/types'
|
||||
import MissingSlugVariableError from 'framework/spree/errors/MissingSlugVariableError'
|
||||
import normalizeProduct from 'framework/spree/utils/normalizeProduct'
|
||||
|
||||
export default function getProductOperation({
|
||||
commerce,
|
||||
}: OperationContext<any>) {
|
||||
}: OperationContext<SpreeApiProvider>) {
|
||||
async function getProduct<T extends GetProductOperation>(opts: {
|
||||
variables: T['variables']
|
||||
config?: Partial<SpreeApiConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getProduct<T extends GetProductOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: Partial<SpreeApiConfig>
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getProduct<T extends GetProductOperation>({
|
||||
query = '',
|
||||
variables,
|
||||
config,
|
||||
variables: getProductVariables,
|
||||
config: userConfig,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: T['variables']
|
||||
config?: Partial<LocalConfig>
|
||||
config?: Partial<SpreeApiConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<Product | {} | any> {
|
||||
}): Promise<T['data']> {
|
||||
console.log(
|
||||
'getProduct called. Configuration: ',
|
||||
'getProductVariables: ',
|
||||
getProductVariables,
|
||||
'config: ',
|
||||
userConfig
|
||||
)
|
||||
|
||||
if (!getProductVariables?.slug) {
|
||||
throw new MissingSlugVariableError()
|
||||
}
|
||||
|
||||
const variables: SpreeSdkVariables = {
|
||||
methodPath: 'products.show',
|
||||
arguments: [
|
||||
getProductVariables.slug,
|
||||
{
|
||||
include: 'variants,images,option_types,variants.option_values',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const config = commerce.getConfig(userConfig)
|
||||
const { fetch: apiFetch } = config // TODO: Send config.locale to Spree.
|
||||
|
||||
const { data: spreeSuccessResponse } = await apiFetch<IProduct>(
|
||||
'__UNUSED__',
|
||||
{ variables }
|
||||
)
|
||||
|
||||
return {
|
||||
product: data.products.find(({ slug }) => slug === variables!.slug),
|
||||
// TODO: Return Spree product.
|
||||
// product: {},
|
||||
product: normalizeProduct(
|
||||
spreeSuccessResponse,
|
||||
spreeSuccessResponse.data
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
1
framework/spree/errors/MissingSlugVariableError.ts
Normal file
1
framework/spree/errors/MissingSlugVariableError.ts
Normal file
@ -0,0 +1 @@
|
||||
export default class MissingSlugVariableError extends Error {}
|
@ -2,8 +2,10 @@ import type {
|
||||
ProductOption,
|
||||
ProductOptionValues,
|
||||
} from '@commerce/types/product'
|
||||
import type { JsonApiDocument } from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
|
||||
import type { IProducts } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
|
||||
import type {
|
||||
JsonApiDocument,
|
||||
JsonApiResponse,
|
||||
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
|
||||
import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
|
||||
import SpreeResponseContentError from '../errors/SpreeResponseContentError'
|
||||
import { findIncluded } from './jsonApi'
|
||||
@ -12,7 +14,7 @@ const isColorProductOption = (productOption: ProductOption) =>
|
||||
productOption.displayName === 'Color'
|
||||
|
||||
const expandOptions = (
|
||||
spreeSuccessResponse: IProducts,
|
||||
spreeSuccessResponse: JsonApiResponse,
|
||||
spreeOptionValue: JsonApiDocument,
|
||||
accumulatedOptions: ProductOption[]
|
||||
): ProductOption[] => {
|
||||
|
100
framework/spree/utils/normalizeProduct.ts
Normal file
100
framework/spree/utils/normalizeProduct.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import type {
|
||||
ProductOption,
|
||||
ProductPrice,
|
||||
ProductVariant,
|
||||
} from '@commerce/types/product'
|
||||
import type {
|
||||
JsonApiListResponse,
|
||||
JsonApiResponse,
|
||||
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
|
||||
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 { requireConfigValue } from '../isomorphicConfig'
|
||||
import createGetAbsoluteImageUrl from './createGetAbsoluteImageUrl'
|
||||
import expandOptions from './expandOptions'
|
||||
import getMediaGallery from './getMediaGallery'
|
||||
import { findIncludedOfType } from './jsonApi'
|
||||
|
||||
const normalizeProduct = (
|
||||
spreeSuccessResponse: JsonApiResponse | JsonApiListResponse,
|
||||
spreeProduct: ProductAttr
|
||||
) => {
|
||||
const spreeImageRecords = findIncludedOfType(
|
||||
spreeSuccessResponse,
|
||||
spreeProduct,
|
||||
'images'
|
||||
)
|
||||
|
||||
const images = getMediaGallery(
|
||||
spreeImageRecords,
|
||||
createGetAbsoluteImageUrl(requireConfigValue('spreeImageHost'))
|
||||
)
|
||||
|
||||
const price: ProductPrice = {
|
||||
value: parseFloat(spreeProduct.attributes.price),
|
||||
currencyCode: spreeProduct.attributes.currency,
|
||||
}
|
||||
|
||||
// TODO: Add sku to product object equal to master SKU from Spree.
|
||||
// Currently, the Spree API doesn't return it.
|
||||
|
||||
const hasNonMasterVariants =
|
||||
(spreeProduct.relationships.variants.data as RelationType[]).length > 0
|
||||
|
||||
let variants: ProductVariant[]
|
||||
let options: ProductOption[] = []
|
||||
|
||||
if (hasNonMasterVariants) {
|
||||
const spreeVariantRecords = findIncludedOfType(
|
||||
spreeSuccessResponse,
|
||||
spreeProduct,
|
||||
'variants'
|
||||
)
|
||||
|
||||
variants = spreeVariantRecords.map((spreeVariantRecord) => {
|
||||
const spreeOptionValues = findIncludedOfType(
|
||||
spreeSuccessResponse,
|
||||
spreeVariantRecord,
|
||||
'option_values'
|
||||
)
|
||||
|
||||
let variantOptions: ProductOption[] = []
|
||||
|
||||
// Only include options which are used by variants.
|
||||
|
||||
spreeOptionValues.forEach((spreeOptionValue) => {
|
||||
variantOptions = expandOptions(
|
||||
spreeSuccessResponse,
|
||||
spreeOptionValue,
|
||||
variantOptions
|
||||
)
|
||||
|
||||
options = expandOptions(spreeSuccessResponse, spreeOptionValue, options)
|
||||
})
|
||||
|
||||
return {
|
||||
id: spreeVariantRecord.id,
|
||||
options: variantOptions,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
variants = []
|
||||
}
|
||||
|
||||
const slug = spreeProduct.attributes.slug
|
||||
const path = `/${spreeProduct.attributes.slug}`
|
||||
|
||||
return {
|
||||
id: spreeProduct.id,
|
||||
name: spreeProduct.attributes.name,
|
||||
description: spreeProduct.attributes.description,
|
||||
images,
|
||||
variants,
|
||||
options,
|
||||
price,
|
||||
slug,
|
||||
path,
|
||||
}
|
||||
}
|
||||
|
||||
export default normalizeProduct
|
Loading…
x
Reference in New Issue
Block a user