mirror of
https://github.com/vercel/commerce.git
synced 2025-07-03 19:51:22 +00:00
Add configuration to show product options when there's one variant available
This commit is contained in:
parent
2c4e2e4cb4
commit
744a8b998e
@ -10,3 +10,4 @@ NEXT_PUBLIC_SPREE_IMAGE_HOST=http://localhost:3000
|
||||
NEXT_PUBLIC_SPREE_ALLOWED_IMAGE_DOMAIN=localhost
|
||||
NEXT_PUBLIC_SPREE_CATEGORIES_TAXONOMY_ID=1
|
||||
NEXT_PUBLIC_SPREE_BRANDS_TAXONOMY_ID=27
|
||||
NEXT_PUBLIC_SHOW_SINGLE_VARIANT_OPTIONS=false
|
||||
|
@ -55,10 +55,9 @@ export default function getAllProductsOperation({
|
||||
const config = commerce.getConfig(userConfig)
|
||||
const { fetch: apiFetch } = config // TODO: Send config.locale to Spree.
|
||||
|
||||
const { data: spreeSuccessResponse } = await apiFetch<IProducts>(
|
||||
'__UNUSED__',
|
||||
{ variables }
|
||||
)
|
||||
const {
|
||||
data: { data: spreeSuccessResponse },
|
||||
} = await apiFetch<{ data: IProducts }>('__UNUSED__', { variables })
|
||||
|
||||
const normalizedProducts: Product[] = spreeSuccessResponse.data.map(
|
||||
(spreeProduct) => normalizeProduct(spreeSuccessResponse, spreeProduct)
|
||||
|
@ -61,10 +61,9 @@ export default function getProductOperation({
|
||||
const config = commerce.getConfig(userConfig)
|
||||
const { fetch: apiFetch } = config // TODO: Send config.locale to Spree.
|
||||
|
||||
const { data: spreeSuccessResponse } = await apiFetch<IProduct>(
|
||||
'__UNUSED__',
|
||||
{ variables }
|
||||
)
|
||||
const {
|
||||
data: { data: spreeSuccessResponse },
|
||||
} = await apiFetch<{ data: IProduct }>('__UNUSED__', { variables })
|
||||
|
||||
return {
|
||||
product: normalizeProduct(
|
||||
|
@ -84,21 +84,25 @@ export default function getSiteInfoOperation({
|
||||
const config = commerce.getConfig(userConfig)
|
||||
const { fetch: apiFetch } = config // TODO: Send config.locale to Spree.
|
||||
|
||||
const { data: spreeCategoriesSuccessResponse } = await apiFetch<ITaxons>(
|
||||
'__UNUSED__',
|
||||
{
|
||||
const {
|
||||
data: { data: spreeCategoriesSuccessResponse },
|
||||
} = await apiFetch<{
|
||||
data: ITaxons
|
||||
}>('__UNUSED__', {
|
||||
variables: createVariables(
|
||||
requireConfigValue('spreeCategoriesTaxonomyId')
|
||||
requireConfigValue('spreeCategoriesTaxonomyId') as string
|
||||
),
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const { data: spreeBrandsSuccessResponse } = await apiFetch<ITaxons>(
|
||||
'__UNUSED__',
|
||||
{
|
||||
variables: createVariables(requireConfigValue('spreeBrandsTaxonomyId')),
|
||||
}
|
||||
)
|
||||
const {
|
||||
data: { data: spreeBrandsSuccessResponse },
|
||||
} = await apiFetch<{
|
||||
data: ITaxons
|
||||
}>('__UNUSED__', {
|
||||
variables: createVariables(
|
||||
requireConfigValue('spreeBrandsTaxonomyId') as string
|
||||
),
|
||||
})
|
||||
|
||||
const normalizedCategories: GetSiteInfoOperation['data']['categories'] =
|
||||
spreeCategoriesSuccessResponse.data.sort(taxonsSort).map((spreeTaxon) => {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { GraphQLFetcher, GraphQLFetcherResult } from '@commerce/api'
|
||||
import { SpreeApiConfig } from '..'
|
||||
import { errors, makeClient } from '@spree/storefront-api-v2-sdk'
|
||||
import { requireConfigValue } from 'framework/spree/isomorphicConfig'
|
||||
@ -6,18 +5,25 @@ import convertSpreeErrorToGraphQlError from 'framework/spree/utils/convertSpreeE
|
||||
import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/ResultResponse'
|
||||
import type {
|
||||
JsonApiListResponse,
|
||||
JsonApiResponse,
|
||||
JsonApiSingleResponse,
|
||||
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
|
||||
import getSpreeSdkMethodFromEndpointPath from 'framework/spree/utils/getSpreeSdkMethodFromEndpointPath'
|
||||
import { SpreeSdkVariables } from 'framework/spree/types'
|
||||
import SpreeSdkMethodFromEndpointPathError from 'framework/spree/errors/SpreeSdkMethodFromEndpointPathError'
|
||||
import { GraphQLFetcher, GraphQLFetcherResult } from '@commerce/api'
|
||||
import createCreateFetchFetcher from '../../utils/createCreateFetchFetcher'
|
||||
import createVercelFetch from '@vercel/fetch'
|
||||
|
||||
const createApiFetch: (
|
||||
getConfig: () => SpreeApiConfig
|
||||
) => GraphQLFetcher<GraphQLFetcherResult<any>, SpreeSdkVariables> = (
|
||||
getConfig
|
||||
_getConfig
|
||||
) => {
|
||||
const client = makeClient({ host: requireConfigValue('spreeApiHost') })
|
||||
const client = makeClient({
|
||||
host: requireConfigValue('spreeApiHost') as string,
|
||||
fetcherType: 'custom',
|
||||
createFetcher: createCreateFetchFetcher({ fetch: createVercelFetch() }),
|
||||
})
|
||||
|
||||
return async (url, queryData = {}, fetchOptions = {}) => {
|
||||
console.log(
|
||||
@ -38,21 +44,22 @@ const createApiFetch: (
|
||||
)
|
||||
}
|
||||
|
||||
const storeResponse: ResultResponse<JsonApiResponse | JsonApiListResponse> =
|
||||
await getSpreeSdkMethodFromEndpointPath(
|
||||
const storeResponse: ResultResponse<
|
||||
JsonApiSingleResponse | JsonApiListResponse
|
||||
> = await getSpreeSdkMethodFromEndpointPath(
|
||||
client,
|
||||
variables.methodPath
|
||||
)(...variables.arguments)
|
||||
|
||||
if (storeResponse.success()) {
|
||||
return {
|
||||
data: storeResponse.success(),
|
||||
res: storeResponse as any, //FIXME: MUST BE fetch() RESPONSE instead of axios.
|
||||
}
|
||||
}
|
||||
if (storeResponse.isSuccess()) {
|
||||
const data = storeResponse.success()
|
||||
const rawFetchRespone = Object.getPrototypeOf(data).response
|
||||
|
||||
// FIXME: Allow Spree SDK to use fetch instead of axios
|
||||
// (https://github.com/spree/spree-storefront-api-v2-js-sdk/issues/189)
|
||||
return {
|
||||
data,
|
||||
res: rawFetchRespone,
|
||||
}
|
||||
}
|
||||
|
||||
const storeResponseError = storeResponse.fail()
|
||||
|
||||
|
@ -4,20 +4,26 @@ import { makeClient } from '@spree/storefront-api-v2-sdk'
|
||||
import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/ResultResponse'
|
||||
import type {
|
||||
JsonApiListResponse,
|
||||
JsonApiResponse,
|
||||
JsonApiSingleResponse,
|
||||
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
|
||||
import { errors } from '@spree/storefront-api-v2-sdk'
|
||||
import { requireConfigValue } from './isomorphicConfig'
|
||||
import getSpreeSdkMethodFromEndpointPath from './utils/getSpreeSdkMethodFromEndpointPath'
|
||||
import SpreeSdkMethodFromEndpointPathError from './errors/SpreeSdkMethodFromEndpointPathError'
|
||||
import type { SpreeSdkVariables } from './types'
|
||||
import { GraphQLFetcherResult } from '@commerce/api'
|
||||
import type { GraphQLFetcherResult } from '@commerce/api'
|
||||
import createCreateFetchFetcher from './utils/createCreateFetchFetcher'
|
||||
|
||||
const client = makeClient({ host: requireConfigValue('spreeApiHost') })
|
||||
const client = makeClient({
|
||||
host: requireConfigValue('spreeApiHost') as string,
|
||||
fetcherType: 'custom',
|
||||
createFetcher: createCreateFetchFetcher({ fetch: globalThis.fetch }),
|
||||
})
|
||||
|
||||
const fetcher: Fetcher<GraphQLFetcherResult<any>, SpreeSdkVariables> = async (
|
||||
requestOptions
|
||||
) => {
|
||||
const fetcher: Fetcher<
|
||||
GraphQLFetcherResult<JsonApiSingleResponse | JsonApiListResponse>,
|
||||
SpreeSdkVariables
|
||||
> = async (requestOptions) => {
|
||||
// url?: string
|
||||
// query?: string
|
||||
// method?: string
|
||||
@ -40,21 +46,22 @@ const fetcher: Fetcher<GraphQLFetcherResult<any>, SpreeSdkVariables> = async (
|
||||
)
|
||||
}
|
||||
|
||||
const storeResponse: ResultResponse<JsonApiResponse | JsonApiListResponse> =
|
||||
await getSpreeSdkMethodFromEndpointPath(
|
||||
const storeResponse: ResultResponse<
|
||||
JsonApiSingleResponse | JsonApiListResponse
|
||||
> = await getSpreeSdkMethodFromEndpointPath(
|
||||
client,
|
||||
variables.methodPath
|
||||
)(...variables.arguments)
|
||||
|
||||
if (storeResponse.success()) {
|
||||
return {
|
||||
data: storeResponse.success(),
|
||||
res: storeResponse as any, //FIXME: MUST BE fetch() RESPONSE instead of axios.
|
||||
}
|
||||
}
|
||||
if (storeResponse.isSuccess()) {
|
||||
const data = storeResponse.success()
|
||||
const rawFetchRespone = Object.getPrototypeOf(data).response
|
||||
|
||||
// FIXME: Allow Spree SDK to use fetch instead of axios
|
||||
// (https://github.com/spree/spree-storefront-api-v2-js-sdk/issues/189)
|
||||
return {
|
||||
data,
|
||||
res: rawFetchRespone,
|
||||
}
|
||||
}
|
||||
|
||||
const storeResponseError = storeResponse.fail()
|
||||
|
||||
@ -65,14 +72,4 @@ const fetcher: Fetcher<GraphQLFetcherResult<any>, SpreeSdkVariables> = async (
|
||||
throw storeResponseError
|
||||
}
|
||||
|
||||
// export const fetcher: Fetcher = async () => {
|
||||
// console.log('FETCHER')
|
||||
// const res = await fetch('./data.json')
|
||||
// if (res.ok) {
|
||||
// const { data } = await res.json()
|
||||
// return data
|
||||
// }
|
||||
// throw res
|
||||
// }
|
||||
|
||||
export default fetcher
|
||||
|
@ -18,8 +18,8 @@ export type SpreeProps = {
|
||||
} & SpreeConfig
|
||||
|
||||
export const spreeCommerceConfigDefaults: CommerceConfig = {
|
||||
locale: requireConfigValue('defaultLocale'),
|
||||
cartCookie: requireConfigValue('cartCookieName'),
|
||||
locale: requireConfigValue('defaultLocale') as string,
|
||||
cartCookie: requireConfigValue('cartCookieName') as string,
|
||||
}
|
||||
|
||||
export type SpreeConfig = CommerceConfig
|
||||
|
@ -9,6 +9,8 @@ const isomorphicConfig = {
|
||||
spreeCategoriesTaxonomyId:
|
||||
process.env.NEXT_PUBLIC_SPREE_CATEGORIES_TAXONOMY_ID,
|
||||
spreeBrandsTaxonomyId: process.env.NEXT_PUBLIC_SPREE_BRANDS_TAXONOMY_ID,
|
||||
showSingleVariantOptions:
|
||||
process.env.NEXT_PUBLIC_SHOW_SINGLE_VARIANT_OPTIONS === 'true',
|
||||
}
|
||||
|
||||
export default forceIsomorphicConfigValues(
|
||||
@ -21,6 +23,7 @@ export default forceIsomorphicConfigValues(
|
||||
'spreeImageHost',
|
||||
'spreeCategoriesTaxonomyId',
|
||||
'spreeBrandsTaxonomyId',
|
||||
'showSingleVariantOptions',
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -40,9 +40,9 @@ export const handler: SWRHook<SearchProductsHook> = {
|
||||
|
||||
const sort = input.sort ? { sort: nextToSpreeSortMap[input.sort] } : {}
|
||||
|
||||
const { data: spreeSuccessResponse } = await fetch<
|
||||
GraphQLFetcherResult<IProducts>
|
||||
>({
|
||||
const {
|
||||
data: { data: spreeSuccessResponse },
|
||||
} = await fetch<GraphQLFetcherResult<{ data: IProducts }>>({
|
||||
variables: {
|
||||
methodPath: 'products.list',
|
||||
arguments: [
|
||||
|
72
framework/spree/utils/createCreateFetchFetcher.ts
Normal file
72
framework/spree/utils/createCreateFetchFetcher.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import * as qs from 'qs'
|
||||
import { errors } from '@spree/storefront-api-v2-sdk'
|
||||
import type { CreateFetcher } from '@spree/storefront-api-v2-sdk/types/interfaces/ClientConfig'
|
||||
import { Request } from 'node-fetch'
|
||||
|
||||
// TODO: Fix rawFetch any type.
|
||||
const createCreateFetchFetcher =
|
||||
({ fetch: rawFetch }): CreateFetcher =>
|
||||
(fetcherOptions) => {
|
||||
const { FetchError } = errors
|
||||
const sharedHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
return {
|
||||
fetch: async (fetchOptions) => {
|
||||
// This fetcher always returns request equal null,
|
||||
// because @vercel/fetch doesn't accept a Request object as argument
|
||||
// and it's not used by NJC anyway.
|
||||
try {
|
||||
const { url, params, method, headers } = fetchOptions
|
||||
const absoluteUrl = new URL(url, fetcherOptions.host)
|
||||
let payload
|
||||
|
||||
switch (method.toUpperCase()) {
|
||||
case 'PUT':
|
||||
case 'POST':
|
||||
case 'DELETE':
|
||||
case 'PATCH':
|
||||
payload = { body: JSON.stringify(params) }
|
||||
break
|
||||
default:
|
||||
payload = null
|
||||
absoluteUrl.search = qs.stringify(params, {
|
||||
arrayFormat: 'brackets',
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await rawFetch(absoluteUrl.toString(), {
|
||||
method,
|
||||
headers: { ...sharedHeaders, ...headers },
|
||||
...payload,
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
// Use the "traditional" approach and reject non 2xx responses.
|
||||
throw new FetchError(response, null, data)
|
||||
}
|
||||
|
||||
return {
|
||||
// Add response key to the prototype so it can be passed inside the GraphQLFetcherResult type.
|
||||
// TODO: Search for a better solution than adding response to the prototype.
|
||||
data: Object.setPrototypeOf({ data }, { response }),
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError) {
|
||||
throw new FetchError(null, null, null)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
} catch (error) {
|
||||
throw new FetchError(null, null, null, error.message)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default createCreateFetchFetcher
|
@ -5,7 +5,7 @@ import type {
|
||||
} from '@commerce/types/product'
|
||||
import type {
|
||||
JsonApiListResponse,
|
||||
JsonApiResponse,
|
||||
JsonApiSingleResponse,
|
||||
} 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'
|
||||
@ -16,7 +16,7 @@ import getMediaGallery from './getMediaGallery'
|
||||
import { findIncludedOfType } from './jsonApi'
|
||||
|
||||
const normalizeProduct = (
|
||||
spreeSuccessResponse: JsonApiResponse | JsonApiListResponse,
|
||||
spreeSuccessResponse: JsonApiSingleResponse | JsonApiListResponse,
|
||||
spreeProduct: ProductAttr
|
||||
) => {
|
||||
const spreeImageRecords = findIncludedOfType(
|
||||
@ -27,7 +27,7 @@ const normalizeProduct = (
|
||||
|
||||
const images = getMediaGallery(
|
||||
spreeImageRecords,
|
||||
createGetAbsoluteImageUrl(requireConfigValue('spreeImageHost'))
|
||||
createGetAbsoluteImageUrl(requireConfigValue('spreeImageHost') as string)
|
||||
)
|
||||
|
||||
const price: ProductPrice = {
|
||||
@ -41,6 +41,10 @@ const normalizeProduct = (
|
||||
const hasNonMasterVariants =
|
||||
(spreeProduct.relationships.variants.data as RelationType[]).length > 1
|
||||
|
||||
const showOptions =
|
||||
(requireConfigValue('showSingleVariantOptions') as boolean) ||
|
||||
hasNonMasterVariants
|
||||
|
||||
let variants: ProductVariant[]
|
||||
let options: ProductOption[] = []
|
||||
|
||||
@ -53,7 +57,7 @@ const normalizeProduct = (
|
||||
variants = spreeVariantRecords.map((spreeVariantRecord) => {
|
||||
let variantOptions: ProductOption[] = []
|
||||
|
||||
if (hasNonMasterVariants) {
|
||||
if (showOptions) {
|
||||
const spreeOptionValues = findIncludedOfType(
|
||||
spreeSuccessResponse,
|
||||
spreeVariantRecord,
|
||||
|
@ -21,6 +21,7 @@
|
||||
"dependencies": {
|
||||
"@react-spring/web": "^9.2.1",
|
||||
"@spree/storefront-api-v2-sdk": "^4.5.4",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@vercel/fetch": "^6.1.0",
|
||||
"autoprefixer": "^10.2.6",
|
||||
"body-scroll-lock": "^3.1.5",
|
||||
@ -38,6 +39,7 @@
|
||||
"next-themes": "^0.0.14",
|
||||
"postcss": "^8.3.5",
|
||||
"postcss-nesting": "^8.0.1",
|
||||
"qs": "^6.7.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-fast-marquee": "^1.1.4",
|
||||
|
@ -1135,6 +1135,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz"
|
||||
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
||||
|
||||
"@types/qs@^6.9.7":
|
||||
version "6.9.7"
|
||||
resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz"
|
||||
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
|
||||
|
||||
"@types/react@^17.0.8":
|
||||
version "17.0.11"
|
||||
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.11.tgz"
|
||||
@ -4960,7 +4965,7 @@ purgecss@^4.0.3:
|
||||
postcss "^8.2.1"
|
||||
postcss-selector-parser "^6.0.2"
|
||||
|
||||
qs@6.7.0:
|
||||
qs@6.7.0, qs@^6.7.0:
|
||||
version "6.7.0"
|
||||
resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz"
|
||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
|
Loading…
x
Reference in New Issue
Block a user