4
0
forked from crowetic/commerce

Update Swell Provider (#359)

* fix update cart item

* update types

* update checkout

* update get-page, cleanup types

* revert change to incorrect file

* cleanup

Co-authored-by: Greg Hoskin <greghoskin@Gregs-MacBook-Pro.local>
This commit is contained in:
ghoskin 2021-06-14 13:37:18 -07:00 committed by GitHub
parent ddd4631ade
commit 4d85b43a30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 503 additions and 545 deletions

View File

@ -1,20 +0,0 @@
import createApiHandler, { SwellApiHandler } from '../utils/create-api-handler'
import { SWELL_CHECKOUT_URL_COOKIE } from '../../const'
import { getConfig } from '..'
const checkoutApi: SwellApiHandler<any> = async (req, res, config) => {
config = getConfig()
const { cookies } = req
const checkoutUrl = cookies[SWELL_CHECKOUT_URL_COOKIE]
if (checkoutUrl) {
res.redirect(checkoutUrl)
} else {
res.redirect('/cart')
}
}
export default createApiHandler(checkoutApi, {}, {})

View File

@ -1 +0,0 @@
export default function () {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1,30 @@
import { CommerceAPI, createEndpoint, GetAPISchema } from '@commerce/api'
import { CheckoutSchema } from '@commerce/types/checkout'
import { SWELL_CHECKOUT_URL_COOKIE } from '../../../const'
import checkoutEndpoint from '@commerce/api/endpoints/checkout'
const checkout: CheckoutEndpoint['handlers']['checkout'] = async ({
req,
res,
config,
}) => {
const { cookies } = req
const checkoutUrl = cookies[SWELL_CHECKOUT_URL_COOKIE]
if (checkoutUrl) {
res.redirect(checkoutUrl)
} else {
res.redirect('/cart')
}
}
export const handlers: CheckoutEndpoint['handlers'] = { checkout }
export type CheckoutAPI = GetAPISchema<CommerceAPI, CheckoutSchema>
export type CheckoutEndpoint = CheckoutAPI['endpoint']
const checkoutApi = createEndpoint<CheckoutAPI>({
handler: checkoutEndpoint,
handlers,
})
export default checkoutApi

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -1,5 +1,8 @@
import type { CommerceAPIConfig } from '@commerce/api' import {
CommerceAPI,
CommerceAPIConfig,
getCommerceApi as commerceApi,
} from '@commerce/api'
import { import {
SWELL_CHECKOUT_ID_COOKIE, SWELL_CHECKOUT_ID_COOKIE,
SWELL_CUSTOMER_TOKEN_COOKIE, SWELL_CUSTOMER_TOKEN_COOKIE,
@ -7,31 +10,19 @@ import {
} from '../const' } from '../const'
import fetchApi from './utils/fetch-swell-api' import fetchApi from './utils/fetch-swell-api'
import login from './operations/login'
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'
export interface SwellConfig extends CommerceAPIConfig { export interface SwellConfig extends CommerceAPIConfig {
fetch: any fetch: any
} }
export class Config { const config: SwellConfig = {
private config: SwellConfig
constructor(config: SwellConfig) {
this.config = config
}
getConfig(userConfig: Partial<SwellConfig> = {}) {
return Object.entries(userConfig).reduce<SwellConfig>(
(cfg, [key, value]) => Object.assign(cfg, { [key]: value }),
{ ...this.config }
)
}
setConfig(newConfig: Partial<SwellConfig>) {
Object.assign(this.config, newConfig)
}
}
const config = new Config({
locale: 'en-US', locale: 'en-US',
commerceUrl: '', commerceUrl: '',
apiToken: ''!, apiToken: ''!,
@ -39,12 +30,24 @@ const config = new Config({
cartCookieMaxAge: SWELL_COOKIE_EXPIRE, cartCookieMaxAge: SWELL_COOKIE_EXPIRE,
fetch: fetchApi, fetch: fetchApi,
customerCookie: SWELL_CUSTOMER_TOKEN_COOKIE, customerCookie: SWELL_CUSTOMER_TOKEN_COOKIE,
})
export function getConfig(userConfig?: Partial<SwellConfig>) {
return config.getConfig(userConfig)
} }
export function setConfig(newConfig: Partial<SwellConfig>) { const operations = {
return config.setConfig(newConfig) login,
getAllPages,
getPage,
getSiteInfo,
getAllProductPaths,
getAllProducts,
getProduct,
}
export const provider = { config, operations }
export type Provider = typeof provider
export function getCommerceApi<P extends Provider>(
customProvider: P = provider as any
): CommerceAPI<P> {
return commerceApi(customProvider)
} }

View File

@ -0,0 +1,45 @@
import { Provider, SwellConfig } from '..'
import type { OperationContext } from '@commerce/api/operations'
import type { Page } from '../../types/page'
export type GetAllPagesResult<
T extends { pages: any[] } = { pages: Page[] }
> = T
export default function getAllPagesOperation({
commerce,
}: OperationContext<Provider>) {
async function getAllPages(opts?: {
config?: Partial<SwellConfig>
preview?: boolean
}): Promise<GetAllPagesResult>
async function getAllPages<T extends { pages: any[] }>(opts: {
url: string
config?: Partial<SwellConfig>
preview?: boolean
}): Promise<GetAllPagesResult<T>>
async function getAllPages({
config: cfg,
preview,
}: {
url?: string
config?: Partial<SwellConfig>
preview?: boolean
} = {}): Promise<GetAllPagesResult> {
const config = commerce.getConfig(cfg)
const { locale, fetch } = config
const data = await fetch('content', 'list', ['pages'])
const pages =
data?.results?.map(({ slug, ...rest }: { slug: string }) => ({
url: `/${locale}/${slug}`,
...rest,
})) ?? []
return {
pages,
}
}
return getAllPages
}

View File

@ -0,0 +1,48 @@
import { SwellProduct } from '../../types'
import { SwellConfig, Provider } from '..'
import { OperationContext, OperationOptions } from '@commerce/api/operations'
import { GetAllProductPathsOperation } from '@commerce/types/product'
export default function getAllProductPathsOperation({
commerce,
}: OperationContext<Provider>) {
async function getAllProductPaths<
T extends GetAllProductPathsOperation
>(opts?: {
variables?: T['variables']
config?: SwellConfig
}): Promise<T['data']>
async function getAllProductPaths<T extends GetAllProductPathsOperation>(
opts: {
variables?: T['variables']
config?: SwellConfig
} & OperationOptions
): Promise<T['data']>
async function getAllProductPaths<T extends GetAllProductPathsOperation>({
variables,
config: cfg,
}: {
query?: string
variables?: T['variables']
config?: SwellConfig
} = {}): Promise<T['data']> {
const config = commerce.getConfig(cfg)
// RecursivePartial forces the method to check for every prop in the data, which is
// required in case there's a custom `query`
const { results } = await config.fetch('products', 'list', [
{
limit: variables?.first,
},
])
return {
products: results?.map(({ slug: handle }: SwellProduct) => ({
path: `/${handle}`,
})),
}
}
return getAllProductPaths
}

View File

@ -0,0 +1,43 @@
import { normalizeProduct } from '../../utils/normalize'
import { SwellProduct } from '../../types'
import { Product } from '@commerce/types/product'
import { Provider, SwellConfig } from '../'
import { OperationContext } from '@commerce/api/operations'
export type ProductVariables = { first?: number }
export default function getAllProductsOperation({
commerce,
}: OperationContext<Provider>) {
async function getAllProducts(opts?: {
variables?: ProductVariables
config?: Partial<SwellConfig>
preview?: boolean
}): Promise<{ products: Product[] }>
async function getAllProducts({
config: cfg,
variables = { first: 250 },
}: {
query?: string
variables?: ProductVariables
config?: Partial<SwellConfig>
preview?: boolean
} = {}): Promise<{ products: Product[] | any[] }> {
const config = commerce.getConfig(cfg)
const { results } = await config.fetch('products', 'list', [
{
limit: variables.first,
},
])
const products = results.map((product: SwellProduct) =>
normalizeProduct(product)
)
return {
products,
}
}
return getAllProducts
}

View File

@ -1,25 +1,54 @@
import { Page } from '../../schema' import { Page } from '../../schema'
import { SwellConfig, getConfig } from '..' import { SwellConfig, Provider } from '..'
import { OperationContext, OperationOptions } from '@commerce/api/operations'
import { GetPageOperation } from '../../types/page'
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
export type PageVariables = { export type PageVariables = {
id: string id: number
} }
async function getPage({ export default function getPageOperation({
url, commerce,
variables, }: OperationContext<Provider>) {
config, async function getPage<T extends GetPageOperation>(opts: {
preview, variables: T['variables']
}: { config?: Partial<SwellConfig>
url?: string preview?: boolean
variables: PageVariables }): Promise<T['data']>
config?: SwellConfig
preview?: boolean
}): Promise<GetPageResult> {
config = getConfig(config)
return {}
}
export default getPage async function getPage<T extends GetPageOperation>(
opts: {
variables: T['variables']
config?: Partial<SwellConfig>
preview?: boolean
} & OperationOptions
): Promise<T['data']>
async function getPage<T extends GetPageOperation>({
variables,
config,
}: {
query?: string
variables: T['variables']
config?: Partial<SwellConfig>
preview?: boolean
}): Promise<T['data']> {
const { fetch, locale = 'en-US' } = commerce.getConfig(config)
const id = variables.id
const result = await fetch('content', 'get', ['pages', id])
const page = result
return {
page: page
? {
...page,
url: `/${locale}/${page.slug}`,
}
: null,
}
}
return getPage
}

View File

@ -0,0 +1,33 @@
import { normalizeProduct } from '../../utils'
import { Product } from '@commerce/types/product'
import { OperationContext } from '@commerce/api/operations'
import { Provider, SwellConfig } from '../'
export default function getProductOperation({
commerce,
}: OperationContext<Provider>) {
async function getProduct({
variables,
config: cfg,
}: {
query?: string
variables: { slug: string }
config?: Partial<SwellConfig>
preview?: boolean
}): Promise<Product | {} | any> {
const config = commerce.getConfig(cfg)
const product = await config.fetch('products', 'get', [variables.slug])
if (product && product.variants) {
product.variants = product.variants?.results
}
return {
product: product ? normalizeProduct(product) : null,
}
}
return getProduct
}

View File

@ -0,0 +1,37 @@
import getCategories from '../../utils/get-categories'
import getVendors, { Brands } from '../../utils/get-vendors'
import { Provider, SwellConfig } from '../'
import { OperationContext } from '@commerce/api/operations'
import { Category } from '@commerce/types/site'
export type GetSiteInfoResult<
T extends { categories: any[]; brands: any[] } = {
categories: Category[]
brands: Brands
}
> = T
export default function getSiteInfoOperation({
commerce,
}: OperationContext<Provider>) {
async function getSiteInfo({
variables,
config: cfg,
}: {
query?: string
variables?: any
config?: Partial<SwellConfig>
preview?: boolean
} = {}): Promise<GetSiteInfoResult> {
const config = commerce.getConfig(cfg)
const categories = await getCategories(config)
const brands = await getVendors(config)
return {
categories,
brands,
}
}
return getSiteInfo
}

View File

@ -0,0 +1,46 @@
import type { ServerResponse } from 'http'
import type {
OperationContext,
OperationOptions,
} from '@commerce/api/operations'
import type { LoginOperation } from '../../types/login'
import { Provider, SwellConfig } from '..'
export default function loginOperation({
commerce,
}: OperationContext<Provider>) {
async function login<T extends LoginOperation>(opts: {
variables: T['variables']
config?: Partial<SwellConfig>
res: ServerResponse
}): Promise<T['data']>
async function login<T extends LoginOperation>(
opts: {
variables: T['variables']
config?: Partial<SwellConfig>
res: ServerResponse
} & OperationOptions
): Promise<T['data']>
async function login<T extends LoginOperation>({
variables,
res: response,
config: cfg,
}: {
query?: string
variables: T['variables']
res: ServerResponse
config?: Partial<SwellConfig>
}): Promise<T['data']> {
const config = commerce.getConfig(cfg)
const { data } = await config.fetch('account', 'login', [variables])
return {
result: data,
}
}
return login
}

View File

@ -1,58 +0,0 @@
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
import { SwellConfig, getConfig } from '..'
export type SwellApiHandler<
T = any,
H extends SwellHandlers = {},
Options extends {} = {}
> = (
req: NextApiRequest,
res: NextApiResponse<SwellApiResponse<T>>,
config: SwellConfig,
handlers: H,
// Custom configs that may be used by a particular handler
options: Options
) => void | Promise<void>
export type SwellHandler<T = any, Body = null> = (options: {
req: NextApiRequest
res: NextApiResponse<SwellApiResponse<T>>
config: SwellConfig
body: Body
}) => void | Promise<void>
export type SwellHandlers<T = any> = {
[k: string]: SwellHandler<T, any>
}
export type SwellApiResponse<T> = {
data: T | null
errors?: { message: string; code?: string }[]
}
export default function createApiHandler<
T = any,
H extends SwellHandlers = {},
Options extends {} = {}
>(
handler: SwellApiHandler<T, H, Options>,
handlers: H,
defaultOptions: Options
) {
return function getApiHandler({
config,
operations,
options,
}: {
config?: SwellConfig
operations?: Partial<H>
options?: Options extends {} ? Partial<Options> : never
} = {}): NextApiHandler {
const ops = { ...operations, ...handlers }
const opts = { ...defaultOptions, ...options }
return function apiHandler(req, res) {
return handler(req, res, getConfig(config), ops, opts)
}
}
}

View File

@ -3,12 +3,12 @@ import type { MutationHook } from '@commerce/utils/types'
import { CommerceError, ValidationError } from '@commerce/utils/errors' import { CommerceError, ValidationError } from '@commerce/utils/errors'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
import { import {
CustomerAccessTokenCreateInput,
CustomerUserError, CustomerUserError,
Mutation, Mutation,
MutationCheckoutCreateArgs, MutationCheckoutCreateArgs,
} from '../schema' } from '../schema'
import useLogin, { UseLogin } from '@commerce/auth/use-login' import useLogin, { UseLogin } from '@commerce/auth/use-login'
import { LoginHook } from '../types/login'
import { setCustomerToken } from '../utils' import { setCustomerToken } from '../utils'
export default useLogin as UseLogin<typeof handler> export default useLogin as UseLogin<typeof handler>
@ -22,7 +22,7 @@ const getErrorMessage = ({ code, message }: CustomerUserError) => {
return message return message
} }
export const handler: MutationHook<null, {}, CustomerAccessTokenCreateInput> = { export const handler: MutationHook<LoginHook> = {
fetchOptions: { fetchOptions: {
query: 'account', query: 'account',
method: 'login', method: 'login',

View File

@ -3,10 +3,11 @@ import type { MutationHook } from '@commerce/utils/types'
import useLogout, { UseLogout } from '@commerce/auth/use-logout' import useLogout, { UseLogout } from '@commerce/auth/use-logout'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
import { getCustomerToken, setCustomerToken } from '../utils/customer-token' import { getCustomerToken, setCustomerToken } from '../utils/customer-token'
import { LogoutHook } from '../types/logout'
export default useLogout as UseLogout<typeof handler> export default useLogout as UseLogout<typeof handler>
export const handler: MutationHook<null> = { export const handler: MutationHook<LogoutHook> = {
fetchOptions: { fetchOptions: {
query: 'account', query: 'account',
method: 'logout', method: 'logout',

View File

@ -3,18 +3,12 @@ import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors' import { CommerceError } from '@commerce/utils/errors'
import useSignup, { UseSignup } from '@commerce/auth/use-signup' import useSignup, { UseSignup } from '@commerce/auth/use-signup'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
import { CustomerCreateInput } from '../schema' import { SignupHook } from '../types/signup'
import handleLogin from '../utils/handle-login' import handleLogin from '../utils/handle-login'
export default useSignup as UseSignup<typeof handler> export default useSignup as UseSignup<typeof handler>
export const handler: MutationHook< export const handler: MutationHook<SignupHook> = {
null,
{},
CustomerCreateInput,
CustomerCreateInput
> = {
fetchOptions: { fetchOptions: {
query: 'account', query: 'account',
method: 'create', method: 'create',

View File

@ -2,14 +2,14 @@ import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors' import { CommerceError } from '@commerce/utils/errors'
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item' import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
import useCart from './use-cart' import useCart from './use-cart'
import { Cart, CartItemBody } from '../types'
import { checkoutToCart } from './utils' import { checkoutToCart } from './utils'
import { getCheckoutId } from '../utils' import { getCheckoutId } from '../utils'
import { useCallback } from 'react' import { useCallback } from 'react'
import { AddItemHook } from '../types/cart'
export default useAddItem as UseAddItem<typeof handler> export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<Cart, {}, CartItemBody> = { export const handler: MutationHook<AddItemHook> = {
fetchOptions: { fetchOptions: {
query: 'cart', query: 'cart',
method: 'addItem', method: 'addItem',
@ -24,7 +24,7 @@ export const handler: MutationHook<Cart, {}, CartItemBody> = {
}) })
} }
const variables: { const variables: {
product_id: string product_id: string | undefined
variant_id?: string variant_id?: string
checkoutId?: string checkoutId?: string
quantity?: number quantity?: number

View File

@ -1,13 +1,13 @@
import useCart, { UseCart } from '@commerce/cart/use-cart' import useCart, { UseCart } from '@commerce/cart/use-cart'
import { Cart } from '@commerce/types'
import { SWRHook } from '@commerce/utils/types' import { SWRHook } from '@commerce/utils/types'
import { useMemo } from 'react' import { useMemo } from 'react'
import { normalizeCart } from '../utils/normalize' import { normalizeCart } from '../utils/normalize'
import { checkoutCreate, checkoutToCart } from './utils' import { checkoutCreate, checkoutToCart } from './utils'
import type { GetCartHook } from '@commerce/types/cart'
export default useCart as UseCart<typeof handler> export default useCart as UseCart<typeof handler>
export const handler: SWRHook<Cart | null, {}, any, { isEmpty?: boolean }> = { export const handler: SWRHook<GetCartHook> = {
fetchOptions: { fetchOptions: {
query: 'cart', query: 'cart',
method: 'get', method: 'get',

View File

@ -1,29 +1,21 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { import type {
MutationHookContext, MutationHookContext,
HookFetcherContext, HookFetcherContext,
} from '@commerce/utils/types' } from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors' import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
import type { Cart, LineItem, RemoveItemHook } from '@commerce/types/cart'
import useRemoveItem, {
RemoveItemInput as RemoveItemInputBase,
UseRemoveItem,
} from '@commerce/cart/use-remove-item'
import useCart from './use-cart' import useCart from './use-cart'
import { checkoutToCart } from './utils' import { checkoutToCart } from './utils'
import { Cart, LineItem } from '../types'
import { RemoveCartItemBody } from '@commerce/types'
export type RemoveItemFn<T = any> = T extends LineItem export type RemoveItemFn<T = any> = T extends LineItem
? (input?: RemoveItemInput<T>) => Promise<Cart | null> ? (input?: RemoveItemActionInput<T>) => Promise<Cart | null | undefined>
: (input: RemoveItemInput<T>) => Promise<Cart | null> : (input: RemoveItemActionInput<T>) => Promise<Cart | null>
export type RemoveItemInput<T = any> = T extends LineItem export type RemoveItemActionInput<T = any> = T extends LineItem
? Partial<RemoveItemInputBase> ? Partial<RemoveItemHook['actionInput']>
: RemoveItemInputBase : RemoveItemHook['actionInput']
export default useRemoveItem as UseRemoveItem<typeof handler> export default useRemoveItem as UseRemoveItem<typeof handler>
@ -36,36 +28,22 @@ export const handler = {
input: { itemId }, input: { itemId },
options, options,
fetch, fetch,
}: HookFetcherContext<RemoveCartItemBody>) { }: HookFetcherContext<RemoveItemHook>) {
const response = await fetch({ const response = await fetch({ ...options, variables: [itemId] })
...options,
variables: [itemId],
})
return checkoutToCart(response) return checkoutToCart(response)
}, },
useHook: ({ useHook: ({ fetch }: MutationHookContext<RemoveItemHook>) => () => {
fetch,
}: MutationHookContext<Cart | null, RemoveCartItemBody>) => <
T extends LineItem | undefined = undefined
>(
ctx: { item?: T } = {}
) => {
const { item } = ctx
const { mutate } = useCart() const { mutate } = useCart()
const removeItem: RemoveItemFn<LineItem> = async (input) => {
const itemId = input?.id ?? item?.id
if (!itemId) { return useCallback(
throw new ValidationError({ async function removeItem(input) {
message: 'Invalid input used for this operation', const data = await fetch({ input: { itemId: input.id } })
}) await mutate(data, false)
}
const data = await fetch({ input: { itemId } }) return data
await mutate(data, false) },
return data [fetch, mutate]
} )
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
}, },
} }

View File

@ -2,25 +2,30 @@ import { useCallback } from 'react'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import type { import type {
HookFetcherContext, HookFetcherContext,
MutationHook,
MutationHookContext, MutationHookContext,
} from '@commerce/utils/types' } from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors' import { ValidationError } from '@commerce/utils/errors'
import useUpdateItem, { // import useUpdateItem, {
UpdateItemInput as UpdateItemInputBase, // UpdateItemInput as UpdateItemInputBase,
UseUpdateItem, // UseUpdateItem,
} from '@commerce/cart/use-update-item' // } from '@commerce/cart/use-update-item'
import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
import useCart from './use-cart' import useCart from './use-cart'
import { handler as removeItemHandler } from './use-remove-item' import { handler as removeItemHandler } from './use-remove-item'
import type { Cart, LineItem, UpdateCartItemBody } from '../types' import { CartItemBody, LineItem } from '@commerce/types/cart'
import { checkoutToCart } from './utils' import { checkoutToCart } from './utils'
import { UpdateItemHook } from '../types/cart'
export type UpdateItemInput<T = any> = T extends LineItem // export type UpdateItemInput<T = any> = T extends LineItem
? Partial<UpdateItemInputBase<LineItem>> // ? Partial<UpdateItemInputBase<LineItem>>
: UpdateItemInputBase<LineItem> // : UpdateItemInputBase<LineItem>
export default useUpdateItem as UseUpdateItem<typeof handler> export default useUpdateItem as UseUpdateItem<typeof handler>
export type UpdateItemActionInput<T = any> = T extends LineItem
? Partial<UpdateItemHook['actionInput']>
: UpdateItemHook['actionInput']
export const handler = { export const handler = {
fetchOptions: { fetchOptions: {
query: 'cart', query: 'cart',
@ -30,7 +35,7 @@ export const handler = {
input: { itemId, item }, input: { itemId, item },
options, options,
fetch, fetch,
}: HookFetcherContext<UpdateCartItemBody>) { }: HookFetcherContext<UpdateItemHook>) {
if (Number.isInteger(item.quantity)) { if (Number.isInteger(item.quantity)) {
// Also allow the update hook to remove an item if the quantity is lower than 1 // Also allow the update hook to remove an item if the quantity is lower than 1
if (item.quantity! < 1) { if (item.quantity! < 1) {
@ -52,9 +57,7 @@ export const handler = {
return checkoutToCart(response) return checkoutToCart(response)
}, },
useHook: ({ useHook: ({ fetch }: MutationHookContext<UpdateItemHook>) => <
fetch,
}: MutationHookContext<Cart | null, UpdateCartItemBody>) => <
T extends LineItem | undefined = undefined T extends LineItem | undefined = undefined
>( >(
ctx: { ctx: {
@ -66,7 +69,7 @@ export const handler = {
const { mutate, data: cartData } = useCart() as any const { mutate, data: cartData } = useCart() as any
return useCallback( return useCallback(
debounce(async (input: UpdateItemInput<T>) => { debounce(async (input: UpdateItemActionInput) => {
const firstLineItem = cartData.lineItems[0] const firstLineItem = cartData.lineItems[0]
const itemId = item?.id || firstLineItem.id const itemId = item?.id || firstLineItem.id
const productId = item?.productId || firstLineItem.productId const productId = item?.productId || firstLineItem.productId

View File

@ -9,7 +9,7 @@ export const checkoutCreate = async (fetch: any) => {
}) })
if (!cart) { if (!cart) {
const cart = await fetch({ await fetch({
query: 'cart', query: 'cart',
method: 'setItems', method: 'setItems',
variables: [[]], variables: [[]],

View File

@ -1,33 +0,0 @@
import { HookFetcherFn } from '@commerce/utils/types'
import { Cart } from '@commerce/types'
// import { checkoutCreate, checkoutToCart } from '.'
import { FetchCartInput } from '@commerce/cart/use-cart'
import { data } from 'autoprefixer'
import { normalizeCart } from '../../utils'
const fetcher: HookFetcherFn<Cart | null, FetchCartInput> = async ({
options,
// input: { cartId: checkoutId },
fetch,
}) => {
let checkout
// if (checkoutId) {
const data = await fetch({
query: 'cart',
method: 'get',
// variables: { category: categoryId },
})
// checkout = data.node
// }
// if (checkout?.completedAt || !checkoutId) {
// checkout = await checkoutCreate(fetch)
// }
// TODO: Fix this type
// return checkoutToCart({ checkout } as any)
return normalizeCart(data)
}
export default fetcher

View File

@ -1,37 +0,0 @@
import { getConfig, SwellConfig } from '../api'
type Variables = {
first?: number
}
type ReturnType = {
pages: Page[]
}
export type Page = {
id: string
name: string
url: string
sort_order?: number
body: string
}
const getAllPages = async (options?: {
variables?: Variables
config: SwellConfig
preview?: boolean
}): Promise<ReturnType> => {
let { config, variables = { first: 250 } } = options ?? {}
config = getConfig(config)
const { locale, fetch } = config
const data = await fetch('content', 'list', ['pages'])
const pages =
data?.results?.map(({ slug, ...rest }: { slug: string }) => ({
url: `/${locale}/${slug}`,
...rest,
})) ?? []
return { pages }
}
export default getAllPages

View File

@ -1,33 +0,0 @@
import { getConfig, SwellConfig } from '../api'
import { Page } from './get-all-pages'
type Variables = {
id: string
}
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
const getPage = async (options: {
variables: Variables
config: SwellConfig
preview?: boolean
}): Promise<GetPageResult> => {
let { config, variables } = options ?? {}
config = getConfig(config)
const { locale } = config
const { id } = variables
const result = await config.fetch('content', 'get', ['pages', id])
const page = result
return {
page: page
? {
...page,
url: `/${locale}/${page.slug}`,
}
: null,
}
}
export default getPage

View File

@ -1,31 +0,0 @@
import getCategories from '../utils/get-categories'
import getVendors, { Brands } from '../utils/get-vendors'
import { Category } from '@commerce/types'
import { getConfig, SwellConfig } from '../api'
export type GetSiteInfoResult<
T extends { categories: any[]; brands: any[] } = {
categories: Category[]
brands: Brands
}
> = T
const getSiteInfo = async (options?: {
variables?: any
config: SwellConfig
preview?: boolean
}): Promise<GetSiteInfoResult> => {
let { config } = options ?? {}
config = getConfig(config)
const categories = await getCategories(config)
const brands = await getVendors(config)
return {
categories,
brands,
}
}
export default getSiteInfo

View File

@ -1,11 +1,11 @@
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer' import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
import { Customer } from '@commerce/types'
import { SWRHook } from '@commerce/utils/types' import { SWRHook } from '@commerce/utils/types'
import { normalizeCustomer } from '../utils/normalize' import { normalizeCustomer } from '../utils/normalize'
import type { CustomerHook } from '../types/customer'
export default useCustomer as UseCustomer<typeof handler> export default useCustomer as UseCustomer<typeof handler>
export const handler: SWRHook<Customer | null> = { export const handler: SWRHook<CustomerHook> = {
fetchOptions: { fetchOptions: {
query: 'account', query: 'account',
method: 'get', method: 'get',
@ -25,26 +25,3 @@ export const handler: SWRHook<Customer | null> = {
}) })
}, },
} }
// const handler = (): { data: Customer } => {
// const swell = getContext();
// const response = swell.account.get();
// const { firstName, lastName, email, company, customerGroupId, notes, phone,
// entityId, addressCount, attributeCount, storeCredit } = response;
// return {
// data: {
// firstName,
// lastName,
// email,
// company,
// customerGroupId,
// notes,
// phone,
// entityId,
// addressCount,
// attributeCount,
// storeCredit
// }
// }
// }
// export default handler;

View File

@ -1,28 +0,0 @@
import { CollectionEdge } from '../schema'
import { getConfig, SwellConfig } from '../api'
const getAllCollections = async (options?: {
variables?: any
config: SwellConfig
preview?: boolean
}) => {
let { config, variables = { limit: 25 } } = options ?? {}
config = getConfig(config)
const response = await config.fetch('categories', 'list', { variables })
const edges = response.results ?? []
const categories = edges.map(
({ node: { id: entityId, title: name, handle } }: CollectionEdge) => ({
entityId,
name,
path: `/${handle}`,
})
)
return {
categories,
}
}
export default getAllCollections

View File

@ -1,39 +0,0 @@
import { SwellProduct } from '../types'
import { getConfig, SwellConfig } from '../api'
type ProductPath = {
path: string
}
export type ProductPathNode = {
node: ProductPath
}
type ReturnType = {
products: ProductPathNode[]
}
const getAllProductPaths = async (options?: {
variables?: any
config?: SwellConfig
preview?: boolean
}): Promise<ReturnType> => {
let { config, variables = [{ limit: 100 }] } = options ?? {}
config = getConfig(config)
const { results } = await config.fetch('products', 'list', [
{
limit: variables.first,
},
])
return {
products: results?.map(({ slug: handle }: SwellProduct) => ({
node: {
path: `/${handle}`,
},
})),
}
}
export default getAllProductPaths

View File

@ -1,36 +0,0 @@
import { getConfig, SwellConfig } from '../api'
import { normalizeProduct } from '../utils/normalize'
import { Product } from '@commerce/types'
import { SwellProduct } from '../types'
type Variables = {
first?: number
field?: string
}
type ReturnType = {
products: Product[]
}
const getAllProducts = async (options: {
variables?: Variables
config?: SwellConfig
preview?: boolean
}): Promise<ReturnType> => {
let { config, variables = { first: 250 } } = options ?? {}
config = getConfig(config)
const { results } = await config.fetch('products', 'list', [
{
limit: variables.first,
},
])
const products = results.map((product: SwellProduct) =>
normalizeProduct(product)
)
return {
products,
}
}
export default getAllProducts

View File

@ -1,32 +0,0 @@
import { GraphQLFetcherResult } from '@commerce/api'
import { getConfig, SwellConfig } from '../api'
import { normalizeProduct } from '../utils'
type Variables = {
slug: string
}
type ReturnType = {
product: any
}
const getProduct = async (options: {
variables: Variables
config: SwellConfig
preview?: boolean
}): Promise<ReturnType> => {
let { config, variables } = options ?? {}
config = getConfig(config)
const product = await config.fetch('products', 'get', [variables.slug])
if (product && product.variants) {
product.variants = product.variants?.results
}
return {
product: product ? normalizeProduct(product) : null,
}
}
export default getProduct

View File

@ -0,0 +1,2 @@
export { default as usePrice } from './use-price'
export { default as useSearch } from './use-search'

View File

@ -1,11 +1,8 @@
import { SWRHook } from '@commerce/utils/types' import { SWRHook } from '@commerce/utils/types'
import useSearch, { UseSearch } from '@commerce/product/use-search' import useSearch, { UseSearch } from '@commerce/product/use-search'
import { normalizeProduct } from '../utils' import { normalizeProduct } from '../utils'
import { Product } from '@commerce/types'
import { SwellProduct } from '../types' import { SwellProduct } from '../types'
import type { SearchProductsHook } from '../types/product'
export default useSearch as UseSearch<typeof handler> export default useSearch as UseSearch<typeof handler>
@ -16,18 +13,9 @@ export type SearchProductsInput = {
sort?: string sort?: string
} }
export type SearchProductsData = { export const handler: SWRHook<SearchProductsHook> = {
products: Product[]
found: boolean
}
export const handler: SWRHook<
SearchProductsData,
SearchProductsInput,
SearchProductsInput
> = {
fetchOptions: { fetchOptions: {
query: 'products', // String(Math.random()), query: 'products',
method: 'list', method: 'list',
}, },
async fetcher({ input, options, fetch }) { async fetcher({ input, options, fetch }) {

View File

@ -1,3 +1,5 @@
import { Provider } from '@commerce'
import { SWELL_CHECKOUT_URL_COOKIE, STORE_DOMAIN } from './const' import { SWELL_CHECKOUT_URL_COOKIE, STORE_DOMAIN } from './const'
import { handler as useCart } from './cart/use-cart' import { handler as useCart } from './cart/use-cart'
@ -14,18 +16,15 @@ import { handler as useSignup } from './auth/use-signup'
import fetcher from './fetcher' import fetcher from './fetcher'
export const swellProvider = { export const swellProvider: Provider = {
locale: 'en-us', locale: 'en-us',
cartCookie: SWELL_CHECKOUT_URL_COOKIE, cartCookie: SWELL_CHECKOUT_URL_COOKIE,
storeDomain: STORE_DOMAIN, // storeDomain: STORE_DOMAIN,
fetcher, fetcher,
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem }, cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
customer: { useCustomer }, customer: { useCustomer },
products: { useSearch }, products: { useSearch },
auth: { useLogin, useLogout, useSignup }, auth: { useLogin, useLogout, useSignup },
features: {
wishlist: false,
},
} }
export type SwellProvider = typeof swellProvider export type SwellProvider = typeof swellProvider

View File

@ -1,4 +1,5 @@
import * as Core from '@commerce/types' import * as Core from '@commerce/types/cart'
import { Customer } from '@commerce/types'
import { CheckoutLineItem } from './schema' import { CheckoutLineItem } from './schema'
export type SwellImage = { export type SwellImage = {
@ -43,12 +44,18 @@ export type SwellVariant = {
name: string name: string
price?: number price?: number
stock_status?: string stock_status?: string
__type?: 'MultipleChoiceOption' | undefined
}
export interface SwellProductOptionValue {
id: string
label: string
hexColors?: string[]
} }
export interface ProductOptionValue { export interface ProductOptionValue {
label: string label: string
hexColors?: string[] hexColors?: string[]
id: string
} }
export type ProductOptions = { export type ProductOptions = {
@ -73,10 +80,7 @@ export interface SwellProduct {
variants: any[] variants: any[]
} }
export interface SwellCustomer extends Core.Customer { export type SwellCustomer = any
first_name: string
last_name: string
}
export type SwellCheckout = { export type SwellCheckout = {
id: string id: string
@ -106,17 +110,3 @@ export type CartItemBody = Core.CartItemBody & {
productId: string // The product id is always required for BC productId: string // The product id is always required for BC
optionSelections?: OptionSelections optionSelections?: OptionSelections
} }
export type GetCartHandlerBody = Core.GetCartHandlerBody
export type AddCartItemBody = Core.AddCartItemBody<CartItemBody>
export type AddCartItemHandlerBody = Core.AddCartItemHandlerBody<CartItemBody>
export type UpdateCartItemBody = Core.UpdateCartItemBody<CartItemBody>
export type UpdateCartItemHandlerBody = Core.UpdateCartItemHandlerBody<CartItemBody>
export type RemoveCartItemBody = Core.RemoveCartItemBody
export type RemoveCartItemHandlerBody = Core.RemoveCartItemHandlerBody

View File

@ -0,0 +1 @@
export * from '@commerce/types/cart'

View File

@ -0,0 +1 @@
export * from '@commerce/types/checkout'

View File

@ -0,0 +1 @@
export * from '@commerce/types/common'

View File

@ -0,0 +1 @@
export * from '@commerce/types/customer'

View File

@ -0,0 +1,25 @@
import * as Cart from './cart'
import * as Checkout from './checkout'
import * as Common from './common'
import * as Customer from './customer'
import * as Login from './login'
import * as Logout from './logout'
import * as Page from './page'
import * as Product from './product'
import * as Signup from './signup'
import * as Site from './site'
import * as Wishlist from './wishlist'
export type {
Cart,
Checkout,
Common,
Customer,
Login,
Logout,
Page,
Product,
Signup,
Site,
Wishlist,
}

View File

@ -0,0 +1,11 @@
import * as Core from '@commerce/types/login'
import { LoginBody, LoginTypes } from '@commerce/types/login'
export * from '@commerce/types/login'
export type LoginHook<T extends LoginTypes = LoginTypes> = {
data: null
actionInput: LoginBody
fetcherInput: LoginBody
body: T['body']
}

View File

@ -0,0 +1 @@
export * from '@commerce/types/logout'

View File

@ -0,0 +1 @@
export * from '@commerce/types/page'

View File

@ -0,0 +1 @@
export * from '@commerce/types/product'

View File

@ -0,0 +1 @@
export * from '@commerce/types/signup'

View File

@ -0,0 +1 @@
export * from '@commerce/types/site'

View File

@ -0,0 +1 @@
export * from '@commerce/types/wishlist'

View File

@ -1,5 +1,5 @@
import { SwellConfig } from '../api' import { SwellConfig } from '../api'
import { Category } from '@commerce/types' import { Category } from '../types/site'
const getCategories = async (config: SwellConfig): Promise<Category[]> => { const getCategories = async (config: SwellConfig): Promise<Category[]> => {
const data = await config.fetch('categories', 'get') const data = await config.fetch('categories', 'get')

View File

@ -1,6 +1,6 @@
import { Product, Customer } from '@commerce/types' import { Customer } from '../types/customer'
import { Product, ProductOption } from '../types/product'
import { MoneyV2, ProductOption } from '../schema' import { MoneyV2 } from '../schema'
import type { import type {
Cart, Cart,
@ -10,6 +10,7 @@ import type {
SwellImage, SwellImage,
SwellVariant, SwellVariant,
ProductOptionValue, ProductOptionValue,
SwellProductOptionValue,
SwellCart, SwellCart,
LineItem, LineItem,
} from '../types' } from '../types'
@ -21,8 +22,13 @@ const money = ({ amount, currencyCode }: MoneyV2) => {
} }
} }
type swellProductOption = {
id: string
name: string
values: any[]
}
type normalizedProductOption = { type normalizedProductOption = {
__typename?: string
id: string id: string
displayName: string displayName: string
values: ProductOptionValue[] values: ProductOptionValue[]
@ -32,11 +38,11 @@ const normalizeProductOption = ({
id, id,
name: displayName = '', name: displayName = '',
values = [], values = [],
}: ProductOption) => { }: swellProductOption): ProductOption => {
let returnValues = values.map((value) => { let returnValues = values.map((value) => {
let output: any = { let output: any = {
label: value.name, label: value.name,
id: value?.id || id, // id: value?.id || id,
} }
if (displayName.match(/colou?r/gi)) { if (displayName.match(/colou?r/gi)) {
output = { output = {
@ -68,7 +74,7 @@ const normalizeProductImages = (images: SwellImage[]) => {
const normalizeProductVariants = ( const normalizeProductVariants = (
variants: SwellVariant[], variants: SwellVariant[],
productOptions: normalizedProductOption[] productOptions: swellProductOption[]
) => { ) => {
return variants?.map( return variants?.map(
({ id, name, price, option_value_ids: optionValueIds = [] }) => { ({ id, name, price, option_value_ids: optionValueIds = [] }) => {
@ -79,22 +85,22 @@ const normalizeProductVariants = (
const options = optionValueIds.map((id) => { const options = optionValueIds.map((id) => {
const matchingOption = productOptions.find((option) => { const matchingOption = productOptions.find((option) => {
return option.values.find( return option.values.find(
(value: ProductOptionValue) => value.id == id (value: SwellProductOptionValue) => value.id == id
) )
}) })
return normalizeProductOption({ return normalizeProductOption({
id, id,
name: matchingOption?.displayName ?? '', name: matchingOption?.name ?? '',
values, values,
}) })
}) })
return { return {
id, id,
name, // name,
// sku: sku ?? id, // sku: sku ?? id,
price: price ?? null, // price: price ?? null,
listPrice: price ?? null, // listPrice: price ?? null,
// requiresShipping: true, // requiresShipping: true,
options, options,
} }
@ -116,11 +122,12 @@ export function normalizeProduct(swellProduct: SwellProduct): Product {
} = swellProduct } = swellProduct
// ProductView accesses variants for each product // ProductView accesses variants for each product
const emptyVariants = [{ options: [], id, name }] const emptyVariants = [{ options: [], id, name }]
const productOptions = options const productOptions = options
? options.map((o) => normalizeProductOption(o)) ? options.map((o) => normalizeProductOption(o))
: [] : []
const productVariants = variants const productVariants = variants
? normalizeProductVariants(variants, productOptions) ? normalizeProductVariants(variants, options)
: [] : []
const productImages = normalizeProductImages(images) const productImages = normalizeProductImages(images)