mirror of
https://github.com/vercel/commerce.git
synced 2025-07-04 12:11:22 +00:00
Implement variants and options
This commit is contained in:
parent
04f9799636
commit
07bc16b9a7
@ -1,15 +1,33 @@
|
|||||||
import data from '../../data.json'
|
import type { OrdercloudConfig, Provider } from '../index'
|
||||||
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
|
import type { GetAllProductPathsOperation } from '@commerce/types/product'
|
||||||
|
import { RawProduct } from '@framework/types/product'
|
||||||
|
|
||||||
export type GetAllProductPathsResult = {
|
export type GetAllProductPathsResult = {
|
||||||
products: Array<{ path: string }>
|
products: Array<{ path: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function getAllProductPathsOperation() {
|
export default function getAllProductPathsOperation({
|
||||||
function getAllProductPaths(): Promise<GetAllProductPathsResult> {
|
commerce,
|
||||||
return Promise.resolve({
|
}: OperationContext<Provider>) {
|
||||||
products: []
|
async function getAllProductPaths<T extends GetAllProductPathsOperation>({
|
||||||
// products: data.products.map(({ path }) => ({ path })),
|
config,
|
||||||
})
|
}: {
|
||||||
|
config?: Partial<OrdercloudConfig>
|
||||||
|
} = {}): Promise<T['data']> {
|
||||||
|
// Get fetch from the config
|
||||||
|
const { fetch } = commerce.getConfig(config)
|
||||||
|
|
||||||
|
// Get all products
|
||||||
|
const rawProducts: RawProduct[] = await fetch<{ Items: RawProduct[] }>(
|
||||||
|
'GET',
|
||||||
|
'/products'
|
||||||
|
).then((response) => response.Items)
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Match a path for every product retrieved
|
||||||
|
products: rawProducts.map((product) => ({ path: `/${product.ID}` })),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return getAllProductPaths
|
return getAllProductPaths
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import type { Product } from '@commerce/types/product'
|
|
||||||
import type { GetAllProductsOperation } from '@commerce/types/product'
|
import type { GetAllProductsOperation } from '@commerce/types/product'
|
||||||
import type { OperationContext } from '@commerce/api/operations'
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
import type { RawProduct } from '@framework/types/product'
|
import type { RawProduct } from '@framework/types/product'
|
||||||
@ -16,15 +15,18 @@ export default function getAllProductsOperation({
|
|||||||
variables?: T['variables']
|
variables?: T['variables']
|
||||||
config?: Partial<OrdercloudConfig>
|
config?: Partial<OrdercloudConfig>
|
||||||
preview?: boolean
|
preview?: boolean
|
||||||
} = {}): Promise<{ products: Product[] }> {
|
} = {}): Promise<T['data']> {
|
||||||
|
// Get fetch from the config
|
||||||
const { fetch } = commerce.getConfig(config)
|
const { fetch } = commerce.getConfig(config)
|
||||||
|
|
||||||
|
// Get all products
|
||||||
const rawProducts: RawProduct[] = await fetch<{ Items: RawProduct[] }>(
|
const rawProducts: RawProduct[] = await fetch<{ Items: RawProduct[] }>(
|
||||||
'GET',
|
'GET',
|
||||||
'/products'
|
'/products'
|
||||||
).then((response) => response.Items)
|
).then((response) => response.Items)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
// Normalize products to commerce schema
|
||||||
products: rawProducts.map(normalizeProduct),
|
products: rawProducts.map(normalizeProduct),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import type { OperationContext } from '@commerce/api/operations'
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
import type { RawProduct } from '@framework/types/product'
|
import type { RawProduct } from '@framework/types/product'
|
||||||
import type { Product } from '@commerce/types/product'
|
|
||||||
import type { GetProductOperation } from '@commerce/types/product'
|
import type { GetProductOperation } from '@commerce/types/product'
|
||||||
import type { OrdercloudConfig, Provider } from '../index'
|
import type { OrdercloudConfig, Provider } from '../index'
|
||||||
|
|
||||||
@ -17,15 +16,18 @@ export default function getProductOperation({
|
|||||||
variables?: T['variables']
|
variables?: T['variables']
|
||||||
config?: Partial<OrdercloudConfig>
|
config?: Partial<OrdercloudConfig>
|
||||||
preview?: boolean
|
preview?: boolean
|
||||||
} = {}): Promise<{ product: Product }> {
|
} = {}): Promise<T['data']> {
|
||||||
|
// Get fetch from the config
|
||||||
const { fetch } = commerce.getConfig(config)
|
const { fetch } = commerce.getConfig(config)
|
||||||
|
|
||||||
|
// Get a single product
|
||||||
const rawProduct: RawProduct = await fetch<RawProduct>(
|
const rawProduct: RawProduct = await fetch<RawProduct>(
|
||||||
'GET',
|
'GET',
|
||||||
`/products/${variables?.slug}`
|
`/products/${variables?.slug}`
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
// Normalize product to commerce schema
|
||||||
product: normalizeProduct(rawProduct),
|
product: normalizeProduct(rawProduct),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { FetcherError } from '@commerce/utils/errors'
|
|
||||||
import type { OrdercloudConfig } from '../index'
|
import type { OrdercloudConfig } from '../index'
|
||||||
|
|
||||||
|
import { FetcherError } from '@commerce/utils/errors'
|
||||||
import fetch from './fetch'
|
import fetch from './fetch'
|
||||||
|
|
||||||
const fetchRestApi: (
|
const fetchRestApi: (
|
||||||
@ -18,8 +19,8 @@ const fetchRestApi: (
|
|||||||
fetchOptions?: Record<string, any>
|
fetchOptions?: Record<string, any>
|
||||||
) => {
|
) => {
|
||||||
const { commerceUrl } = getConfig()
|
const { commerceUrl } = getConfig()
|
||||||
// Check if we have a token stored
|
|
||||||
if (!global.token) {
|
async function getToken() {
|
||||||
// If not, get a new one and store it
|
// If not, get a new one and store it
|
||||||
const authResponse = await fetch(`${commerceUrl}/oauth/token`, {
|
const authResponse = await fetch(`${commerceUrl}/oauth/token`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -48,32 +49,58 @@ const fetchRestApi: (
|
|||||||
.then((response) => response.access_token)
|
.then((response) => response.access_token)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the request with the correct headers
|
async function fetchData(retries = 0): Promise<T> {
|
||||||
const dataResponse = await fetch(`${commerceUrl}/v1${resource}`, {
|
// Do the request with the correct headers
|
||||||
...fetchOptions,
|
const dataResponse = await fetch(`${commerceUrl}/v1${resource}`, {
|
||||||
method,
|
...fetchOptions,
|
||||||
headers: {
|
method,
|
||||||
...fetchOptions?.headers,
|
headers: {
|
||||||
accept: 'application/json, text/plain, */*',
|
...fetchOptions?.headers,
|
||||||
authorization: `Bearer ${global.token}`,
|
accept: 'application/json, text/plain, */*',
|
||||||
},
|
authorization: `Bearer ${global.token}`,
|
||||||
body: body ? JSON.stringify(body) : undefined,
|
},
|
||||||
})
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
|
|
||||||
// If something failed getting the data response
|
|
||||||
if (!dataResponse.ok) {
|
|
||||||
// Get the body of it
|
|
||||||
const error = await dataResponse.json()
|
|
||||||
|
|
||||||
// And return an error
|
|
||||||
throw new FetcherError({
|
|
||||||
errors: [{ message: error.error_description.Code }],
|
|
||||||
status: error.error_description.HttpStatus,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// If something failed getting the data response
|
||||||
|
if (!dataResponse.ok) {
|
||||||
|
// If token is expired
|
||||||
|
if (dataResponse.status === 401) {
|
||||||
|
// Reset it
|
||||||
|
global.token = null
|
||||||
|
|
||||||
|
// Get a new one
|
||||||
|
await getToken()
|
||||||
|
|
||||||
|
// And if retries left
|
||||||
|
if (retries < 2) {
|
||||||
|
// Refetch
|
||||||
|
return fetchData(retries + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the body of it
|
||||||
|
const error = await dataResponse.json()
|
||||||
|
|
||||||
|
// And return an error
|
||||||
|
throw new FetcherError({
|
||||||
|
errors: [{ message: error.error_description.Code }],
|
||||||
|
status: error.error_description.HttpStatus,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return data response
|
||||||
|
return dataResponse.json() as Promise<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have a token stored
|
||||||
|
if (!global.token) {
|
||||||
|
// If not, get a new one and store it
|
||||||
|
await getToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the data and specify the expected type
|
// Return the data and specify the expected type
|
||||||
return (await dataResponse.json()) as T
|
return fetchData()
|
||||||
}
|
}
|
||||||
|
|
||||||
export default fetchRestApi
|
export default fetchRestApi
|
||||||
|
@ -23,5 +23,6 @@ export interface RawProduct {
|
|||||||
Images: {
|
Images: {
|
||||||
url: string
|
url: string
|
||||||
}[]
|
}[]
|
||||||
|
Facets: Record<string, string[]>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,49 +12,32 @@ export function normalize(product: RawProduct): Product {
|
|||||||
value: product.xp.Price,
|
value: product.xp.Price,
|
||||||
currencyCode: product.xp.PriceCurrency,
|
currencyCode: product.xp.PriceCurrency,
|
||||||
},
|
},
|
||||||
// TODO: Implement this
|
// Variants are not always present, in case they are not, return a single unique variant
|
||||||
variants: [
|
variants:
|
||||||
{
|
product.VariantCount === 0
|
||||||
id: 'unique',
|
? [
|
||||||
options: [
|
{
|
||||||
{
|
id: 'unique',
|
||||||
id: 'unique',
|
options: [
|
||||||
displayName: 'Model',
|
{
|
||||||
values: [
|
id: 'unique',
|
||||||
{
|
displayName: 'Unique',
|
||||||
label: 'Unique',
|
values: [{ label: 'Unique' }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
]
|
||||||
},
|
: [],
|
||||||
],
|
// Facets are not always present, just iterate them if they are
|
||||||
options: [
|
options: product.xp.Facets
|
||||||
{
|
? Object.entries(product.xp.Facets).map(([key, values]) => ({
|
||||||
id: 'option-color',
|
id: key,
|
||||||
displayName: 'Color',
|
displayName: key,
|
||||||
values: [
|
__typename: 'MultipleChoiceOption',
|
||||||
{
|
values: values.map((value) => ({
|
||||||
label: 'color',
|
label: value,
|
||||||
hexColors: ['#222'],
|
})),
|
||||||
},
|
}))
|
||||||
],
|
: [],
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'option-size',
|
|
||||||
displayName: 'Size',
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
label: 'S',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'M',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'L',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user