mirror of
https://github.com/vercel/commerce.git
synced 2025-03-31 09:15:53 +00:00
Add ordercloud provider (#500)
* Add ordercloud provider * Fix provider errors * Make submit checkout optional * Make submit checkout optional * Remove nullables when creating endpoint type * Update readme * Log checkout error * Log error * Save token to cookie * Update fetch rest * Use token at checkout Co-authored-by: Luis Alvarez <luis@vercel.com>
This commit is contained in:
parent
f9644fecef
commit
3f0c38461b
.env.templatepackage.jsonyarn.lock
framework
commerce
ordercloud
.env.templateREADME.md
api
endpoints
index.tsoperations
get-all-pages.tsget-all-product-paths.tsget-all-products.tsget-page.tsget-product.tsget-site-info.tsindex.ts
utils
auth
cart
checkout
commerce.config.jsonconstants.tscustomer
fetcher.tsindex.tsxnext.config.jsproduct
provider.tstypes
utils
wishlist
@ -23,3 +23,7 @@ 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=
|
||||||
|
|
||||||
|
ORDERCLOUD_CLIENT_ID=
|
||||||
|
ORDERCLOUD_CLIENT_SECRET=
|
||||||
|
STRIPE_SECRET=
|
||||||
|
@ -65,8 +65,8 @@ export type EndpointHandlers<
|
|||||||
[H in keyof E['handlers']]: APIHandler<
|
[H in keyof E['handlers']]: APIHandler<
|
||||||
C,
|
C,
|
||||||
EndpointHandlers<C, E>,
|
EndpointHandlers<C, E>,
|
||||||
E['handlers'][H]['data'],
|
NonNullable<E['handlers'][H]>['data'],
|
||||||
E['handlers'][H]['body'],
|
NonNullable<E['handlers'][H]>['body'],
|
||||||
E['options']
|
E['options']
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ const PROVIDERS = [
|
|||||||
'shopify',
|
'shopify',
|
||||||
'swell',
|
'swell',
|
||||||
'vendure',
|
'vendure',
|
||||||
|
'ordercloud',
|
||||||
]
|
]
|
||||||
|
|
||||||
function getProviderName() {
|
function getProviderName() {
|
||||||
|
@ -30,7 +30,7 @@ export type GetCheckoutHook<T extends CheckoutTypes = CheckoutTypes> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CheckoutHooks<T extends CheckoutTypes = CheckoutTypes> = {
|
export type CheckoutHooks<T extends CheckoutTypes = CheckoutTypes> = {
|
||||||
submitCheckout: SubmitCheckoutHook<T>
|
submitCheckout?: SubmitCheckoutHook<T>
|
||||||
getCheckout: GetCheckoutHook<T>
|
getCheckout: GetCheckoutHook<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,41 +1,46 @@
|
|||||||
export interface Address {
|
export interface Address {
|
||||||
id: string;
|
id: string
|
||||||
mask: string;
|
mask: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddressFields {
|
export interface AddressFields {
|
||||||
type: string;
|
type: string
|
||||||
firstName: string;
|
firstName: string
|
||||||
lastName: string;
|
lastName: string
|
||||||
company: string;
|
company: string
|
||||||
streetNumber: string;
|
streetNumber: string
|
||||||
apartments: string;
|
apartments: string
|
||||||
zipCode: string;
|
zipCode: string
|
||||||
city: string;
|
city: string
|
||||||
country: string;
|
country: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CustomerAddressTypes = {
|
export type CustomerAddressTypes = {
|
||||||
address?: Address;
|
address?: Address
|
||||||
fields: AddressFields;
|
fields: AddressFields
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetAddressesHook<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
export type GetAddressesHook<
|
||||||
data: T['address'] | null
|
T extends CustomerAddressTypes = CustomerAddressTypes
|
||||||
|
> = {
|
||||||
|
data: T['address'][] | null
|
||||||
input: {}
|
input: {}
|
||||||
fetcherInput: { cartId?: string }
|
fetcherInput: { cartId?: string }
|
||||||
swrState: { isEmpty: boolean }
|
swrState: { isEmpty: boolean }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddItemHook<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
export type AddItemHook<T extends CustomerAddressTypes = CustomerAddressTypes> =
|
||||||
|
{
|
||||||
data: T['address']
|
data: T['address']
|
||||||
input?: T['fields']
|
input?: T['fields']
|
||||||
fetcherInput: T['fields']
|
fetcherInput: T['fields']
|
||||||
body: { item: T['fields'] }
|
body: { item: T['fields'] }
|
||||||
actionInput: T['fields']
|
actionInput: T['fields']
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateItemHook<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
export type UpdateItemHook<
|
||||||
|
T extends CustomerAddressTypes = CustomerAddressTypes
|
||||||
|
> = {
|
||||||
data: T['address'] | null
|
data: T['address'] | null
|
||||||
input: { item?: T['fields']; wait?: number }
|
input: { item?: T['fields']; wait?: number }
|
||||||
fetcherInput: { itemId: string; item: T['fields'] }
|
fetcherInput: { itemId: string; item: T['fields'] }
|
||||||
@ -43,49 +48,62 @@ export type UpdateItemHook<T extends CustomerAddressTypes = CustomerAddressTypes
|
|||||||
actionInput: T['fields'] & { id: string }
|
actionInput: T['fields'] & { id: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RemoveItemHook<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
export type RemoveItemHook<
|
||||||
|
T extends CustomerAddressTypes = CustomerAddressTypes
|
||||||
|
> = {
|
||||||
data: T['address'] | null
|
data: T['address'] | null
|
||||||
input: { item?: T['fields'] }
|
input: { item?: T['address'] }
|
||||||
fetcherInput: { itemId: string }
|
fetcherInput: { itemId: string }
|
||||||
body: { itemId: string }
|
body: { itemId: string }
|
||||||
actionInput: { id: string }
|
actionInput: { id: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CustomerAddressHooks<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
export type CustomerAddressHooks<
|
||||||
|
T extends CustomerAddressTypes = CustomerAddressTypes
|
||||||
|
> = {
|
||||||
getAddresses: GetAddressesHook<T>
|
getAddresses: GetAddressesHook<T>
|
||||||
addItem: AddItemHook<T>
|
addItem: AddItemHook<T>
|
||||||
updateItem: UpdateItemHook<T>
|
updateItem: UpdateItemHook<T>
|
||||||
removeItem: RemoveItemHook<T>
|
removeItem: RemoveItemHook<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddresssHandler<T extends CustomerAddressTypes = CustomerAddressTypes> = GetAddressesHook<T> & {
|
export type AddressHandler<
|
||||||
|
T extends CustomerAddressTypes = CustomerAddressTypes
|
||||||
|
> = GetAddressesHook<T> & {
|
||||||
body: { cartId?: string }
|
body: { cartId?: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddItemHandler<T extends CustomerAddressTypes = CustomerAddressTypes> = AddItemHook<T> & {
|
export type AddItemHandler<
|
||||||
|
T extends CustomerAddressTypes = CustomerAddressTypes
|
||||||
|
> = AddItemHook<T> & {
|
||||||
body: { cartId: string }
|
body: { cartId: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateItemHandler<T extends CustomerAddressTypes = CustomerAddressTypes> =
|
export type UpdateItemHandler<
|
||||||
UpdateItemHook<T> & {
|
T extends CustomerAddressTypes = CustomerAddressTypes
|
||||||
|
> = UpdateItemHook<T> & {
|
||||||
data: T['address']
|
data: T['address']
|
||||||
body: { cartId: string }
|
body: { cartId: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RemoveItemHandler<T extends CustomerAddressTypes = CustomerAddressTypes> =
|
export type RemoveItemHandler<
|
||||||
RemoveItemHook<T> & {
|
T extends CustomerAddressTypes = CustomerAddressTypes
|
||||||
|
> = RemoveItemHook<T> & {
|
||||||
body: { cartId: string }
|
body: { cartId: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CustomerAddressHandlers<
|
||||||
export type CustomerAddressHandlers<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
T extends CustomerAddressTypes = CustomerAddressTypes
|
||||||
|
> = {
|
||||||
getAddresses: GetAddressesHook<T>
|
getAddresses: GetAddressesHook<T>
|
||||||
addItem: AddItemHandler<T>
|
addItem: AddItemHandler<T>
|
||||||
updateItem: UpdateItemHandler<T>
|
updateItem: UpdateItemHandler<T>
|
||||||
removeItem: RemoveItemHandler<T>
|
removeItem: RemoveItemHandler<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CustomerAddressSchema<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
export type CustomerAddressSchema<
|
||||||
|
T extends CustomerAddressTypes = CustomerAddressTypes
|
||||||
|
> = {
|
||||||
endpoint: {
|
endpoint: {
|
||||||
options: {}
|
options: {}
|
||||||
handlers: CustomerAddressHandlers<T>
|
handlers: CustomerAddressHandlers<T>
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
export interface Card {
|
export interface Card {
|
||||||
id: string;
|
id: string
|
||||||
mask: string;
|
mask: string
|
||||||
provider: string;
|
provider: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardFields {
|
export interface CardFields {
|
||||||
cardHolder: string;
|
cardHolder: string
|
||||||
cardNumber: string;
|
cardNumber: string
|
||||||
cardExpireDate: string;
|
cardExpireDate: string
|
||||||
cardCvc: string;
|
cardCvc: string
|
||||||
firstName: string;
|
firstName: string
|
||||||
lastName: string;
|
lastName: string
|
||||||
company: string;
|
company: string
|
||||||
streetNumber: string;
|
streetNumber: string
|
||||||
zipCode: string;
|
zipCode: string
|
||||||
city: string;
|
city: string
|
||||||
country: string;
|
country: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CustomerCardTypes = {
|
export type CustomerCardTypes = {
|
||||||
card?: Card;
|
card?: Card
|
||||||
fields: CardFields;
|
fields: CardFields
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetCardsHook<T extends CustomerCardTypes = CustomerCardTypes> = {
|
export type GetCardsHook<T extends CustomerCardTypes = CustomerCardTypes> = {
|
||||||
data: T['card'] | null
|
data: T['card'][] | null
|
||||||
input: {}
|
input: {}
|
||||||
fetcherInput: { cartId?: string }
|
fetcherInput: { cartId?: string }
|
||||||
swrState: { isEmpty: boolean }
|
swrState: { isEmpty: boolean }
|
||||||
@ -48,26 +48,29 @@ export type UpdateItemHook<T extends CustomerCardTypes = CustomerCardTypes> = {
|
|||||||
|
|
||||||
export type RemoveItemHook<T extends CustomerCardTypes = CustomerCardTypes> = {
|
export type RemoveItemHook<T extends CustomerCardTypes = CustomerCardTypes> = {
|
||||||
data: T['card'] | null
|
data: T['card'] | null
|
||||||
input: { item?: T['fields'] }
|
input: { item?: T['card'] }
|
||||||
fetcherInput: { itemId: string }
|
fetcherInput: { itemId: string }
|
||||||
body: { itemId: string }
|
body: { itemId: string }
|
||||||
actionInput: { id: string }
|
actionInput: { id: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CustomerCardHooks<T extends CustomerCardTypes = CustomerCardTypes> = {
|
export type CustomerCardHooks<T extends CustomerCardTypes = CustomerCardTypes> =
|
||||||
|
{
|
||||||
getCards: GetCardsHook<T>
|
getCards: GetCardsHook<T>
|
||||||
addItem: AddItemHook<T>
|
addItem: AddItemHook<T>
|
||||||
updateItem: UpdateItemHook<T>
|
updateItem: UpdateItemHook<T>
|
||||||
removeItem: RemoveItemHook<T>
|
removeItem: RemoveItemHook<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CardsHandler<T extends CustomerCardTypes = CustomerCardTypes> = GetCardsHook<T> & {
|
export type CardsHandler<T extends CustomerCardTypes = CustomerCardTypes> =
|
||||||
|
GetCardsHook<T> & {
|
||||||
body: { cartId?: string }
|
body: { cartId?: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddItemHandler<T extends CustomerCardTypes = CustomerCardTypes> = AddItemHook<T> & {
|
export type AddItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
|
||||||
|
AddItemHook<T> & {
|
||||||
body: { cartId: string }
|
body: { cartId: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
|
export type UpdateItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
|
||||||
UpdateItemHook<T> & {
|
UpdateItemHook<T> & {
|
||||||
@ -80,15 +83,18 @@ export type RemoveItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
|
|||||||
body: { cartId: string }
|
body: { cartId: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CustomerCardHandlers<
|
||||||
export type CustomerCardHandlers<T extends CustomerCardTypes = CustomerCardTypes> = {
|
T extends CustomerCardTypes = CustomerCardTypes
|
||||||
|
> = {
|
||||||
getCards: GetCardsHook<T>
|
getCards: GetCardsHook<T>
|
||||||
addItem: AddItemHandler<T>
|
addItem: AddItemHandler<T>
|
||||||
updateItem: UpdateItemHandler<T>
|
updateItem: UpdateItemHandler<T>
|
||||||
removeItem: RemoveItemHandler<T>
|
removeItem: RemoveItemHandler<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CustomerCardSchema<T extends CustomerCardTypes = CustomerCardTypes> = {
|
export type CustomerCardSchema<
|
||||||
|
T extends CustomerCardTypes = CustomerCardTypes
|
||||||
|
> = {
|
||||||
endpoint: {
|
endpoint: {
|
||||||
options: {}
|
options: {}
|
||||||
handlers: CustomerCardHandlers<T>
|
handlers: CustomerCardHandlers<T>
|
||||||
|
5
framework/ordercloud/.env.template
Normal file
5
framework/ordercloud/.env.template
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
COMMERCE_PROVIDER=ordercloud
|
||||||
|
|
||||||
|
ORDERCLOUD_CLIENT_ID=
|
||||||
|
ORDERCLOUD_CLIENT_SECRET=
|
||||||
|
STRIPE_SECRET=
|
3
framework/ordercloud/README.md
Normal file
3
framework/ordercloud/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Next.js Ordercloud Provider
|
||||||
|
|
||||||
|
Create your own store from [here](https://nextjs.org/commerce)
|
99
framework/ordercloud/api/endpoints/cart/add-item.ts
Normal file
99
framework/ordercloud/api/endpoints/cart/add-item.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import type { CartEndpoint } from '.'
|
||||||
|
import type { RawVariant } from '../../../types/product'
|
||||||
|
import type { OrdercloudLineItem } from '../../../types/cart'
|
||||||
|
|
||||||
|
import { serialize } from 'cookie'
|
||||||
|
|
||||||
|
import { formatCart } from '../../utils/cart'
|
||||||
|
|
||||||
|
const addItem: CartEndpoint['handlers']['addItem'] = async ({
|
||||||
|
res,
|
||||||
|
body: { cartId, item },
|
||||||
|
config: { restBuyerFetch, cartCookie, tokenCookie },
|
||||||
|
}) => {
|
||||||
|
// Return an error if no item is present
|
||||||
|
if (!item) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Missing item' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store token
|
||||||
|
let token
|
||||||
|
|
||||||
|
// Set the quantity if not present
|
||||||
|
if (!item.quantity) item.quantity = 1
|
||||||
|
|
||||||
|
// Create an order if it doesn't exist
|
||||||
|
if (!cartId) {
|
||||||
|
const { ID, meta } = await restBuyerFetch(
|
||||||
|
'POST',
|
||||||
|
`/orders/Outgoing`,
|
||||||
|
{}
|
||||||
|
).then((response: { ID: string; meta: { token: string } }) => response)
|
||||||
|
|
||||||
|
// Set the cart id and token
|
||||||
|
cartId = ID
|
||||||
|
token = meta.token
|
||||||
|
|
||||||
|
// Set the cart and token cookie
|
||||||
|
res.setHeader('Set-Cookie', [
|
||||||
|
serialize(tokenCookie, meta.token, {
|
||||||
|
maxAge: 60 * 60 * 24 * 30,
|
||||||
|
expires: new Date(Date.now() + 60 * 60 * 24 * 30 * 1000),
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
path: '/',
|
||||||
|
sameSite: 'lax',
|
||||||
|
}),
|
||||||
|
serialize(cartCookie, cartId, {
|
||||||
|
maxAge: 60 * 60 * 24 * 30,
|
||||||
|
expires: new Date(Date.now() + 60 * 60 * 24 * 30 * 1000),
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
path: '/',
|
||||||
|
sameSite: 'lax',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store specs
|
||||||
|
let specs: RawVariant['Specs'] = []
|
||||||
|
|
||||||
|
// If a variant is present, fetch its specs
|
||||||
|
if (item.variantId) {
|
||||||
|
specs = await restBuyerFetch(
|
||||||
|
'GET',
|
||||||
|
`/me/products/${item.productId}/variants/${item.variantId}`,
|
||||||
|
null,
|
||||||
|
{ token }
|
||||||
|
).then((res: RawVariant) => res.Specs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the item to the order
|
||||||
|
await restBuyerFetch(
|
||||||
|
'POST',
|
||||||
|
`/orders/Outgoing/${cartId}/lineitems`,
|
||||||
|
{
|
||||||
|
ProductID: item.productId,
|
||||||
|
Quantity: item.quantity,
|
||||||
|
Specs: specs,
|
||||||
|
},
|
||||||
|
{ token }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get cart
|
||||||
|
const [cart, lineItems] = await Promise.all([
|
||||||
|
restBuyerFetch('GET', `/orders/Outgoing/${cartId}`, null, { token }),
|
||||||
|
restBuyerFetch('GET', `/orders/Outgoing/${cartId}/lineitems`, null, {
|
||||||
|
token,
|
||||||
|
}).then((response: { Items: OrdercloudLineItem[] }) => response.Items),
|
||||||
|
])
|
||||||
|
|
||||||
|
// Format cart
|
||||||
|
const formattedCart = formatCart(cart, lineItems)
|
||||||
|
|
||||||
|
// Return cart and errors
|
||||||
|
res.status(200).json({ data: formattedCart, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default addItem
|
65
framework/ordercloud/api/endpoints/cart/get-cart.ts
Normal file
65
framework/ordercloud/api/endpoints/cart/get-cart.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import type { OrdercloudLineItem } from '../../../types/cart'
|
||||||
|
import type { CartEndpoint } from '.'
|
||||||
|
|
||||||
|
import { serialize } from 'cookie'
|
||||||
|
|
||||||
|
import { formatCart } from '../../utils/cart'
|
||||||
|
|
||||||
|
// Return current cart info
|
||||||
|
const getCart: CartEndpoint['handlers']['getCart'] = async ({
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
body: { cartId },
|
||||||
|
config: { restBuyerFetch, cartCookie, tokenCookie },
|
||||||
|
}) => {
|
||||||
|
if (!cartId) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Invalid request' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get token from cookies
|
||||||
|
const token = req.cookies[tokenCookie]
|
||||||
|
|
||||||
|
// Get cart
|
||||||
|
const cart = await restBuyerFetch(
|
||||||
|
'GET',
|
||||||
|
`/orders/Outgoing/${cartId}`,
|
||||||
|
null,
|
||||||
|
{ token }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get line items
|
||||||
|
const lineItems = await restBuyerFetch(
|
||||||
|
'GET',
|
||||||
|
`/orders/Outgoing/${cartId}/lineitems`,
|
||||||
|
null,
|
||||||
|
{ token }
|
||||||
|
).then((response: { Items: OrdercloudLineItem[] }) => response.Items)
|
||||||
|
|
||||||
|
// Format cart
|
||||||
|
const formattedCart = formatCart(cart, lineItems)
|
||||||
|
|
||||||
|
// Return cart and errors
|
||||||
|
res.status(200).json({ data: formattedCart, errors: [] })
|
||||||
|
} catch (error) {
|
||||||
|
// Reset cart and token cookie
|
||||||
|
res.setHeader('Set-Cookie', [
|
||||||
|
serialize(cartCookie, cartId, {
|
||||||
|
maxAge: -1,
|
||||||
|
path: '/',
|
||||||
|
}),
|
||||||
|
serialize(tokenCookie, cartId, {
|
||||||
|
maxAge: -1,
|
||||||
|
path: '/',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
// Return empty cart
|
||||||
|
res.status(200).json({ data: null, errors: [] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getCart
|
28
framework/ordercloud/api/endpoints/cart/index.ts
Normal file
28
framework/ordercloud/api/endpoints/cart/index.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { CartSchema } from '../../../types/cart'
|
||||||
|
import type { OrdercloudAPI } from '../..'
|
||||||
|
|
||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import cartEndpoint from '@commerce/api/endpoints/cart'
|
||||||
|
|
||||||
|
import getCart from './get-cart'
|
||||||
|
import addItem from './add-item'
|
||||||
|
import updateItem from './update-item'
|
||||||
|
import removeItem from './remove-item'
|
||||||
|
|
||||||
|
export type CartAPI = GetAPISchema<OrdercloudAPI, CartSchema>
|
||||||
|
|
||||||
|
export type CartEndpoint = CartAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: CartEndpoint['handlers'] = {
|
||||||
|
getCart,
|
||||||
|
addItem,
|
||||||
|
updateItem,
|
||||||
|
removeItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
const cartApi = createEndpoint<CartAPI>({
|
||||||
|
handler: cartEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default cartApi
|
45
framework/ordercloud/api/endpoints/cart/remove-item.ts
Normal file
45
framework/ordercloud/api/endpoints/cart/remove-item.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import type { CartEndpoint } from '.'
|
||||||
|
|
||||||
|
import { formatCart } from '../../utils/cart'
|
||||||
|
import { OrdercloudLineItem } from '../../../types/cart'
|
||||||
|
|
||||||
|
const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
body: { cartId, itemId },
|
||||||
|
config: { restBuyerFetch, tokenCookie },
|
||||||
|
}) => {
|
||||||
|
if (!cartId || !itemId) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Invalid request' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get token from cookies
|
||||||
|
const token = req.cookies[tokenCookie]
|
||||||
|
|
||||||
|
// Remove the item to the order
|
||||||
|
await restBuyerFetch(
|
||||||
|
'DELETE',
|
||||||
|
`/orders/Outgoing/${cartId}/lineitems/${itemId}`,
|
||||||
|
null,
|
||||||
|
{ token }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get cart
|
||||||
|
const [cart, lineItems] = await Promise.all([
|
||||||
|
restBuyerFetch('GET', `/orders/Outgoing/${cartId}`, null, { token }),
|
||||||
|
restBuyerFetch('GET', `/orders/Outgoing/${cartId}/lineitems`, null, {
|
||||||
|
token,
|
||||||
|
}).then((response: { Items: OrdercloudLineItem[] }) => response.Items),
|
||||||
|
])
|
||||||
|
|
||||||
|
// Format cart
|
||||||
|
const formattedCart = formatCart(cart, lineItems)
|
||||||
|
|
||||||
|
// Return cart and errors
|
||||||
|
res.status(200).json({ data: formattedCart, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default removeItem
|
63
framework/ordercloud/api/endpoints/cart/update-item.ts
Normal file
63
framework/ordercloud/api/endpoints/cart/update-item.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import type { OrdercloudLineItem } from '../../../types/cart'
|
||||||
|
import type { RawVariant } from '../../../types/product'
|
||||||
|
import type { CartEndpoint } from '.'
|
||||||
|
|
||||||
|
import { formatCart } from '../../utils/cart'
|
||||||
|
|
||||||
|
const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
body: { cartId, itemId, item },
|
||||||
|
config: { restBuyerFetch, tokenCookie },
|
||||||
|
}) => {
|
||||||
|
if (!cartId || !itemId || !item) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Invalid request' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get token from cookies
|
||||||
|
const token = req.cookies[tokenCookie]
|
||||||
|
|
||||||
|
// Store specs
|
||||||
|
let specs: RawVariant['Specs'] = []
|
||||||
|
|
||||||
|
// If a variant is present, fetch its specs
|
||||||
|
if (item.variantId) {
|
||||||
|
specs = await restBuyerFetch(
|
||||||
|
'GET',
|
||||||
|
`/me/products/${item.productId}/variants/${item.variantId}`,
|
||||||
|
null,
|
||||||
|
{ token }
|
||||||
|
).then((res: RawVariant) => res.Specs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the item to the order
|
||||||
|
await restBuyerFetch(
|
||||||
|
'PATCH',
|
||||||
|
`/orders/Outgoing/${cartId}/lineitems/${itemId}`,
|
||||||
|
{
|
||||||
|
ProductID: item.productId,
|
||||||
|
Quantity: item.quantity,
|
||||||
|
Specs: specs,
|
||||||
|
},
|
||||||
|
{ token }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get cart
|
||||||
|
const [cart, lineItems] = await Promise.all([
|
||||||
|
restBuyerFetch('GET', `/orders/Outgoing/${cartId}`, null, { token }),
|
||||||
|
restBuyerFetch('GET', `/orders/Outgoing/${cartId}/lineitems`, null, {
|
||||||
|
token,
|
||||||
|
}).then((response: { Items: OrdercloudLineItem[] }) => response.Items),
|
||||||
|
])
|
||||||
|
|
||||||
|
// Format cart
|
||||||
|
const formattedCart = formatCart(cart, lineItems)
|
||||||
|
|
||||||
|
// Return cart and errors
|
||||||
|
res.status(200).json({ data: formattedCart, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateItem
|
1
framework/ordercloud/api/endpoints/catalog/index.ts
Normal file
1
framework/ordercloud/api/endpoints/catalog/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/ordercloud/api/endpoints/catalog/products.ts
Normal file
1
framework/ordercloud/api/endpoints/catalog/products.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
47
framework/ordercloud/api/endpoints/checkout/get-checkout.ts
Normal file
47
framework/ordercloud/api/endpoints/checkout/get-checkout.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import type { CheckoutEndpoint } from '.'
|
||||||
|
|
||||||
|
const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
body: { cartId },
|
||||||
|
config: { restBuyerFetch, tokenCookie },
|
||||||
|
}) => {
|
||||||
|
// Return an error if no item is present
|
||||||
|
if (!cartId) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Missing cookie' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get token from cookies
|
||||||
|
const token = req.cookies[tokenCookie]
|
||||||
|
|
||||||
|
// Register credit card
|
||||||
|
const payments = await restBuyerFetch(
|
||||||
|
'GET',
|
||||||
|
`/orders/Outgoing/${cartId}/payments`,
|
||||||
|
null,
|
||||||
|
{ token }
|
||||||
|
).then((response: { Items: unknown[] }) => response.Items)
|
||||||
|
|
||||||
|
const address = await restBuyerFetch(
|
||||||
|
'GET',
|
||||||
|
`/orders/Outgoing/${cartId}`,
|
||||||
|
null,
|
||||||
|
{ token }
|
||||||
|
).then(
|
||||||
|
(response: { ShippingAddressID: string }) => response.ShippingAddressID
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return cart and errors
|
||||||
|
res.status(200).json({
|
||||||
|
data: {
|
||||||
|
hasPayment: payments.length > 0,
|
||||||
|
hasShipping: Boolean(address),
|
||||||
|
},
|
||||||
|
errors: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getCheckout
|
23
framework/ordercloud/api/endpoints/checkout/index.ts
Normal file
23
framework/ordercloud/api/endpoints/checkout/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import type { CheckoutSchema } from '../../../types/checkout'
|
||||||
|
import type { OrdercloudAPI } from '../..'
|
||||||
|
|
||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import checkoutEndpoint from '@commerce/api/endpoints/checkout'
|
||||||
|
|
||||||
|
import getCheckout from './get-checkout'
|
||||||
|
import submitCheckout from './submit-checkout'
|
||||||
|
|
||||||
|
export type CheckoutAPI = GetAPISchema<OrdercloudAPI, CheckoutSchema>
|
||||||
|
export type CheckoutEndpoint = CheckoutAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: CheckoutEndpoint['handlers'] = {
|
||||||
|
getCheckout,
|
||||||
|
submitCheckout,
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkoutApi = createEndpoint<CheckoutAPI>({
|
||||||
|
handler: checkoutEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default checkoutApi
|
@ -0,0 +1,32 @@
|
|||||||
|
import type { CheckoutEndpoint } from '.'
|
||||||
|
|
||||||
|
const submitCheckout: CheckoutEndpoint['handlers']['submitCheckout'] = async ({
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
body: { cartId },
|
||||||
|
config: { restBuyerFetch, tokenCookie },
|
||||||
|
}) => {
|
||||||
|
// Return an error if no item is present
|
||||||
|
if (!cartId) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Missing item' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get token from cookies
|
||||||
|
const token = req.cookies[tokenCookie]
|
||||||
|
|
||||||
|
// Submit order
|
||||||
|
await restBuyerFetch(
|
||||||
|
'POST',
|
||||||
|
`/orders/Outgoing/${cartId}/submit`,
|
||||||
|
{},
|
||||||
|
{ token }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return cart and errors
|
||||||
|
res.status(200).json({ data: null, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default submitCheckout
|
@ -0,0 +1,47 @@
|
|||||||
|
import type { CustomerAddressEndpoint } from '.'
|
||||||
|
|
||||||
|
const addItem: CustomerAddressEndpoint['handlers']['addItem'] = async ({
|
||||||
|
res,
|
||||||
|
body: { item, cartId },
|
||||||
|
config: { restBuyerFetch },
|
||||||
|
}) => {
|
||||||
|
// Return an error if no item is present
|
||||||
|
if (!item) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Missing item' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an error if no item is present
|
||||||
|
if (!cartId) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Cookie not found' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register address
|
||||||
|
const address = await restBuyerFetch('POST', `/me/addresses`, {
|
||||||
|
AddressName: 'main address',
|
||||||
|
CompanyName: item.company,
|
||||||
|
FirstName: item.firstName,
|
||||||
|
LastName: item.lastName,
|
||||||
|
Street1: item.streetNumber,
|
||||||
|
Street2: item.streetNumber,
|
||||||
|
City: item.city,
|
||||||
|
State: item.city,
|
||||||
|
Zip: item.zipCode,
|
||||||
|
Country: item.country.slice(0, 2).toLowerCase(),
|
||||||
|
Shipping: true,
|
||||||
|
}).then((response: { ID: string }) => response.ID)
|
||||||
|
|
||||||
|
// Assign address to order
|
||||||
|
await restBuyerFetch('PATCH', `/orders/Outgoing/${cartId}`, {
|
||||||
|
ShippingAddressID: address,
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(200).json({ data: null, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default addItem
|
@ -0,0 +1,9 @@
|
|||||||
|
import type { CustomerAddressEndpoint } from '.'
|
||||||
|
|
||||||
|
const getCards: CustomerAddressEndpoint['handlers']['getAddresses'] = async ({
|
||||||
|
res,
|
||||||
|
}) => {
|
||||||
|
return res.status(200).json({ data: null, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getCards
|
27
framework/ordercloud/api/endpoints/customer/address/index.ts
Normal file
27
framework/ordercloud/api/endpoints/customer/address/index.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import type { CustomerAddressSchema } from '../../../../types/customer/address'
|
||||||
|
import type { OrdercloudAPI } from '../../..'
|
||||||
|
|
||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import customerAddressEndpoint from '@commerce/api/endpoints/customer/address'
|
||||||
|
|
||||||
|
import getAddresses from './get-addresses'
|
||||||
|
import addItem from './add-item'
|
||||||
|
import updateItem from './update-item'
|
||||||
|
import removeItem from './remove-item'
|
||||||
|
|
||||||
|
export type CustomerAddressAPI = GetAPISchema<OrdercloudAPI, CustomerAddressSchema>
|
||||||
|
export type CustomerAddressEndpoint = CustomerAddressAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: CustomerAddressEndpoint['handlers'] = {
|
||||||
|
getAddresses,
|
||||||
|
addItem,
|
||||||
|
updateItem,
|
||||||
|
removeItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
const customerAddressApi = createEndpoint<CustomerAddressAPI>({
|
||||||
|
handler: customerAddressEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default customerAddressApi
|
@ -0,0 +1,9 @@
|
|||||||
|
import type { CustomerAddressEndpoint } from '.'
|
||||||
|
|
||||||
|
const removeItem: CustomerAddressEndpoint['handlers']['removeItem'] = async ({
|
||||||
|
res,
|
||||||
|
}) => {
|
||||||
|
return res.status(200).json({ data: null, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default removeItem
|
@ -0,0 +1,9 @@
|
|||||||
|
import type { CustomerAddressEndpoint } from '.'
|
||||||
|
|
||||||
|
const updateItem: CustomerAddressEndpoint['handlers']['updateItem'] = async ({
|
||||||
|
res,
|
||||||
|
}) => {
|
||||||
|
return res.status(200).json({ data: null, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateItem
|
74
framework/ordercloud/api/endpoints/customer/card/add-item.ts
Normal file
74
framework/ordercloud/api/endpoints/customer/card/add-item.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import type { CustomerCardEndpoint } from '.'
|
||||||
|
import type { OredercloudCreditCard } from '../../../../types/customer/card'
|
||||||
|
|
||||||
|
import Stripe from 'stripe'
|
||||||
|
|
||||||
|
const stripe = new Stripe(process.env.STRIPE_SECRET as string, {
|
||||||
|
apiVersion: '2020-08-27',
|
||||||
|
})
|
||||||
|
|
||||||
|
const addItem: CustomerCardEndpoint['handlers']['addItem'] = async ({
|
||||||
|
res,
|
||||||
|
body: { item, cartId },
|
||||||
|
config: { restBuyerFetch, restMiddlewareFetch },
|
||||||
|
}) => {
|
||||||
|
// Return an error if no item is present
|
||||||
|
if (!item) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Missing item' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an error if no item is present
|
||||||
|
if (!cartId) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Cookie not found' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get token
|
||||||
|
const token = await stripe.tokens
|
||||||
|
.create({
|
||||||
|
card: {
|
||||||
|
number: item.cardNumber,
|
||||||
|
exp_month: item.cardExpireDate.split('/')[0],
|
||||||
|
exp_year: item.cardExpireDate.split('/')[1],
|
||||||
|
cvc: item.cardCvc,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res: { id: string }) => res.id)
|
||||||
|
|
||||||
|
// Register credit card
|
||||||
|
const creditCard = await restBuyerFetch('POST', `/me/creditcards`, {
|
||||||
|
Token: token,
|
||||||
|
CardType: 'credit',
|
||||||
|
PartialAccountNumber: item.cardNumber.slice(-4),
|
||||||
|
CardholderName: item.cardHolder,
|
||||||
|
ExpirationDate: item.cardExpireDate,
|
||||||
|
}).then((response: OredercloudCreditCard) => response.ID)
|
||||||
|
|
||||||
|
// Assign payment to order
|
||||||
|
const payment = await restBuyerFetch(
|
||||||
|
'POST',
|
||||||
|
`/orders/All/${cartId}/payments`,
|
||||||
|
{
|
||||||
|
Type: 'CreditCard',
|
||||||
|
CreditCardID: creditCard,
|
||||||
|
}
|
||||||
|
).then((response: { ID: string }) => response.ID)
|
||||||
|
|
||||||
|
// Accept payment to order
|
||||||
|
await restMiddlewareFetch(
|
||||||
|
'PATCH',
|
||||||
|
`/orders/All/${cartId}/payments/${payment}`,
|
||||||
|
{
|
||||||
|
Accepted: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.status(200).json({ data: null, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default addItem
|
@ -0,0 +1,9 @@
|
|||||||
|
import type { CustomerCardEndpoint } from '.'
|
||||||
|
|
||||||
|
const getCards: CustomerCardEndpoint['handlers']['getCards'] = async ({
|
||||||
|
res,
|
||||||
|
}) => {
|
||||||
|
return res.status(200).json({ data: null, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getCards
|
27
framework/ordercloud/api/endpoints/customer/card/index.ts
Normal file
27
framework/ordercloud/api/endpoints/customer/card/index.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import type { CustomerCardSchema } from '../../../../types/customer/card'
|
||||||
|
import type { OrdercloudAPI } from '../../..'
|
||||||
|
|
||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import customerCardEndpoint from '@commerce/api/endpoints/customer/card'
|
||||||
|
|
||||||
|
import getCards from './get-cards'
|
||||||
|
import addItem from './add-item'
|
||||||
|
import updateItem from './update-item'
|
||||||
|
import removeItem from './remove-item'
|
||||||
|
|
||||||
|
export type CustomerCardAPI = GetAPISchema<OrdercloudAPI, CustomerCardSchema>
|
||||||
|
export type CustomerCardEndpoint = CustomerCardAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: CustomerCardEndpoint['handlers'] = {
|
||||||
|
getCards,
|
||||||
|
addItem,
|
||||||
|
updateItem,
|
||||||
|
removeItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
const customerCardApi = createEndpoint<CustomerCardAPI>({
|
||||||
|
handler: customerCardEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default customerCardApi
|
@ -0,0 +1,9 @@
|
|||||||
|
import type { CustomerCardEndpoint } from '.'
|
||||||
|
|
||||||
|
const removeItem: CustomerCardEndpoint['handlers']['removeItem'] = async ({
|
||||||
|
res,
|
||||||
|
}) => {
|
||||||
|
return res.status(200).json({ data: null, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default removeItem
|
@ -0,0 +1,9 @@
|
|||||||
|
import type { CustomerCardEndpoint } from '.'
|
||||||
|
|
||||||
|
const updateItem: CustomerCardEndpoint['handlers']['updateItem'] = async ({
|
||||||
|
res,
|
||||||
|
}) => {
|
||||||
|
return res.status(200).json({ data: null, errors: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateItem
|
1
framework/ordercloud/api/endpoints/customer/index.ts
Normal file
1
framework/ordercloud/api/endpoints/customer/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/ordercloud/api/endpoints/login/index.ts
Normal file
1
framework/ordercloud/api/endpoints/login/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/ordercloud/api/endpoints/logout/index.ts
Normal file
1
framework/ordercloud/api/endpoints/logout/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/ordercloud/api/endpoints/signup/index.ts
Normal file
1
framework/ordercloud/api/endpoints/signup/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/ordercloud/api/endpoints/wishlist/index.tsx
Normal file
1
framework/ordercloud/api/endpoints/wishlist/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
71
framework/ordercloud/api/index.ts
Normal file
71
framework/ordercloud/api/index.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import type { CommerceAPI, CommerceAPIConfig } from '@commerce/api'
|
||||||
|
import { getCommerceApi as commerceApi } from '@commerce/api'
|
||||||
|
import { createBuyerFetcher, createMiddlewareFetcher } from './utils/fetch-rest'
|
||||||
|
import createGraphqlFetcher from './utils/fetch-graphql'
|
||||||
|
|
||||||
|
import getAllPages from './operations/get-all-pages'
|
||||||
|
import getPage from './operations/get-page'
|
||||||
|
import getSiteInfo from './operations/get-site-info'
|
||||||
|
import getAllProductPaths from './operations/get-all-product-paths'
|
||||||
|
import getAllProducts from './operations/get-all-products'
|
||||||
|
import getProduct from './operations/get-product'
|
||||||
|
|
||||||
|
import {
|
||||||
|
API_URL,
|
||||||
|
API_VERSION,
|
||||||
|
CART_COOKIE,
|
||||||
|
CUSTOMER_COOKIE,
|
||||||
|
TOKEN_COOKIE,
|
||||||
|
} from '../constants'
|
||||||
|
|
||||||
|
export interface OrdercloudConfig extends CommerceAPIConfig {
|
||||||
|
restBuyerFetch: <T>(
|
||||||
|
method: string,
|
||||||
|
resource: string,
|
||||||
|
body?: Record<string, unknown>,
|
||||||
|
fetchOptions?: Record<string, any>
|
||||||
|
) => Promise<T>
|
||||||
|
restMiddlewareFetch: <T>(
|
||||||
|
method: string,
|
||||||
|
resource: string,
|
||||||
|
body?: Record<string, unknown>,
|
||||||
|
fetchOptions?: Record<string, any>
|
||||||
|
) => Promise<T>
|
||||||
|
apiVersion: string
|
||||||
|
tokenCookie: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const config: OrdercloudConfig = {
|
||||||
|
commerceUrl: API_URL,
|
||||||
|
apiToken: '',
|
||||||
|
apiVersion: API_VERSION,
|
||||||
|
cartCookie: CART_COOKIE,
|
||||||
|
customerCookie: CUSTOMER_COOKIE,
|
||||||
|
tokenCookie: TOKEN_COOKIE,
|
||||||
|
cartCookieMaxAge: 2592000,
|
||||||
|
restBuyerFetch: createBuyerFetcher(() => getCommerceApi().getConfig()),
|
||||||
|
restMiddlewareFetch: createMiddlewareFetcher(() =>
|
||||||
|
getCommerceApi().getConfig()
|
||||||
|
),
|
||||||
|
fetch: createGraphqlFetcher(() => getCommerceApi().getConfig()),
|
||||||
|
}
|
||||||
|
|
||||||
|
const operations = {
|
||||||
|
getAllPages,
|
||||||
|
getPage,
|
||||||
|
getSiteInfo,
|
||||||
|
getAllProductPaths,
|
||||||
|
getAllProducts,
|
||||||
|
getProduct,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const provider = { config, operations }
|
||||||
|
|
||||||
|
export type Provider = typeof provider
|
||||||
|
export type OrdercloudAPI<P extends Provider = Provider> = CommerceAPI<P | any>
|
||||||
|
|
||||||
|
export function getCommerceApi<P extends Provider>(
|
||||||
|
customProvider: P = provider as any
|
||||||
|
): OrdercloudAPI<P> {
|
||||||
|
return commerceApi(customProvider as any)
|
||||||
|
}
|
22
framework/ordercloud/api/operations/get-all-pages.ts
Normal file
22
framework/ordercloud/api/operations/get-all-pages.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import type { OrdercloudConfig } from '../'
|
||||||
|
|
||||||
|
import { GetAllPagesOperation } from '@commerce/types/page'
|
||||||
|
|
||||||
|
export type Page = { url: string }
|
||||||
|
export type GetAllPagesResult = { pages: Page[] }
|
||||||
|
|
||||||
|
export default function getAllPagesOperation() {
|
||||||
|
async function getAllPages<T extends GetAllPagesOperation>({
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
}: {
|
||||||
|
url?: string
|
||||||
|
config?: Partial<OrdercloudConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<T['data']> {
|
||||||
|
return Promise.resolve({
|
||||||
|
pages: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return getAllPages
|
||||||
|
}
|
34
framework/ordercloud/api/operations/get-all-product-paths.ts
Normal file
34
framework/ordercloud/api/operations/get-all-product-paths.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
|
import type { GetAllProductPathsOperation } from '@commerce/types/product'
|
||||||
|
|
||||||
|
import type { RawProduct } from '../../types/product'
|
||||||
|
import type { OrdercloudConfig, Provider } from '../'
|
||||||
|
|
||||||
|
export type GetAllProductPathsResult = {
|
||||||
|
products: Array<{ path: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function getAllProductPathsOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function getAllProductPaths<T extends GetAllProductPathsOperation>({
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
config?: Partial<OrdercloudConfig>
|
||||||
|
} = {}): Promise<T['data']> {
|
||||||
|
// Get fetch from the config
|
||||||
|
const { restBuyerFetch } = commerce.getConfig(config)
|
||||||
|
|
||||||
|
// Get all products
|
||||||
|
const rawProducts: RawProduct[] = await restBuyerFetch<{
|
||||||
|
Items: RawProduct[]
|
||||||
|
}>('GET', '/me/products').then((response) => response.Items)
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Match a path for every product retrieved
|
||||||
|
products: rawProducts.map((product) => ({ path: `/${product.ID}` })),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAllProductPaths
|
||||||
|
}
|
35
framework/ordercloud/api/operations/get-all-products.ts
Normal file
35
framework/ordercloud/api/operations/get-all-products.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type { GetAllProductsOperation } from '@commerce/types/product'
|
||||||
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
|
|
||||||
|
import type { RawProduct } from '../../types/product'
|
||||||
|
import type { OrdercloudConfig, Provider } from '../index'
|
||||||
|
|
||||||
|
import { normalize as normalizeProduct } from '../../utils/product'
|
||||||
|
|
||||||
|
export default function getAllProductsOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function getAllProducts<T extends GetAllProductsOperation>({
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables?: T['variables']
|
||||||
|
config?: Partial<OrdercloudConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<T['data']> {
|
||||||
|
// Get fetch from the config
|
||||||
|
const { restBuyerFetch } = commerce.getConfig(config)
|
||||||
|
|
||||||
|
// Get all products
|
||||||
|
const rawProducts: RawProduct[] = await restBuyerFetch<{
|
||||||
|
Items: RawProduct[]
|
||||||
|
}>('GET', '/me/products').then((response) => response.Items)
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Normalize products to commerce schema
|
||||||
|
products: rawProducts.map(normalizeProduct),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAllProducts
|
||||||
|
}
|
15
framework/ordercloud/api/operations/get-page.ts
Normal file
15
framework/ordercloud/api/operations/get-page.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { GetPageOperation } from "@commerce/types/page"
|
||||||
|
|
||||||
|
export type Page = any
|
||||||
|
export type GetPageResult = { page?: Page }
|
||||||
|
|
||||||
|
export type PageVariables = {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function getPageOperation() {
|
||||||
|
async function getPage<T extends GetPageOperation>(): Promise<T['data']> {
|
||||||
|
return Promise.resolve({})
|
||||||
|
}
|
||||||
|
return getPage
|
||||||
|
}
|
60
framework/ordercloud/api/operations/get-product.ts
Normal file
60
framework/ordercloud/api/operations/get-product.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
|
import type { GetProductOperation } from '@commerce/types/product'
|
||||||
|
|
||||||
|
import type { RawProduct, RawSpec, RawVariant } from '../../types/product'
|
||||||
|
import type { OrdercloudConfig, Provider } from '../index'
|
||||||
|
|
||||||
|
import { normalize as normalizeProduct } from '../../utils/product'
|
||||||
|
|
||||||
|
export default function getProductOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function getProduct<T extends GetProductOperation>({
|
||||||
|
config,
|
||||||
|
variables,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables?: T['variables']
|
||||||
|
config?: Partial<OrdercloudConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<T['data']> {
|
||||||
|
// Get fetch from the config
|
||||||
|
const { restBuyerFetch } = commerce.getConfig(config)
|
||||||
|
|
||||||
|
// Get a single product
|
||||||
|
const productPromise = restBuyerFetch<RawProduct>(
|
||||||
|
'GET',
|
||||||
|
`/me/products/${variables?.slug}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get product specs
|
||||||
|
const specsPromise = restBuyerFetch<{ Items: RawSpec[] }>(
|
||||||
|
'GET',
|
||||||
|
`/me/products/${variables?.slug}/specs`
|
||||||
|
).then((res) => res.Items)
|
||||||
|
|
||||||
|
// Get product variants
|
||||||
|
const variantsPromise = restBuyerFetch<{ Items: RawVariant[] }>(
|
||||||
|
'GET',
|
||||||
|
`/me/products/${variables?.slug}/variants`
|
||||||
|
).then((res) => res.Items)
|
||||||
|
|
||||||
|
// Execute all promises in parallel
|
||||||
|
const [product, specs, variants] = await Promise.all([
|
||||||
|
productPromise,
|
||||||
|
specsPromise,
|
||||||
|
variantsPromise,
|
||||||
|
])
|
||||||
|
|
||||||
|
// Hydrate product
|
||||||
|
product.xp.Specs = specs
|
||||||
|
product.xp.Variants = variants
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Normalize product to commerce schema
|
||||||
|
product: normalizeProduct(product),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getProduct
|
||||||
|
}
|
46
framework/ordercloud/api/operations/get-site-info.ts
Normal file
46
framework/ordercloud/api/operations/get-site-info.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
|
import type { Category, GetSiteInfoOperation } from '@commerce/types/site'
|
||||||
|
|
||||||
|
import type { RawCategory } from '../../types/category'
|
||||||
|
import type { OrdercloudConfig, Provider } from '../index'
|
||||||
|
|
||||||
|
export type GetSiteInfoResult<
|
||||||
|
T extends { categories: any[]; brands: any[] } = {
|
||||||
|
categories: Category[]
|
||||||
|
brands: any[]
|
||||||
|
}
|
||||||
|
> = T
|
||||||
|
|
||||||
|
export default function getSiteInfoOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function getSiteInfo<T extends GetSiteInfoOperation>({
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables?: any
|
||||||
|
config?: Partial<OrdercloudConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<T['data']> {
|
||||||
|
// Get fetch from the config
|
||||||
|
const { restBuyerFetch } = commerce.getConfig(config)
|
||||||
|
|
||||||
|
// Get list of categories
|
||||||
|
const rawCategories: RawCategory[] = await restBuyerFetch<{
|
||||||
|
Items: RawCategory[]
|
||||||
|
}>('GET', `/me/categories`).then((response) => response.Items)
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Normalize categories
|
||||||
|
categories: rawCategories.map((category) => ({
|
||||||
|
id: category.ID,
|
||||||
|
name: category.Name,
|
||||||
|
slug: category.ID,
|
||||||
|
path: `/${category.ID}`,
|
||||||
|
})),
|
||||||
|
brands: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSiteInfo
|
||||||
|
}
|
6
framework/ordercloud/api/operations/index.ts
Normal file
6
framework/ordercloud/api/operations/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export { default as getAllPages } from './get-all-pages'
|
||||||
|
export { default as getPage } from './get-page'
|
||||||
|
export { default as getSiteInfo } from './get-site-info'
|
||||||
|
export { default as getProduct } from './get-product'
|
||||||
|
export { default as getAllProducts } from './get-all-products'
|
||||||
|
export { default as getAllProductPaths } from './get-all-product-paths'
|
41
framework/ordercloud/api/utils/cart.ts
Normal file
41
framework/ordercloud/api/utils/cart.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import type { Cart, OrdercloudCart, OrdercloudLineItem } from '../../types/cart'
|
||||||
|
|
||||||
|
export function formatCart(
|
||||||
|
cart: OrdercloudCart,
|
||||||
|
lineItems: OrdercloudLineItem[]
|
||||||
|
): Cart {
|
||||||
|
return {
|
||||||
|
id: cart.ID,
|
||||||
|
customerId: cart.FromUserID,
|
||||||
|
email: cart.FromUser.Email,
|
||||||
|
createdAt: cart.DateCreated,
|
||||||
|
currency: {
|
||||||
|
code: cart.FromUser?.xp?.currency ?? 'USD',
|
||||||
|
},
|
||||||
|
taxesIncluded: cart.TaxCost === 0,
|
||||||
|
lineItems: lineItems.map((lineItem) => ({
|
||||||
|
id: lineItem.ID,
|
||||||
|
variantId: lineItem.Variant ? String(lineItem.Variant.ID) : '',
|
||||||
|
productId: lineItem.ProductID,
|
||||||
|
name: lineItem.Product.Name,
|
||||||
|
quantity: lineItem.Quantity,
|
||||||
|
discounts: [],
|
||||||
|
path: lineItem.ProductID,
|
||||||
|
variant: {
|
||||||
|
id: lineItem.Variant ? String(lineItem.Variant.ID) : '',
|
||||||
|
sku: lineItem.ID,
|
||||||
|
name: lineItem.Product.Name,
|
||||||
|
image: {
|
||||||
|
url: lineItem.Product.xp?.Images?.[0]?.url,
|
||||||
|
},
|
||||||
|
requiresShipping: Boolean(lineItem.ShippingAddress),
|
||||||
|
price: lineItem.UnitPrice,
|
||||||
|
listPrice: lineItem.UnitPrice,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
lineItemsSubtotalPrice: cart.Subtotal,
|
||||||
|
subtotalPrice: cart.Subtotal,
|
||||||
|
totalPrice: cart.Total,
|
||||||
|
discounts: [],
|
||||||
|
}
|
||||||
|
}
|
14
framework/ordercloud/api/utils/fetch-graphql.ts
Normal file
14
framework/ordercloud/api/utils/fetch-graphql.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { GraphQLFetcher } from '@commerce/api'
|
||||||
|
import type { OrdercloudConfig } from '../'
|
||||||
|
|
||||||
|
import { FetcherError } from '@commerce/utils/errors'
|
||||||
|
|
||||||
|
const fetchGraphqlApi: (getConfig: () => OrdercloudConfig) => GraphQLFetcher =
|
||||||
|
() => async () => {
|
||||||
|
throw new FetcherError({
|
||||||
|
errors: [{ message: 'GraphQL fetch is not implemented' }],
|
||||||
|
status: 500,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fetchGraphqlApi
|
176
framework/ordercloud/api/utils/fetch-rest.ts
Normal file
176
framework/ordercloud/api/utils/fetch-rest.ts
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import vercelFetch from '@vercel/fetch'
|
||||||
|
import { FetcherError } from '@commerce/utils/errors'
|
||||||
|
|
||||||
|
import { OrdercloudConfig } from '../index'
|
||||||
|
|
||||||
|
// Get an instance to vercel fetch
|
||||||
|
const fetch = vercelFetch()
|
||||||
|
|
||||||
|
// Get token util
|
||||||
|
async function getToken({
|
||||||
|
baseUrl,
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
}: {
|
||||||
|
baseUrl: string
|
||||||
|
clientId: string
|
||||||
|
clientSecret?: string
|
||||||
|
}): Promise<string> {
|
||||||
|
// If not, get a new one and store it
|
||||||
|
const authResponse = await fetch(`${baseUrl}/oauth/token`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
body: `client_id=${clientId}&client_secret=${clientSecret}&grant_type=client_credentials`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the token
|
||||||
|
return authResponse
|
||||||
|
.json()
|
||||||
|
.then((response: { access_token: string }) => response.access_token)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchData<T>(opts: {
|
||||||
|
token: string
|
||||||
|
path: string
|
||||||
|
method: string
|
||||||
|
config: OrdercloudConfig
|
||||||
|
fetchOptions?: Record<string, any>
|
||||||
|
body?: Record<string, unknown>
|
||||||
|
}): Promise<T> {
|
||||||
|
// Destructure opts
|
||||||
|
const { path, body, fetchOptions, config, token, method = 'GET' } = opts
|
||||||
|
|
||||||
|
// Do the request with the correct headers
|
||||||
|
const dataResponse = await fetch(
|
||||||
|
`${config.commerceUrl}/${config.apiVersion}${path}`,
|
||||||
|
{
|
||||||
|
...fetchOptions,
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
...fetchOptions?.headers,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
accept: 'application/json, text/plain, */*',
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
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.textConverted()
|
||||||
|
|
||||||
|
// And return an error
|
||||||
|
throw new FetcherError({
|
||||||
|
errors: [{ message: error || dataResponse.statusText }],
|
||||||
|
status: dataResponse.status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Return data response as json
|
||||||
|
return (await dataResponse.json()) as Promise<T>
|
||||||
|
} catch (error) {
|
||||||
|
// If response is empty return it as text
|
||||||
|
return null as unknown as Promise<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createMiddlewareFetcher: (
|
||||||
|
getConfig: () => OrdercloudConfig
|
||||||
|
) => <T>(
|
||||||
|
method: string,
|
||||||
|
path: string,
|
||||||
|
body?: Record<string, unknown>,
|
||||||
|
fetchOptions?: Record<string, any>
|
||||||
|
) => Promise<T> =
|
||||||
|
(getConfig) =>
|
||||||
|
async <T>(
|
||||||
|
method: string,
|
||||||
|
path: string,
|
||||||
|
body?: Record<string, unknown>,
|
||||||
|
fetchOptions?: Record<string, any>
|
||||||
|
) => {
|
||||||
|
// Get provider config
|
||||||
|
const config = getConfig()
|
||||||
|
|
||||||
|
// Get a token
|
||||||
|
const token = await getToken({
|
||||||
|
baseUrl: config.commerceUrl,
|
||||||
|
clientId: process.env.ORDERCLOUD_MIDDLEWARE_CLIENT_ID as string,
|
||||||
|
clientSecret: process.env.ORDERCLOUD_MIDDLEWARE_CLIENT_SECRET,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return the data and specify the expected type
|
||||||
|
return fetchData<T>({
|
||||||
|
token,
|
||||||
|
fetchOptions,
|
||||||
|
method,
|
||||||
|
config,
|
||||||
|
path,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createBuyerFetcher: (
|
||||||
|
getConfig: () => OrdercloudConfig
|
||||||
|
) => <T>(
|
||||||
|
method: string,
|
||||||
|
path: string,
|
||||||
|
body?: Record<string, unknown>,
|
||||||
|
fetchOptions?: Record<string, any>
|
||||||
|
) => Promise<T> =
|
||||||
|
(getConfig) =>
|
||||||
|
async <T>(
|
||||||
|
method: string,
|
||||||
|
path: string,
|
||||||
|
body?: Record<string, unknown>,
|
||||||
|
fetchOptions?: Record<string, any>
|
||||||
|
) => {
|
||||||
|
// Get provider config
|
||||||
|
const config = getConfig()
|
||||||
|
|
||||||
|
// If a token was passed, set it on global
|
||||||
|
if (fetchOptions?.token) {
|
||||||
|
global.token = fetchOptions.token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a token
|
||||||
|
if (!global.token) {
|
||||||
|
global.token = await getToken({
|
||||||
|
baseUrl: config.commerceUrl,
|
||||||
|
clientId: process.env.ORDERCLOUD_BUYER_CLIENT_ID as string,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the data and specify the expected type
|
||||||
|
const data = await fetchData<T>({
|
||||||
|
token: global.token as string,
|
||||||
|
fetchOptions,
|
||||||
|
config,
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
meta: { token: global.token as string },
|
||||||
|
}
|
||||||
|
}
|
3
framework/ordercloud/auth/index.ts
Normal file
3
framework/ordercloud/auth/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as useLogin } from './use-login'
|
||||||
|
export { default as useLogout } from './use-logout'
|
||||||
|
export { default as useSignup } from './use-signup'
|
16
framework/ordercloud/auth/use-login.tsx
Normal file
16
framework/ordercloud/auth/use-login.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
|
import useLogin, { UseLogin } from '@commerce/auth/use-login'
|
||||||
|
|
||||||
|
export default useLogin as UseLogin<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
async fetcher() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
useHook: () => () => {
|
||||||
|
return async function () {}
|
||||||
|
},
|
||||||
|
}
|
17
framework/ordercloud/auth/use-logout.tsx
Normal file
17
framework/ordercloud/auth/use-logout.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
|
import useLogout, { UseLogout } from '@commerce/auth/use-logout'
|
||||||
|
|
||||||
|
export default useLogout as UseLogout<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
async fetcher() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() =>
|
||||||
|
async () => {},
|
||||||
|
}
|
19
framework/ordercloud/auth/use-signup.tsx
Normal file
19
framework/ordercloud/auth/use-signup.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import useCustomer from '../customer/use-customer'
|
||||||
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
|
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
|
||||||
|
|
||||||
|
export default useSignup as UseSignup<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
async fetcher() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() =>
|
||||||
|
() => {},
|
||||||
|
}
|
4
framework/ordercloud/cart/index.ts
Normal file
4
framework/ordercloud/cart/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export { default as useCart } from './use-cart'
|
||||||
|
export { default as useAddItem } from './use-add-item'
|
||||||
|
export { default as useRemoveItem } from './use-remove-item'
|
||||||
|
export { default as useUpdateItem } from './use-update-item'
|
48
framework/ordercloud/cart/use-add-item.tsx
Normal file
48
framework/ordercloud/cart/use-add-item.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import type { AddItemHook } from '@commerce/types/cart'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
|
||||||
|
import useCart from './use-cart'
|
||||||
|
|
||||||
|
export default useAddItem as UseAddItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<AddItemHook> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/cart',
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
async fetcher({ input: item, options, fetch }) {
|
||||||
|
if (
|
||||||
|
item.quantity &&
|
||||||
|
(!Number.isInteger(item.quantity) || item.quantity! < 1)
|
||||||
|
) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'The item quantity has to be a valid integer greater than 0',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch({
|
||||||
|
...options,
|
||||||
|
body: { item },
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) =>
|
||||||
|
function useHook() {
|
||||||
|
const { mutate } = useCart()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function addItem(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
|
||||||
|
await mutate(data, false)
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
33
framework/ordercloud/cart/use-cart.tsx
Normal file
33
framework/ordercloud/cart/use-cart.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import type { GetCartHook } from '@commerce/types/cart'
|
||||||
|
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useCart, { UseCart } from '@commerce/cart/use-cart'
|
||||||
|
|
||||||
|
export default useCart as UseCart<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<GetCartHook> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/cart',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
useHook: ({ useData }) =>
|
||||||
|
function useHook(input) {
|
||||||
|
const response = useData({
|
||||||
|
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||||
|
})
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
Object.create(response, {
|
||||||
|
isEmpty: {
|
||||||
|
get() {
|
||||||
|
return (response.data?.lineItems?.length ?? 0) <= 0
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[response]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
60
framework/ordercloud/cart/use-remove-item.tsx
Normal file
60
framework/ordercloud/cart/use-remove-item.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import type {
|
||||||
|
MutationHookContext,
|
||||||
|
HookFetcherContext,
|
||||||
|
} from '@commerce/utils/types'
|
||||||
|
import type { Cart, LineItem, RemoveItemHook } from '@commerce/types/cart'
|
||||||
|
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import { ValidationError } from '@commerce/utils/errors'
|
||||||
|
import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
|
||||||
|
|
||||||
|
import useCart from './use-cart'
|
||||||
|
|
||||||
|
export type RemoveItemFn<T = any> = T extends LineItem
|
||||||
|
? (input?: RemoveItemActionInput<T>) => Promise<Cart | null | undefined>
|
||||||
|
: (input: RemoveItemActionInput<T>) => Promise<Cart | null>
|
||||||
|
|
||||||
|
export type RemoveItemActionInput<T = any> = T extends LineItem
|
||||||
|
? Partial<RemoveItemHook['actionInput']>
|
||||||
|
: RemoveItemHook['actionInput']
|
||||||
|
|
||||||
|
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/cart',
|
||||||
|
method: 'DELETE',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { itemId },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}: HookFetcherContext<RemoveItemHook>) {
|
||||||
|
return await fetch({ ...options, body: { itemId } })
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }: MutationHookContext<RemoveItemHook>) =>
|
||||||
|
function useHook<T extends LineItem | undefined = undefined>(
|
||||||
|
ctx: { item?: T } = {}
|
||||||
|
) {
|
||||||
|
const { item } = ctx
|
||||||
|
const { mutate } = useCart()
|
||||||
|
const removeItem: RemoveItemFn<LineItem> = async (input) => {
|
||||||
|
const itemId = input?.id ?? item?.id
|
||||||
|
|
||||||
|
if (!itemId) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'Invalid input used for this operation',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch({ input: { itemId } })
|
||||||
|
|
||||||
|
await mutate(data, false)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
|
||||||
|
},
|
||||||
|
}
|
93
framework/ordercloud/cart/use-update-item.tsx
Normal file
93
framework/ordercloud/cart/use-update-item.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import type {
|
||||||
|
HookFetcherContext,
|
||||||
|
MutationHookContext,
|
||||||
|
} from '@commerce/utils/types'
|
||||||
|
import type { UpdateItemHook, LineItem } from '@commerce/types/cart'
|
||||||
|
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import debounce from 'lodash.debounce'
|
||||||
|
|
||||||
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
|
import { ValidationError } from '@commerce/utils/errors'
|
||||||
|
import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
|
||||||
|
|
||||||
|
import { handler as removeItemHandler } from './use-remove-item'
|
||||||
|
import useCart from './use-cart'
|
||||||
|
|
||||||
|
export type UpdateItemActionInput<T = any> = T extends LineItem
|
||||||
|
? Partial<UpdateItemHook['actionInput']>
|
||||||
|
: UpdateItemHook['actionInput']
|
||||||
|
|
||||||
|
export default useUpdateItem as UseUpdateItem<any>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/cart',
|
||||||
|
method: 'PUT',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { itemId, item },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}: HookFetcherContext<UpdateItemHook>) {
|
||||||
|
if (Number.isInteger(item.quantity)) {
|
||||||
|
// Also allow the update hook to remove an item if the quantity is lower than 1
|
||||||
|
if (item.quantity! < 1) {
|
||||||
|
return removeItemHandler.fetcher({
|
||||||
|
options: removeItemHandler.fetchOptions,
|
||||||
|
input: { itemId },
|
||||||
|
fetch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (item.quantity) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'The item quantity has to be a valid integer',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await fetch({
|
||||||
|
...options,
|
||||||
|
body: { itemId, item },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }: MutationHookContext<UpdateItemHook>) =>
|
||||||
|
function useHook<T extends LineItem | undefined = undefined>(
|
||||||
|
ctx: {
|
||||||
|
item?: T
|
||||||
|
wait?: number
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
const { item } = ctx
|
||||||
|
const { mutate } = useCart() as any
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
debounce(async (input: UpdateItemActionInput<T>) => {
|
||||||
|
const itemId = input.id ?? item?.id
|
||||||
|
const productId = input.productId ?? item?.productId
|
||||||
|
const variantId = input.productId ?? item?.variantId
|
||||||
|
|
||||||
|
if (!itemId || !productId) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'Invalid input used for this operation',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch({
|
||||||
|
input: {
|
||||||
|
itemId,
|
||||||
|
item: {
|
||||||
|
productId,
|
||||||
|
variantId: variantId || '',
|
||||||
|
quantity: input.quantity,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await mutate(data, false)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}, ctx.wait ?? 500),
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
2
framework/ordercloud/checkout/index.ts
Normal file
2
framework/ordercloud/checkout/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as useSubmitCheckout } from './use-submit-checkout'
|
||||||
|
export { default as useCheckout } from './use-checkout'
|
41
framework/ordercloud/checkout/use-checkout.tsx
Normal file
41
framework/ordercloud/checkout/use-checkout.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import type { GetCheckoutHook } from '@commerce/types/checkout'
|
||||||
|
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useCheckout, { UseCheckout } from '@commerce/checkout/use-checkout'
|
||||||
|
import useSubmitCheckout from './use-submit-checkout'
|
||||||
|
|
||||||
|
export default useCheckout as UseCheckout<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<GetCheckoutHook> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/checkout',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
useHook: ({ useData }) =>
|
||||||
|
function useHook(input) {
|
||||||
|
const submit = useSubmitCheckout();
|
||||||
|
const response = useData({
|
||||||
|
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||||
|
})
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
Object.create(response, {
|
||||||
|
isEmpty: {
|
||||||
|
get() {
|
||||||
|
return (response.data?.lineItems?.length ?? 0) <= 0
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
get() {
|
||||||
|
return submit
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[response, submit]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
36
framework/ordercloud/checkout/use-submit-checkout.tsx
Normal file
36
framework/ordercloud/checkout/use-submit-checkout.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import type { SubmitCheckoutHook } from '@commerce/types/checkout'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import useSubmitCheckout, { UseSubmitCheckout } from '@commerce/checkout/use-submit-checkout'
|
||||||
|
|
||||||
|
export default useSubmitCheckout as UseSubmitCheckout<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<SubmitCheckoutHook> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/checkout',
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
async fetcher({ input: item, options, fetch }) {
|
||||||
|
// @TODO: Make form validations in here, import generic error like import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
// Get payment and delivery information in here
|
||||||
|
|
||||||
|
const data = await fetch({
|
||||||
|
...options,
|
||||||
|
body: { item },
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) =>
|
||||||
|
function useHook() {
|
||||||
|
return useCallback(
|
||||||
|
async function onSubmitCheckout(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
10
framework/ordercloud/commerce.config.json
Normal file
10
framework/ordercloud/commerce.config.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"provider": "ordercloud",
|
||||||
|
"features": {
|
||||||
|
"wishlist": false,
|
||||||
|
"cart": true,
|
||||||
|
"search": false,
|
||||||
|
"customerAuth": false,
|
||||||
|
"customCheckout": true
|
||||||
|
}
|
||||||
|
}
|
6
framework/ordercloud/constants.ts
Normal file
6
framework/ordercloud/constants.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export const CART_COOKIE = 'ordercloud.cart'
|
||||||
|
export const TOKEN_COOKIE = 'ordercloud.token'
|
||||||
|
export const CUSTOMER_COOKIE = 'ordercloud.customer'
|
||||||
|
export const API_URL = 'https://sandboxapi.ordercloud.io'
|
||||||
|
export const API_VERSION = 'v1'
|
||||||
|
export const LOCALE = 'en-us'
|
4
framework/ordercloud/customer/address/index.ts
Normal file
4
framework/ordercloud/customer/address/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export { default as useAddresses } from './use-addresses'
|
||||||
|
export { default as useAddItem } from './use-add-item'
|
||||||
|
export { default as useRemoveItem } from './use-remove-item'
|
||||||
|
export { default as useUpdateItem } from './use-update-item'
|
38
framework/ordercloud/customer/address/use-add-item.tsx
Normal file
38
framework/ordercloud/customer/address/use-add-item.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import type { AddItemHook } from '@commerce/types/customer/address'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import useAddItem, { UseAddItem } from '@commerce/customer/address/use-add-item'
|
||||||
|
import useAddresses from './use-addresses'
|
||||||
|
|
||||||
|
export default useAddItem as UseAddItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<AddItemHook> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/customer/address',
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
async fetcher({ input: item, options, fetch }) {
|
||||||
|
const data = await fetch({
|
||||||
|
...options,
|
||||||
|
body: { item },
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) =>
|
||||||
|
function useHook() {
|
||||||
|
const { mutate } = useAddresses()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function addItem(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
|
||||||
|
await mutate([data], false)
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
35
framework/ordercloud/customer/address/use-addresses.tsx
Normal file
35
framework/ordercloud/customer/address/use-addresses.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type { GetAddressesHook } from '@commerce/types/customer/address'
|
||||||
|
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useAddresses, {
|
||||||
|
UseAddresses,
|
||||||
|
} from '@commerce/customer/address/use-addresses'
|
||||||
|
|
||||||
|
export default useAddresses as UseAddresses<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<GetAddressesHook> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/customer/address',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
useHook: ({ useData }) =>
|
||||||
|
function useHook(input) {
|
||||||
|
const response = useData({
|
||||||
|
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||||
|
})
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
Object.create(response, {
|
||||||
|
isEmpty: {
|
||||||
|
get() {
|
||||||
|
return (response.data?.length ?? 0) <= 0
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[response]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
62
framework/ordercloud/customer/address/use-remove-item.tsx
Normal file
62
framework/ordercloud/customer/address/use-remove-item.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import type {
|
||||||
|
MutationHookContext,
|
||||||
|
HookFetcherContext,
|
||||||
|
} from '@commerce/utils/types'
|
||||||
|
import type { Address, RemoveItemHook } from '@commerce/types/customer/address'
|
||||||
|
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import { ValidationError } from '@commerce/utils/errors'
|
||||||
|
import useRemoveItem, {
|
||||||
|
UseRemoveItem,
|
||||||
|
} from '@commerce/customer/address/use-remove-item'
|
||||||
|
|
||||||
|
import useAddresses from './use-addresses'
|
||||||
|
|
||||||
|
export type RemoveItemFn<T = any> = T extends Address
|
||||||
|
? (input?: RemoveItemActionInput<T>) => Promise<Address | null | undefined>
|
||||||
|
: (input: RemoveItemActionInput<T>) => Promise<Address | null>
|
||||||
|
|
||||||
|
export type RemoveItemActionInput<T = any> = T extends Address
|
||||||
|
? Partial<RemoveItemHook['actionInput']>
|
||||||
|
: RemoveItemHook['actionInput']
|
||||||
|
|
||||||
|
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/customer/address',
|
||||||
|
method: 'DELETE',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { itemId },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}: HookFetcherContext<RemoveItemHook>) {
|
||||||
|
return await fetch({ ...options, body: { itemId } })
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }: MutationHookContext<RemoveItemHook>) =>
|
||||||
|
function useHook<T extends Address | undefined = undefined>(
|
||||||
|
ctx: { item?: T } = {}
|
||||||
|
) {
|
||||||
|
const { item } = ctx
|
||||||
|
const { mutate } = useAddresses()
|
||||||
|
const removeItem: RemoveItemFn<Address> = async (input) => {
|
||||||
|
const itemId = input?.id ?? item?.id
|
||||||
|
|
||||||
|
if (!itemId) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'Invalid input used for this operation',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch({ input: { itemId } })
|
||||||
|
|
||||||
|
await mutate([], false)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
|
||||||
|
},
|
||||||
|
}
|
52
framework/ordercloud/customer/address/use-update-item.tsx
Normal file
52
framework/ordercloud/customer/address/use-update-item.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import type {
|
||||||
|
HookFetcherContext,
|
||||||
|
MutationHookContext,
|
||||||
|
} from '@commerce/utils/types'
|
||||||
|
import type { UpdateItemHook, Address } from '@commerce/types/customer/address'
|
||||||
|
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
|
import useUpdateItem, {
|
||||||
|
UseUpdateItem,
|
||||||
|
} from '@commerce/customer/address/use-update-item'
|
||||||
|
|
||||||
|
import useAddresses from './use-addresses'
|
||||||
|
|
||||||
|
export type UpdateItemActionInput<T = any> = T extends Address
|
||||||
|
? Partial<UpdateItemHook['actionInput']>
|
||||||
|
: UpdateItemHook['actionInput']
|
||||||
|
|
||||||
|
export default useUpdateItem as UseUpdateItem<any>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/customer/address',
|
||||||
|
method: 'PUT',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { itemId, item },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}: HookFetcherContext<UpdateItemHook>) {
|
||||||
|
return await fetch({
|
||||||
|
...options,
|
||||||
|
body: { itemId, item },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }: MutationHookContext<UpdateItemHook>) =>
|
||||||
|
function useHook() {
|
||||||
|
const { mutate } = useAddresses()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function updateItem(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
|
||||||
|
await mutate([], false)
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
4
framework/ordercloud/customer/card/index.ts
Normal file
4
framework/ordercloud/customer/card/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export { default as useCards } from './use-cards'
|
||||||
|
export { default as useAddItem } from './use-add-item'
|
||||||
|
export { default as useRemoveItem } from './use-remove-item'
|
||||||
|
export { default as useUpdateItem } from './use-update-item'
|
38
framework/ordercloud/customer/card/use-add-item.tsx
Normal file
38
framework/ordercloud/customer/card/use-add-item.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import type { AddItemHook } from '@commerce/types/customer/card'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import useAddItem, { UseAddItem } from '@commerce/customer/card/use-add-item'
|
||||||
|
import useCards from './use-cards'
|
||||||
|
|
||||||
|
export default useAddItem as UseAddItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<AddItemHook> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/customer/card',
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
async fetcher({ input: item, options, fetch }) {
|
||||||
|
const data = await fetch({
|
||||||
|
...options,
|
||||||
|
body: { item },
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) =>
|
||||||
|
function useHook() {
|
||||||
|
const { mutate } = useCards()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function addItem(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
|
||||||
|
await mutate([data], false)
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
33
framework/ordercloud/customer/card/use-cards.tsx
Normal file
33
framework/ordercloud/customer/card/use-cards.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import type { GetCardsHook } from '@commerce/types/customer/card'
|
||||||
|
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useCard, { UseCards } from '@commerce/customer/card/use-cards'
|
||||||
|
|
||||||
|
export default useCard as UseCards<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<GetCardsHook> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/customer/card',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
useHook: ({ useData }) =>
|
||||||
|
function useHook(input) {
|
||||||
|
const response = useData({
|
||||||
|
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||||
|
})
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
Object.create(response, {
|
||||||
|
isEmpty: {
|
||||||
|
get() {
|
||||||
|
return (response.data?.length ?? 0) <= 0
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[response]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
62
framework/ordercloud/customer/card/use-remove-item.tsx
Normal file
62
framework/ordercloud/customer/card/use-remove-item.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import type {
|
||||||
|
MutationHookContext,
|
||||||
|
HookFetcherContext,
|
||||||
|
} from '@commerce/utils/types'
|
||||||
|
import type { Card, RemoveItemHook } from '@commerce/types/customer/card'
|
||||||
|
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import { ValidationError } from '@commerce/utils/errors'
|
||||||
|
import useRemoveItem, {
|
||||||
|
UseRemoveItem,
|
||||||
|
} from '@commerce/customer/card/use-remove-item'
|
||||||
|
|
||||||
|
import useCards from './use-cards'
|
||||||
|
|
||||||
|
export type RemoveItemFn<T = any> = T extends Card
|
||||||
|
? (input?: RemoveItemActionInput<T>) => Promise<Card | null | undefined>
|
||||||
|
: (input: RemoveItemActionInput<T>) => Promise<Card | null>
|
||||||
|
|
||||||
|
export type RemoveItemActionInput<T = any> = T extends Card
|
||||||
|
? Partial<RemoveItemHook['actionInput']>
|
||||||
|
: RemoveItemHook['actionInput']
|
||||||
|
|
||||||
|
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/customer/card',
|
||||||
|
method: 'DELETE',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { itemId },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}: HookFetcherContext<RemoveItemHook>) {
|
||||||
|
return await fetch({ ...options, body: { itemId } })
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }: MutationHookContext<RemoveItemHook>) =>
|
||||||
|
function useHook<T extends Card | undefined = undefined>(
|
||||||
|
ctx: { item?: T } = {}
|
||||||
|
) {
|
||||||
|
const { item } = ctx
|
||||||
|
const { mutate } = useCards()
|
||||||
|
const removeItem: RemoveItemFn<Card> = async (input) => {
|
||||||
|
const itemId = input?.id ?? item?.id
|
||||||
|
|
||||||
|
if (!itemId) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'Invalid input used for this operation',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch({ input: { itemId } })
|
||||||
|
|
||||||
|
await mutate([], false)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
|
||||||
|
},
|
||||||
|
}
|
52
framework/ordercloud/customer/card/use-update-item.tsx
Normal file
52
framework/ordercloud/customer/card/use-update-item.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import type {
|
||||||
|
HookFetcherContext,
|
||||||
|
MutationHookContext,
|
||||||
|
} from '@commerce/utils/types'
|
||||||
|
import type { UpdateItemHook, Card } from '@commerce/types/customer/card'
|
||||||
|
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
|
import useUpdateItem, {
|
||||||
|
UseUpdateItem,
|
||||||
|
} from '@commerce/customer/card/use-update-item'
|
||||||
|
|
||||||
|
import useCards from './use-cards'
|
||||||
|
|
||||||
|
export type UpdateItemActionInput<T = any> = T extends Card
|
||||||
|
? Partial<UpdateItemHook['actionInput']>
|
||||||
|
: UpdateItemHook['actionInput']
|
||||||
|
|
||||||
|
export default useUpdateItem as UseUpdateItem<any>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/customer/card',
|
||||||
|
method: 'PUT',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { itemId, item },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}: HookFetcherContext<UpdateItemHook>) {
|
||||||
|
return await fetch({
|
||||||
|
...options,
|
||||||
|
body: { itemId, item },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }: MutationHookContext<UpdateItemHook>) =>
|
||||||
|
function useHook() {
|
||||||
|
const { mutate } = useCards()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function updateItem(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
|
||||||
|
await mutate([], false)
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
1
framework/ordercloud/customer/index.ts
Normal file
1
framework/ordercloud/customer/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as useCustomer } from './use-customer'
|
15
framework/ordercloud/customer/use-customer.tsx
Normal file
15
framework/ordercloud/customer/use-customer.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
|
||||||
|
|
||||||
|
export default useCustomer as UseCustomer<typeof handler>
|
||||||
|
export const handler: SWRHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
async fetcher({ input, options, fetch }) {},
|
||||||
|
useHook: () => () => {
|
||||||
|
return async function addItem() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
17
framework/ordercloud/fetcher.ts
Normal file
17
framework/ordercloud/fetcher.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Fetcher } from '@commerce/utils/types'
|
||||||
|
|
||||||
|
const clientFetcher: Fetcher = async ({ method, url, body }) => {
|
||||||
|
const response = await fetch(url!, {
|
||||||
|
method,
|
||||||
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((response) => response.data)
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
export default clientFetcher
|
9
framework/ordercloud/index.tsx
Normal file
9
framework/ordercloud/index.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { ordercloudProvider, OrdercloudProvider } from './provider'
|
||||||
|
import { getCommerceProvider, useCommerce as useCoreCommerce } from '@commerce'
|
||||||
|
|
||||||
|
export { ordercloudProvider }
|
||||||
|
export type { OrdercloudProvider }
|
||||||
|
|
||||||
|
export const CommerceProvider = getCommerceProvider(ordercloudProvider)
|
||||||
|
|
||||||
|
export const useCommerce = () => useCoreCommerce()
|
8
framework/ordercloud/next.config.js
Normal file
8
framework/ordercloud/next.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const commerce = require('./commerce.config.json')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
commerce,
|
||||||
|
images: {
|
||||||
|
domains: ['localhost', 'ocdevops.blob.core.windows.net'],
|
||||||
|
},
|
||||||
|
}
|
2
framework/ordercloud/product/index.ts
Normal file
2
framework/ordercloud/product/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as usePrice } from './use-price'
|
||||||
|
export { default as useSearch } from './use-search'
|
2
framework/ordercloud/product/use-price.tsx
Normal file
2
framework/ordercloud/product/use-price.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from '@commerce/product/use-price'
|
||||||
|
export { default } from '@commerce/product/use-price'
|
17
framework/ordercloud/product/use-search.tsx
Normal file
17
framework/ordercloud/product/use-search.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useSearch, { UseSearch } from '@commerce/product/use-search'
|
||||||
|
export default useSearch as UseSearch<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
async fetcher({ input, options, fetch }) {},
|
||||||
|
useHook: () => () => {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
products: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
62
framework/ordercloud/provider.ts
Normal file
62
framework/ordercloud/provider.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { handler as useCart } from './cart/use-cart'
|
||||||
|
import { handler as useAddCartItem } from './cart/use-add-item'
|
||||||
|
import { handler as useUpdateCartItem } from './cart/use-update-item'
|
||||||
|
import { handler as useRemoveCartItem } from './cart/use-remove-item'
|
||||||
|
|
||||||
|
import { handler as useCustomer } from './customer/use-customer'
|
||||||
|
import { handler as useSearch } from './product/use-search'
|
||||||
|
|
||||||
|
import { handler as useLogin } from './auth/use-login'
|
||||||
|
import { handler as useLogout } from './auth/use-logout'
|
||||||
|
import { handler as useSignup } from './auth/use-signup'
|
||||||
|
|
||||||
|
import { handler as useCheckout } from './checkout/use-checkout'
|
||||||
|
import { handler as useSubmitCheckout } from './checkout/use-submit-checkout'
|
||||||
|
|
||||||
|
import { handler as useCards } from './customer/card/use-cards'
|
||||||
|
import { handler as useAddCardItem } from './customer/card/use-add-item'
|
||||||
|
import { handler as useUpdateCardItem } from './customer/card/use-update-item'
|
||||||
|
import { handler as useRemoveCardItem } from './customer/card/use-remove-item'
|
||||||
|
|
||||||
|
import { handler as useAddresses } from './customer/address/use-addresses'
|
||||||
|
import { handler as useAddAddressItem } from './customer/address/use-add-item'
|
||||||
|
import { handler as useUpdateAddressItem } from './customer/address/use-update-item'
|
||||||
|
import { handler as useRemoveAddressItem } from './customer/address/use-remove-item'
|
||||||
|
|
||||||
|
import { CART_COOKIE, LOCALE } from './constants'
|
||||||
|
import { default as fetcher } from './fetcher'
|
||||||
|
|
||||||
|
export const ordercloudProvider = {
|
||||||
|
locale: LOCALE,
|
||||||
|
cartCookie: CART_COOKIE,
|
||||||
|
fetcher,
|
||||||
|
cart: {
|
||||||
|
useCart,
|
||||||
|
useAddItem: useAddCartItem,
|
||||||
|
useUpdateItem: useUpdateCartItem,
|
||||||
|
useRemoveItem: useRemoveCartItem
|
||||||
|
},
|
||||||
|
checkout: {
|
||||||
|
useCheckout,
|
||||||
|
useSubmitCheckout,
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
useCustomer,
|
||||||
|
card: {
|
||||||
|
useCards,
|
||||||
|
useAddItem: useAddCardItem,
|
||||||
|
useUpdateItem: useUpdateCardItem,
|
||||||
|
useRemoveItem: useRemoveCardItem
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
useAddresses,
|
||||||
|
useAddItem: useAddAddressItem,
|
||||||
|
useUpdateItem: useUpdateAddressItem,
|
||||||
|
useRemoveItem: useRemoveAddressItem
|
||||||
|
}
|
||||||
|
},
|
||||||
|
products: { useSearch },
|
||||||
|
auth: { useLogin, useLogout, useSignup },
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OrdercloudProvider = typeof ordercloudProvider
|
126
framework/ordercloud/types/cart.ts
Normal file
126
framework/ordercloud/types/cart.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import * as Core from '@commerce/types/cart'
|
||||||
|
|
||||||
|
export * from '@commerce/types/cart'
|
||||||
|
|
||||||
|
export interface OrdercloudCart {
|
||||||
|
ID: string
|
||||||
|
FromUser: {
|
||||||
|
ID: string
|
||||||
|
Username: string
|
||||||
|
Password: null
|
||||||
|
FirstName: string
|
||||||
|
LastName: string
|
||||||
|
Email: string
|
||||||
|
Phone: null
|
||||||
|
TermsAccepted: null
|
||||||
|
Active: true
|
||||||
|
xp: {
|
||||||
|
something: string
|
||||||
|
currency: string
|
||||||
|
}
|
||||||
|
AvailableRoles: null
|
||||||
|
DateCreated: string
|
||||||
|
PasswordLastSetDate: null
|
||||||
|
}
|
||||||
|
FromCompanyID: string
|
||||||
|
ToCompanyID: string
|
||||||
|
FromUserID: string
|
||||||
|
BillingAddressID: null
|
||||||
|
BillingAddress: null
|
||||||
|
ShippingAddressID: null
|
||||||
|
Comments: null
|
||||||
|
LineItemCount: number
|
||||||
|
Status: string
|
||||||
|
DateCreated: string
|
||||||
|
DateSubmitted: null
|
||||||
|
DateApproved: null
|
||||||
|
DateDeclined: null
|
||||||
|
DateCanceled: null
|
||||||
|
DateCompleted: null
|
||||||
|
LastUpdated: string
|
||||||
|
Subtotal: number
|
||||||
|
ShippingCost: number
|
||||||
|
TaxCost: number
|
||||||
|
PromotionDiscount: number
|
||||||
|
Total: number
|
||||||
|
IsSubmitted: false
|
||||||
|
xp: {
|
||||||
|
productId: string
|
||||||
|
variantId: string
|
||||||
|
quantity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrdercloudLineItem {
|
||||||
|
ID: string
|
||||||
|
ProductID: string
|
||||||
|
Quantity: 1
|
||||||
|
DateAdded: string
|
||||||
|
QuantityShipped: number
|
||||||
|
UnitPrice: number
|
||||||
|
PromotionDiscount: number
|
||||||
|
LineTotal: number
|
||||||
|
LineSubtotal: number
|
||||||
|
CostCenter: null
|
||||||
|
DateNeeded: null
|
||||||
|
ShippingAccount: null
|
||||||
|
ShippingAddressID: null
|
||||||
|
ShipFromAddressID: null
|
||||||
|
Product: {
|
||||||
|
ID: string
|
||||||
|
Name: string
|
||||||
|
Description: string
|
||||||
|
QuantityMultiplier: number
|
||||||
|
ShipWeight: number
|
||||||
|
ShipHeight: null
|
||||||
|
ShipWidth: null
|
||||||
|
ShipLength: null
|
||||||
|
xp: {
|
||||||
|
Images: {
|
||||||
|
url: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Variant: null | {
|
||||||
|
ID: string
|
||||||
|
Name: null
|
||||||
|
Description: null
|
||||||
|
ShipWeight: null
|
||||||
|
ShipHeight: null
|
||||||
|
ShipWidth: null
|
||||||
|
ShipLength: null
|
||||||
|
xp: null
|
||||||
|
}
|
||||||
|
ShippingAddress: null
|
||||||
|
ShipFromAddress: null
|
||||||
|
SupplierID: null
|
||||||
|
Specs: []
|
||||||
|
xp: null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend core cart types
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Cart = Core.Cart & {
|
||||||
|
lineItems: Core.LineItem[]
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CartTypes = Core.CartTypes
|
||||||
|
|
||||||
|
export type CartHooks = Core.CartHooks<CartTypes>
|
||||||
|
|
||||||
|
export type GetCartHook = CartHooks['getCart']
|
||||||
|
export type AddItemHook = CartHooks['addItem']
|
||||||
|
export type UpdateItemHook = CartHooks['updateItem']
|
||||||
|
export type RemoveItemHook = CartHooks['removeItem']
|
||||||
|
|
||||||
|
export type CartSchema = Core.CartSchema<CartTypes>
|
||||||
|
|
||||||
|
export type CartHandlers = Core.CartHandlers<CartTypes>
|
||||||
|
|
||||||
|
export type GetCartHandler = CartHandlers['getCart']
|
||||||
|
export type AddItemHandler = CartHandlers['addItem']
|
||||||
|
export type UpdateItemHandler = CartHandlers['updateItem']
|
||||||
|
export type RemoveItemHandler = CartHandlers['removeItem']
|
10
framework/ordercloud/types/category.ts
Normal file
10
framework/ordercloud/types/category.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export interface RawCategory {
|
||||||
|
ID: string
|
||||||
|
Name: string
|
||||||
|
Description: null | string
|
||||||
|
ListOrder: number
|
||||||
|
Active: boolean
|
||||||
|
ParentID: null
|
||||||
|
ChildCount: number
|
||||||
|
xp: null
|
||||||
|
}
|
4
framework/ordercloud/types/checkout.ts
Normal file
4
framework/ordercloud/types/checkout.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import * as Core from '@commerce/types/checkout'
|
||||||
|
|
||||||
|
export type CheckoutTypes = Core.CheckoutTypes
|
||||||
|
export type CheckoutSchema = Core.CheckoutSchema<CheckoutTypes>
|
31
framework/ordercloud/types/customer/address.ts
Normal file
31
framework/ordercloud/types/customer/address.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import * as Core from '@commerce/types/customer/address'
|
||||||
|
|
||||||
|
export type CustomerAddressTypes = Core.CustomerAddressTypes
|
||||||
|
export type CustomerAddressSchema = Core.CustomerAddressSchema<CustomerAddressTypes>
|
||||||
|
|
||||||
|
export interface OrdercloudAddress {
|
||||||
|
ID: string;
|
||||||
|
"FromCompanyID": string;
|
||||||
|
"ToCompanyID": string;
|
||||||
|
"FromUserID": string;
|
||||||
|
"BillingAddressID": null,
|
||||||
|
"BillingAddress": null,
|
||||||
|
"ShippingAddressID": null,
|
||||||
|
"Comments": null,
|
||||||
|
"LineItemCount": number;
|
||||||
|
"Status": string;
|
||||||
|
"DateCreated": string;
|
||||||
|
"DateSubmitted": null,
|
||||||
|
"DateApproved": null,
|
||||||
|
"DateDeclined": null,
|
||||||
|
"DateCanceled": null,
|
||||||
|
"DateCompleted": null,
|
||||||
|
"LastUpdated": string;
|
||||||
|
"Subtotal": number
|
||||||
|
"ShippingCost": number
|
||||||
|
"TaxCost": number
|
||||||
|
"PromotionDiscount": number
|
||||||
|
"Total": number
|
||||||
|
"IsSubmitted": false,
|
||||||
|
"xp": null
|
||||||
|
}
|
16
framework/ordercloud/types/customer/card.ts
Normal file
16
framework/ordercloud/types/customer/card.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import * as Core from '@commerce/types/customer/card'
|
||||||
|
|
||||||
|
export type CustomerCardTypes = Core.CustomerCardTypes
|
||||||
|
export type CustomerCardSchema = Core.CustomerCardSchema<CustomerCardTypes>
|
||||||
|
|
||||||
|
export interface OredercloudCreditCard {
|
||||||
|
"ID": string;
|
||||||
|
"Editable": boolean;
|
||||||
|
"Token": string;
|
||||||
|
"DateCreated": string;
|
||||||
|
"CardType": string;
|
||||||
|
"PartialAccountNumber": string;
|
||||||
|
"CardholderName": string;
|
||||||
|
"ExpirationDate": string;
|
||||||
|
"xp": null
|
||||||
|
}
|
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
|
||||||
|
}
|
||||||
|
}
|
55
framework/ordercloud/types/product.ts
Normal file
55
framework/ordercloud/types/product.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
interface RawVariantSpec {
|
||||||
|
SpecID: string
|
||||||
|
Name: string
|
||||||
|
OptionID: string
|
||||||
|
Value: string
|
||||||
|
PriceMarkupType: string
|
||||||
|
PriceMarkup: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RawSpec {
|
||||||
|
ID: string
|
||||||
|
Name: string
|
||||||
|
Options: {
|
||||||
|
ID: string
|
||||||
|
Value: string
|
||||||
|
xp: {
|
||||||
|
hexColor?: string
|
||||||
|
}
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RawVariant {
|
||||||
|
ID: string
|
||||||
|
Specs: RawVariantSpec[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RawProduct {
|
||||||
|
OwnerID: string
|
||||||
|
DefaultPriceScheduleID: string | null
|
||||||
|
AutoForward: boolean
|
||||||
|
ID: string
|
||||||
|
Name: string
|
||||||
|
Description: string
|
||||||
|
QuantityMultiplier: number
|
||||||
|
ShipWeight: null
|
||||||
|
ShipHeight: null
|
||||||
|
ShipWidth: null
|
||||||
|
ShipLength: null
|
||||||
|
Active: boolean
|
||||||
|
SpecCount: number
|
||||||
|
VariantCount: number
|
||||||
|
ShipFromAddressID: null
|
||||||
|
Inventory: null
|
||||||
|
DefaultSupplierID: null
|
||||||
|
AllSuppliersCanSell: boolean
|
||||||
|
xp: {
|
||||||
|
Price: number
|
||||||
|
PriceCurrency: string
|
||||||
|
Images: {
|
||||||
|
url: string
|
||||||
|
}[]
|
||||||
|
Variants?: RawVariant[]
|
||||||
|
Specs?: RawSpec[]
|
||||||
|
}
|
||||||
|
}
|
47
framework/ordercloud/utils/product.ts
Normal file
47
framework/ordercloud/utils/product.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import type { Product } from '@commerce/types/product'
|
||||||
|
|
||||||
|
import type { RawProduct } from '../types/product'
|
||||||
|
|
||||||
|
export function normalize(product: RawProduct): Product {
|
||||||
|
return {
|
||||||
|
id: product.ID,
|
||||||
|
name: product.Name,
|
||||||
|
description: product.Description,
|
||||||
|
slug: product.ID,
|
||||||
|
images: product.xp.Images,
|
||||||
|
price: {
|
||||||
|
value: product.xp.Price,
|
||||||
|
currencyCode: product.xp.PriceCurrency,
|
||||||
|
},
|
||||||
|
variants: product.xp.Variants?.length
|
||||||
|
? product.xp.Variants.map((variant) => ({
|
||||||
|
id: variant.ID,
|
||||||
|
options: variant.Specs.map((spec) => ({
|
||||||
|
id: spec.SpecID,
|
||||||
|
__typename: 'MultipleChoiceOption',
|
||||||
|
displayName: spec.Name,
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
label: spec.Value,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
options: product.xp.Specs?.length
|
||||||
|
? product.xp.Specs.map((spec) => ({
|
||||||
|
id: spec.ID,
|
||||||
|
displayName: spec.Name,
|
||||||
|
values: spec.Options.map((option) => ({
|
||||||
|
label: option.Value,
|
||||||
|
...(option.xp?.hexColor && { hexColors: [option.xp.hexColor] }),
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
}
|
||||||
|
}
|
13
framework/ordercloud/wishlist/use-add-item.tsx
Normal file
13
framework/ordercloud/wishlist/use-add-item.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export function emptyHook() {
|
||||||
|
const useEmptyHook = async (options = {}) => {
|
||||||
|
return useCallback(async function () {
|
||||||
|
return Promise.resolve()
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return useEmptyHook
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emptyHook
|
17
framework/ordercloud/wishlist/use-remove-item.tsx
Normal file
17
framework/ordercloud/wishlist/use-remove-item.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
includeProducts?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emptyHook(options?: Options) {
|
||||||
|
const useEmptyHook = async ({ id }: { id: string | number }) => {
|
||||||
|
return useCallback(async function () {
|
||||||
|
return Promise.resolve()
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return useEmptyHook
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emptyHook
|
43
framework/ordercloud/wishlist/use-wishlist.tsx
Normal file
43
framework/ordercloud/wishlist/use-wishlist.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { HookFetcher } from '@commerce/utils/types'
|
||||||
|
import type { Product } from '@commerce/types/product'
|
||||||
|
|
||||||
|
const defaultOpts = {}
|
||||||
|
|
||||||
|
export type Wishlist = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
product_id: number
|
||||||
|
variant_id: number
|
||||||
|
id: number
|
||||||
|
product: Product
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseWishlistOptions {
|
||||||
|
includeProducts?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseWishlistInput extends UseWishlistOptions {
|
||||||
|
customerId?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetcher: HookFetcher<Wishlist | null, UseWishlistInput> = () => {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extendHook(
|
||||||
|
customFetcher: typeof fetcher,
|
||||||
|
// swrOptions?: SwrOptions<Wishlist | null, UseWishlistInput>
|
||||||
|
swrOptions?: any
|
||||||
|
) {
|
||||||
|
const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => {
|
||||||
|
return { data: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
useWishlist.extend = extendHook
|
||||||
|
|
||||||
|
return useWishlist
|
||||||
|
}
|
||||||
|
|
||||||
|
export default extendHook(fetcher)
|
@ -43,6 +43,7 @@
|
|||||||
"react-fast-marquee": "^1.1.4",
|
"react-fast-marquee": "^1.1.4",
|
||||||
"react-merge-refs": "^1.1.0",
|
"react-merge-refs": "^1.1.0",
|
||||||
"react-use-measure": "^2.0.4",
|
"react-use-measure": "^2.0.4",
|
||||||
|
"stripe": "^8.176.0",
|
||||||
"swell-js": "^4.0.0-next.0",
|
"swell-js": "^4.0.0-next.0",
|
||||||
"swr": "^0.5.6",
|
"swr": "^0.5.6",
|
||||||
"tabbable": "^5.2.0",
|
"tabbable": "^5.2.0",
|
||||||
|
20
yarn.lock
20
yarn.lock
@ -1170,6 +1170,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
|
||||||
integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
|
integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
|
||||||
|
|
||||||
|
"@types/node@>=8.1.0":
|
||||||
|
version "16.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.6.tgz#040a64d7faf9e5d9e940357125f0963012e66f04"
|
||||||
|
integrity sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ==
|
||||||
|
|
||||||
"@types/parse-json@^4.0.0":
|
"@types/parse-json@^4.0.0":
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||||
@ -5686,6 +5691,13 @@ qs@6.7.0:
|
|||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||||
|
|
||||||
|
qs@^6.6.0:
|
||||||
|
version "6.10.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
|
||||||
|
integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==
|
||||||
|
dependencies:
|
||||||
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
querystring-es3@0.2.1, querystring-es3@^0.2.0:
|
querystring-es3@0.2.1, querystring-es3@^0.2.0:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||||
@ -6487,6 +6499,14 @@ strip-json-comments@~2.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||||
|
|
||||||
|
stripe@^8.176.0:
|
||||||
|
version "8.176.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.176.0.tgz#2f4980ab49acbfe6d67ecaddd54c05e20de9532c"
|
||||||
|
integrity sha512-0KCDo8TWFgeNWU7cPaqdjO2u2OSth0cmWYZmA7xsuxRCk7/lgWbJ/UbeSphx74cCIjFCmGuzDoNuNxqon9lEbg==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" ">=8.1.0"
|
||||||
|
qs "^6.6.0"
|
||||||
|
|
||||||
styled-jsx@3.3.2:
|
styled-jsx@3.3.2:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-3.3.2.tgz#2474601a26670a6049fb4d3f94bd91695b3ce018"
|
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-3.3.2.tgz#2474601a26670a6049fb4d3f94bd91695b3ce018"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user