mirror of
https://github.com/vercel/commerce.git
synced 2025-07-03 19:51:22 +00:00
WIP POC in progress
This commit is contained in:
parent
f765590484
commit
04f9799636
@ -1,4 +1,4 @@
|
|||||||
# Available providers: bigcommerce, shopify, swell
|
# Available providers: bigcommerce, shopify, swell, local, saleor, vendure, ordercloud
|
||||||
COMMERCE_PROVIDER=
|
COMMERCE_PROVIDER=
|
||||||
|
|
||||||
BIGCOMMERCE_STOREFRONT_API_URL=
|
BIGCOMMERCE_STOREFRONT_API_URL=
|
||||||
@ -23,3 +23,6 @@ NEXT_PUBLIC_SALEOR_CHANNEL=
|
|||||||
|
|
||||||
NEXT_PUBLIC_VENDURE_SHOP_API_URL=
|
NEXT_PUBLIC_VENDURE_SHOP_API_URL=
|
||||||
NEXT_PUBLIC_VENDURE_LOCAL_URL=
|
NEXT_PUBLIC_VENDURE_LOCAL_URL=
|
||||||
|
|
||||||
|
NEXT_PUBLIC_ORDERCLOUD_CLIENT_ID=
|
||||||
|
NEXT_PUBLIC_ORDERCLOUD_CLIENT_SECRET=
|
||||||
|
@ -20,7 +20,7 @@ export interface OrdercloudConfig extends Omit<CommerceAPIConfig, 'fetch'> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const config: OrdercloudConfig = {
|
const config: OrdercloudConfig = {
|
||||||
commerceUrl: 'https://sandboxapi.ordercloud.io/v1',
|
commerceUrl: 'https://sandboxapi.ordercloud.io',
|
||||||
apiToken: '',
|
apiToken: '',
|
||||||
cartCookie: '',
|
cartCookie: '',
|
||||||
customerCookie: '',
|
customerCookie: '',
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import { Product } from '@commerce/types/product'
|
import type { Product } from '@commerce/types/product'
|
||||||
import { 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 { OrdercloudConfig, Provider } from '../index'
|
import type { OrdercloudConfig, Provider } from '../index'
|
||||||
import {
|
|
||||||
PriceSchedule,
|
|
||||||
RawProduct,
|
|
||||||
RawProductWithPrice,
|
|
||||||
} from '@framework/types/product'
|
|
||||||
import { normalize as normalizeProduct } from '@framework/utils/product'
|
import { normalize as normalizeProduct } from '@framework/utils/product'
|
||||||
|
|
||||||
export default function getAllProductsOperation({
|
export default function getAllProductsOperation({
|
||||||
@ -26,19 +23,11 @@ export default function getAllProductsOperation({
|
|||||||
'GET',
|
'GET',
|
||||||
'/products'
|
'/products'
|
||||||
).then((response) => response.Items)
|
).then((response) => response.Items)
|
||||||
const rawProductsWithPrice: RawProductWithPrice[] = await Promise.all(
|
|
||||||
rawProducts.map(async (product) => ({
|
|
||||||
...product,
|
|
||||||
priceSchedule: await fetch<PriceSchedule>(
|
|
||||||
'GET',
|
|
||||||
`/priceschedules/${product.ID}`
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
products: rawProductsWithPrice.map(normalizeProduct),
|
products: rawProducts.map(normalizeProduct),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return getAllProducts
|
return getAllProducts
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,32 @@
|
|||||||
import type { OrdercloudConfig } from '../index'
|
|
||||||
import { Product } from '@commerce/types/product'
|
|
||||||
import { GetProductOperation } from '@commerce/types/product'
|
|
||||||
import data from '../../data.json'
|
|
||||||
import type { OperationContext } from '@commerce/api/operations'
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
|
import type { RawProduct } from '@framework/types/product'
|
||||||
|
import type { Product } from '@commerce/types/product'
|
||||||
|
import type { GetProductOperation } from '@commerce/types/product'
|
||||||
|
import type { OrdercloudConfig, Provider } from '../index'
|
||||||
|
|
||||||
|
import { normalize as normalizeProduct } from '@framework/utils/product'
|
||||||
|
|
||||||
export default function getProductOperation({
|
export default function getProductOperation({
|
||||||
commerce,
|
commerce,
|
||||||
}: OperationContext<any>) {
|
}: OperationContext<Provider>) {
|
||||||
async function getProduct<T extends GetProductOperation>({
|
async function getProduct<T extends GetProductOperation>({
|
||||||
query = '',
|
|
||||||
variables,
|
|
||||||
config,
|
config,
|
||||||
|
variables,
|
||||||
}: {
|
}: {
|
||||||
query?: string
|
query?: string
|
||||||
variables?: T['variables']
|
variables?: T['variables']
|
||||||
config?: Partial<OrdercloudConfig>
|
config?: Partial<OrdercloudConfig>
|
||||||
preview?: boolean
|
preview?: boolean
|
||||||
} = {}): Promise<Product | {} | any> {
|
} = {}): Promise<{ product: Product }> {
|
||||||
|
const { fetch } = commerce.getConfig(config)
|
||||||
|
|
||||||
|
const rawProduct: RawProduct = await fetch<RawProduct>(
|
||||||
|
'GET',
|
||||||
|
`/products/${variables?.slug}`
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
product: null // data.products.find(({ slug }) => slug === variables!.slug),
|
product: normalizeProduct(rawProduct),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,26 +18,62 @@ const fetchRestApi: (
|
|||||||
fetchOptions?: Record<string, any>
|
fetchOptions?: Record<string, any>
|
||||||
) => {
|
) => {
|
||||||
const { commerceUrl } = getConfig()
|
const { commerceUrl } = getConfig()
|
||||||
const res = await fetch(`${commerceUrl}${resource}`, {
|
// Check if we have a token stored
|
||||||
|
if (!global.token) {
|
||||||
|
// If not, get a new one and store it
|
||||||
|
const authResponse = await fetch(`${commerceUrl}/oauth/token`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
body: `client_id=${process.env.NEXT_PUBLIC_ORDERCLOUD_CLIENT_ID}&grant_type=client_credentials&client_secret=${process.env.NEXT_PUBLIC_ORDERCLOUD_CLIENT_SECRET}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// If something failed getting the auth response
|
||||||
|
if (!authResponse.ok) {
|
||||||
|
// Get the body of it
|
||||||
|
const error = await authResponse.json()
|
||||||
|
|
||||||
|
// And return an error
|
||||||
|
throw new FetcherError({
|
||||||
|
errors: [{ message: error.error_description.Code }],
|
||||||
|
status: error.error_description.HttpStatus,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// If everything is fine, store the access token in global.token
|
||||||
|
global.token = await authResponse
|
||||||
|
.json()
|
||||||
|
.then((response) => response.access_token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the request with the correct headers
|
||||||
|
const dataResponse = await fetch(`${commerceUrl}/v1${resource}`, {
|
||||||
...fetchOptions,
|
...fetchOptions,
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
...fetchOptions?.headers,
|
...fetchOptions?.headers,
|
||||||
accept: 'application/json, text/plain, */*',
|
accept: 'application/json, text/plain, */*',
|
||||||
'accept-language': 'es,en;q=0.9,es-ES;q=0.8,fr;q=0.7',
|
authorization: `Bearer ${global.token}`,
|
||||||
authorization: 'Bearer <your token>',
|
|
||||||
},
|
},
|
||||||
body: body ? JSON.stringify(body) : undefined,
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!res.ok) {
|
// 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({
|
throw new FetcherError({
|
||||||
errors: [{ message: res.statusText }],
|
errors: [{ message: error.error_description.Code }],
|
||||||
status: res.status,
|
status: error.error_description.HttpStatus,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await res.json()) as T
|
// Return the data and specify the expected type
|
||||||
|
return (await dataResponse.json()) as T
|
||||||
}
|
}
|
||||||
|
|
||||||
export default fetchRestApi
|
export default fetchRestApi
|
||||||
|
@ -3,6 +3,15 @@ const commerce = require('./commerce.config.json')
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
commerce,
|
commerce,
|
||||||
images: {
|
images: {
|
||||||
domains: ['localhost'],
|
domains: [
|
||||||
|
'localhost',
|
||||||
|
// TODO: Remove this
|
||||||
|
'images.bloomingdalesassets.com',
|
||||||
|
'pbs.twimg.com',
|
||||||
|
'images.asos-media.com',
|
||||||
|
'di2ponv0v5otw.cloudfront.net',
|
||||||
|
'cdn.shopify.com',
|
||||||
|
'encrypted-tbn0.gstatic.com',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
5
framework/ordercloud/types/node.d.ts
vendored
Normal file
5
framework/ordercloud/types/node.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
declare module NodeJS {
|
||||||
|
interface Global {
|
||||||
|
token: string | null | undefined
|
||||||
|
}
|
||||||
|
}
|
@ -17,28 +17,11 @@ export interface RawProduct {
|
|||||||
Inventory: null
|
Inventory: null
|
||||||
DefaultSupplierID: null
|
DefaultSupplierID: null
|
||||||
AllSuppliersCanSell: boolean
|
AllSuppliersCanSell: boolean
|
||||||
xp: null
|
xp: {
|
||||||
}
|
|
||||||
|
|
||||||
export interface RawProductWithPrice extends RawProduct {
|
|
||||||
priceSchedule: PriceSchedule
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PriceSchedule {
|
|
||||||
OwnerID: string
|
|
||||||
ID: string
|
|
||||||
Name: string
|
|
||||||
ApplyTax: boolean
|
|
||||||
ApplyShipping: boolean
|
|
||||||
MinQuantity: number
|
|
||||||
MaxQuantity: number
|
|
||||||
UseCumulativeQuantity: boolean
|
|
||||||
RestrictedQuantity: boolean
|
|
||||||
PriceBreaks: [
|
|
||||||
{
|
|
||||||
Quantity: number
|
|
||||||
Price: number
|
Price: number
|
||||||
|
PriceCurrency: string
|
||||||
|
Images: {
|
||||||
|
url: string
|
||||||
|
}[]
|
||||||
}
|
}
|
||||||
]
|
|
||||||
xp: null
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,60 @@
|
|||||||
import type { RawProductWithPrice } from '@framework/types/product'
|
import type { RawProduct } from '@framework/types/product'
|
||||||
import type { Product } from '@commerce/types/product'
|
import type { Product } from '@commerce/types/product'
|
||||||
|
|
||||||
export function normalize(product: RawProductWithPrice): Product {
|
export function normalize(product: RawProduct): Product {
|
||||||
return {
|
return {
|
||||||
id: product.ID,
|
id: product.ID,
|
||||||
name: product.Name,
|
name: product.Name,
|
||||||
description: product.Description,
|
description: product.Description,
|
||||||
images: [],
|
slug: product.ID,
|
||||||
variants: [],
|
images: product.xp.Images,
|
||||||
price: {
|
price: {
|
||||||
value: product.priceSchedule.PriceBreaks[0].Price,
|
value: product.xp.Price,
|
||||||
currencyCode: 'USD',
|
currencyCode: product.xp.PriceCurrency,
|
||||||
},
|
},
|
||||||
options: [],
|
// TODO: Implement this
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: 'unique',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 'unique',
|
||||||
|
displayName: 'Model',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
label: 'Unique',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 'option-color',
|
||||||
|
displayName: 'Color',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
label: 'color',
|
||||||
|
hexColors: ['#222'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'option-size',
|
||||||
|
displayName: 'Size',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
label: 'S',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'M',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'L',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user