Add configuration to show product options when there's one variant available

This commit is contained in:
tniezg 2021-07-29 13:02:57 +02:00
parent 2c4e2e4cb4
commit 744a8b998e
13 changed files with 167 additions and 74 deletions

View File

@ -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

View File

@ -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)

View File

@ -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(

View File

@ -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__',
{
variables: createVariables(
requireConfigValue('spreeCategoriesTaxonomyId')
),
}
)
const {
data: { data: spreeCategoriesSuccessResponse },
} = await apiFetch<{
data: ITaxons
}>('__UNUSED__', {
variables: createVariables(
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) => {

View File

@ -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,22 +44,23 @@ const createApiFetch: (
)
}
const storeResponse: ResultResponse<JsonApiResponse | JsonApiListResponse> =
await getSpreeSdkMethodFromEndpointPath(
client,
variables.methodPath
)(...variables.arguments)
const storeResponse: ResultResponse<
JsonApiSingleResponse | JsonApiListResponse
> = await getSpreeSdkMethodFromEndpointPath(
client,
variables.methodPath
)(...variables.arguments)
if (storeResponse.isSuccess()) {
const data = storeResponse.success()
const rawFetchRespone = Object.getPrototypeOf(data).response
if (storeResponse.success()) {
return {
data: storeResponse.success(),
res: storeResponse as any, //FIXME: MUST BE fetch() RESPONSE instead of axios.
data,
res: rawFetchRespone,
}
}
// FIXME: Allow Spree SDK to use fetch instead of axios
// (https://github.com/spree/spree-storefront-api-v2-js-sdk/issues/189)
const storeResponseError = storeResponse.fail()
if (storeResponseError instanceof errors.SpreeError) {

View File

@ -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,22 +46,23 @@ const fetcher: Fetcher<GraphQLFetcherResult<any>, SpreeSdkVariables> = async (
)
}
const storeResponse: ResultResponse<JsonApiResponse | JsonApiListResponse> =
await getSpreeSdkMethodFromEndpointPath(
client,
variables.methodPath
)(...variables.arguments)
const storeResponse: ResultResponse<
JsonApiSingleResponse | JsonApiListResponse
> = await getSpreeSdkMethodFromEndpointPath(
client,
variables.methodPath
)(...variables.arguments)
if (storeResponse.isSuccess()) {
const data = storeResponse.success()
const rawFetchRespone = Object.getPrototypeOf(data).response
if (storeResponse.success()) {
return {
data: storeResponse.success(),
res: storeResponse as any, //FIXME: MUST BE fetch() RESPONSE instead of axios.
data,
res: rawFetchRespone,
}
}
// FIXME: Allow Spree SDK to use fetch instead of axios
// (https://github.com/spree/spree-storefront-api-v2-js-sdk/issues/189)
const storeResponseError = storeResponse.fail()
if (storeResponseError instanceof errors.SpreeError) {
@ -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

View File

@ -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

View File

@ -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',
]
)

View File

@ -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: [

View 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

View File

@ -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,

View File

@ -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",

View File

@ -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==