From ecafee83106fbbce2e27e088bf968c231ac95abd Mon Sep 17 00:00:00 2001 From: Jing Wang Date: Mon, 19 Oct 2020 09:10:45 -0400 Subject: [PATCH] add in initial wishlist api functions and hooks --- lib/bigcommerce/api/catalog/products.ts | 4 +- .../api/wishlist/handlers/add-item.ts | 30 ++++ .../api/wishlist/handlers/add-wishlist.ts | 25 +++ .../wishlist/handlers/get-all-wishlists.ts | 22 +++ .../api/wishlist/handlers/get-wishlist.ts | 21 +++ .../api/wishlist/handlers/remove-item.ts | 25 +++ .../api/wishlist/handlers/remove-wishlist.ts | 25 +++ .../api/wishlist/handlers/update-wishlist.ts | 27 ++++ lib/bigcommerce/api/wishlist/index.ts | 152 ++++++++++++++++++ lib/bigcommerce/wishlist/use-add-item.tsx | 46 ++++++ lib/bigcommerce/wishlist/use-remove-item.tsx | 51 ++++++ .../wishlist/use-wishlist-actions.tsx | 11 ++ lib/bigcommerce/wishlist/use-wishlist.tsx | 41 +++++ 13 files changed, 478 insertions(+), 2 deletions(-) create mode 100644 lib/bigcommerce/api/wishlist/handlers/add-item.ts create mode 100644 lib/bigcommerce/api/wishlist/handlers/add-wishlist.ts create mode 100644 lib/bigcommerce/api/wishlist/handlers/get-all-wishlists.ts create mode 100644 lib/bigcommerce/api/wishlist/handlers/get-wishlist.ts create mode 100644 lib/bigcommerce/api/wishlist/handlers/remove-item.ts create mode 100644 lib/bigcommerce/api/wishlist/handlers/remove-wishlist.ts create mode 100644 lib/bigcommerce/api/wishlist/handlers/update-wishlist.ts create mode 100644 lib/bigcommerce/api/wishlist/index.ts create mode 100644 lib/bigcommerce/wishlist/use-add-item.tsx create mode 100644 lib/bigcommerce/wishlist/use-remove-item.tsx create mode 100644 lib/bigcommerce/wishlist/use-wishlist-actions.tsx create mode 100644 lib/bigcommerce/wishlist/use-wishlist.tsx diff --git a/lib/bigcommerce/api/catalog/products.ts b/lib/bigcommerce/api/catalog/products.ts index f3c01d934..0af21149e 100644 --- a/lib/bigcommerce/api/catalog/products.ts +++ b/lib/bigcommerce/api/catalog/products.ts @@ -22,7 +22,7 @@ export type ProductsHandlers = { const METHODS = ['GET'] // TODO: a complete implementation should have schema validation for `req.body` -const cartApi: BigcommerceApiHandler< +const productApi: BigcommerceApiHandler< SearchProductsData, ProductsHandlers > = async (req, res, config, handlers) => { @@ -45,4 +45,4 @@ const cartApi: BigcommerceApiHandler< export const handlers = { getProducts } -export default createApiHandler(cartApi, handlers, {}) +export default createApiHandler(productApi, handlers, {}) diff --git a/lib/bigcommerce/api/wishlist/handlers/add-item.ts b/lib/bigcommerce/api/wishlist/handlers/add-item.ts new file mode 100644 index 000000000..d6678031f --- /dev/null +++ b/lib/bigcommerce/api/wishlist/handlers/add-item.ts @@ -0,0 +1,30 @@ +import type { WishlistHandlers } from '..' + +// Return current wishlist info +const addItem: WishlistHandlers['addItem'] = async ({ + res, + body: { wishlistId, item }, + config, +}) => { + if (!item) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Missing item' }], + }) + } + + const options = { + method: 'POST', + body: JSON.stringify({ + items: [item], + }), + } + const { data } = await config.storeApiFetch( + `/v3/wishlists/${wishlistId}/items`, + options + ) + + res.status(200).json({ data }) +} + +export default addItem diff --git a/lib/bigcommerce/api/wishlist/handlers/add-wishlist.ts b/lib/bigcommerce/api/wishlist/handlers/add-wishlist.ts new file mode 100644 index 000000000..b4090adaa --- /dev/null +++ b/lib/bigcommerce/api/wishlist/handlers/add-wishlist.ts @@ -0,0 +1,25 @@ +import type { WishlistHandlers } from '..' + +// Return current wishlist info +const addWishlist: WishlistHandlers['addWishlist'] = async ({ + res, + body: { wishlist }, + config, +}) => { + if (!wishlist) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Missing wishlist data' }], + }) + } + + const options = { + method: 'POST', + body: JSON.stringify(wishlist), + } + const { data } = await config.storeApiFetch(`/v3/wishlists/`, options) + + res.status(200).json({ data }) +} + +export default addWishlist diff --git a/lib/bigcommerce/api/wishlist/handlers/get-all-wishlists.ts b/lib/bigcommerce/api/wishlist/handlers/get-all-wishlists.ts new file mode 100644 index 000000000..310c7f41d --- /dev/null +++ b/lib/bigcommerce/api/wishlist/handlers/get-all-wishlists.ts @@ -0,0 +1,22 @@ +import { BigcommerceApiError } from '../../utils/errors' +import type { WishlistList, WishlistHandlers } from '..' + +// Return all wishlists +const getAllWishlists: WishlistHandlers['getAllWishlists'] = async ({ + res, + body: { customerId }, + config, +}) => { + let result: { data?: WishlistList } = {} + + try { + result = await config.storeApiFetch(`/v3/wishlists/customer_id=${customerId}`) + } catch (error) { + throw error + } + + const data = (result.data ?? []) as any + res.status(200).json({ data }) +} + +export default getAllWishlists diff --git a/lib/bigcommerce/api/wishlist/handlers/get-wishlist.ts b/lib/bigcommerce/api/wishlist/handlers/get-wishlist.ts new file mode 100644 index 000000000..1fcbeaed8 --- /dev/null +++ b/lib/bigcommerce/api/wishlist/handlers/get-wishlist.ts @@ -0,0 +1,21 @@ +import { BigcommerceApiError } from '../../utils/errors' +import type { Wishlist, WishlistHandlers } from '..' + +// Return wishlist info +const getWishlist: WishlistHandlers['getWishlist'] = async ({ + res, + body: { wishlistId }, + config, +}) => { + let result: { data?: Wishlist } = {} + + try { + result = await config.storeApiFetch(`/v3/wishlists/${wishlistId}`) + } catch (error) { + throw error + } + + res.status(200).json({ data: result.data ?? null }) +} + +export default getWishlist diff --git a/lib/bigcommerce/api/wishlist/handlers/remove-item.ts b/lib/bigcommerce/api/wishlist/handlers/remove-item.ts new file mode 100644 index 000000000..9be970f11 --- /dev/null +++ b/lib/bigcommerce/api/wishlist/handlers/remove-item.ts @@ -0,0 +1,25 @@ +import type { WishlistHandlers } from '..' + +// Return current wishlist info +const removeItem: WishlistHandlers['removeItem'] = async ({ + res, + body: { wishlistId, itemId }, + config, +}) => { + if (!wishlistId || !itemId) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + + const result = await config.storeApiFetch<{ data: any } | null>( + `/v3/wishlists/${wishlistId}/items/${itemId}`, + { method: 'DELETE' } + ) + const data = result?.data ?? null + + res.status(200).json({ data }) +} + +export default removeItem diff --git a/lib/bigcommerce/api/wishlist/handlers/remove-wishlist.ts b/lib/bigcommerce/api/wishlist/handlers/remove-wishlist.ts new file mode 100644 index 000000000..41b210024 --- /dev/null +++ b/lib/bigcommerce/api/wishlist/handlers/remove-wishlist.ts @@ -0,0 +1,25 @@ +import type { WishlistHandlers } from '..' + +// Return current wishlist info +const removeWishlist: WishlistHandlers['removeWishlist'] = async ({ + res, + body: { wishlistId }, + config, +}) => { + if (!wishlistId) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + + const result = await config.storeApiFetch<{ data: any } | null>( + `/v3/wishlists/${wishlistId}/`, + { method: 'DELETE' } + ) + const data = result?.data ?? null + + res.status(200).json({ data }) +} + +export default removeWishlist diff --git a/lib/bigcommerce/api/wishlist/handlers/update-wishlist.ts b/lib/bigcommerce/api/wishlist/handlers/update-wishlist.ts new file mode 100644 index 000000000..ea1753b65 --- /dev/null +++ b/lib/bigcommerce/api/wishlist/handlers/update-wishlist.ts @@ -0,0 +1,27 @@ +import type { WishlistHandlers } from '..' + +// Update wish info +const updateWishlist: WishlistHandlers['updateWishlist'] = async ({ + res, + body: { wishlistId, wishlist }, + config, +}) => { + if (!wishlistId || !wishlist) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + + const { data } = await config.storeApiFetch( + `/v3/wishlists/${wishlistId}/`, + { + method: 'PUT', + body: JSON.stringify(wishlist), + } + ) + + res.status(200).json({ data }) +} + +export default updateWishlist diff --git a/lib/bigcommerce/api/wishlist/index.ts b/lib/bigcommerce/api/wishlist/index.ts new file mode 100644 index 000000000..912d59491 --- /dev/null +++ b/lib/bigcommerce/api/wishlist/index.ts @@ -0,0 +1,152 @@ +import isAllowedMethod from '../utils/is-allowed-method' +import createApiHandler, { + BigcommerceApiHandler, + BigcommerceHandler, +} from '../utils/create-api-handler' +import { BigcommerceApiError } from '../utils/errors' +import getWishlist from './handlers/get-wishlist' +import getAllWishlists from './handlers/get-all-wishlists' +import addItem from './handlers/add-item' +import removeItem from './handlers/remove-item' +import updateWishlist from './handlers/update-wishlist' +import removeWishlist from './handlers/remove-wishlist' +import addWishlist from './handlers/add-wishlist' + +type Body = Partial | undefined + +export type ItemBody = { + product_id: number + variant_id: number +} + +export type AddItemBody = { wishlistId: string; item: ItemBody } + +export type RemoveItemBody = { wishlistId: string; itemId: string } + +export type WishlistBody = { + customer_id: number + is_public: number + name: string + items: any[] +} + +export type AddWishlistBody = { wishlist: WishlistBody } + +// TODO: this type should match: +// https://developer.bigcommerce.com/api-reference/store-management/wishlists/wishlists/wishlistsbyidget +export type Wishlist = { + id: string + customer_id: number + name: string + is_public: boolean + token: string + items: any[] + // TODO: add missing fields +} + +export type WishlistList = Wishlist[] + +export type WishlistHandlers = { + getAllWishlists: BigcommerceHandler + getWishlist: BigcommerceHandler + addWishlist: BigcommerceHandler< + Wishlist, + { wishlistId: string } & Body + > + updateWishlist: BigcommerceHandler< + Wishlist, + { wishlistId: string } & Body + > + addItem: BigcommerceHandler> + removeItem: BigcommerceHandler< + Wishlist, + { wishlistId: string } & Body + > + removeWishlist: BigcommerceHandler +} + +const METHODS = ['GET', 'POST', 'PUT', 'DELETE'] + +// TODO: a complete implementation should have schema validation for `req.body` +const wishlistApi: BigcommerceApiHandler = async ( + req, + res, + config, + handlers +) => { + if (!isAllowedMethod(req, res, METHODS)) return + + try { + const { wishlistId, itemId, customerId } = req.body + // Return current wishlist info + if (req.method === 'GET' && wishlistId) { + const body = { wishlistId: wishlistId as string } + return await handlers['getWishlist']({ req, res, config, body }) + } + + // Add an item to the wishlist + if (req.method === 'POST' && wishlistId) { + const body = { wishlistId, ...req.body } + return await handlers['addItem']({ req, res, config, body }) + } + + // Update a wishlist + if (req.method === 'PUT' && wishlistId) { + const body = { wishlistId, ...req.body } + return await handlers['updateWishlist']({ req, res, config, body }) + } + + // Remove an item from the wishlist + if (req.method === 'DELETE' && wishlistId && itemId) { + const body = { + wishlistId: wishlistId as string, + itemId: itemId as string, + } + return await handlers['removeItem']({ req, res, config, body }) + } + + // Remove the wishlist + if (req.method === 'DELETE' && wishlistId && !itemId) { + const body = { wishlistId: wishlistId as string } + return await handlers['removeWishlist']({ req, res, config, body }) + } + + // Get all the wishlists + if (req.method === 'GET' && !wishlistId) { + const body = { customerId: customerId as string } + return await handlers['getAllWishlists']({ + req, + res: res as any, + config, + body, + }) + } + + // Create a wishlist + if (req.method === 'POST' && !wishlistId) { + const { body } = req + return await handlers['addWishlist']({ req, res, config, body }) + } + } catch (error) { + console.error(error) + + const message = + error instanceof BigcommerceApiError + ? 'An unexpected error ocurred with the Bigcommerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +export const handlers = { + getWishlist, + addItem, + updateWishlist, + removeItem, + removeWishlist, + getAllWishlists, + addWishlist, +} + +export default createApiHandler(wishlistApi, handlers, {}) diff --git a/lib/bigcommerce/wishlist/use-add-item.tsx b/lib/bigcommerce/wishlist/use-add-item.tsx new file mode 100644 index 000000000..862041bbd --- /dev/null +++ b/lib/bigcommerce/wishlist/use-add-item.tsx @@ -0,0 +1,46 @@ +import { useCallback } from 'react' +import { HookFetcher } from '@lib/commerce/utils/types' +import useAction from '@lib/commerce/utils/use-action' +import type { ItemBody, AddItemBody } from '../api/wishlist' +import useWishlist, { Wishlist } from './use-wishlist' + +const defaultOpts = { + url: '/api/bigcommerce/wishlist', + method: 'POST', +} + +export type AddItemInput = ItemBody + +export const fetcher: HookFetcher = ( + options, + { wishlistId, item }, + fetch +) => { + return fetch({ + url: options?.url ?? defaultOpts.url, + method: options?.method ?? defaultOpts.method, + body: { wishlistId, item }, + }) +} + +export function extendHook(customFetcher: typeof fetcher) { + const useAddItem = (wishlistId: string) => { + const { mutate } = useWishlist(wishlistId) + const fn = useAction(defaultOpts, customFetcher) + + return useCallback( + async function addItem(input: AddItemInput) { + const data = await fn({ wishlistId, item: input }) + await mutate(data, false) + return data + }, + [fn, mutate] + ) + } + + useAddItem.extend = extendHook + + return useAddItem +} + +export default extendHook(fetcher) diff --git a/lib/bigcommerce/wishlist/use-remove-item.tsx b/lib/bigcommerce/wishlist/use-remove-item.tsx new file mode 100644 index 000000000..ca9648d35 --- /dev/null +++ b/lib/bigcommerce/wishlist/use-remove-item.tsx @@ -0,0 +1,51 @@ +import { useCallback } from 'react' +import { HookFetcher } from '@lib/commerce/utils/types' +import useAction from '@lib/commerce/utils/use-action' +import type { RemoveItemBody } from '../api/wishlist' +import useWishlist, { Wishlist } from './use-wishlist' + +const defaultOpts = { + url: '/api/bigcommerce/wishlists', + method: 'DELETE', +} + +export type RemoveItemInput = { + id: string +} + +export const fetcher: HookFetcher = ( + options, + { wishlistId, itemId }, + fetch +) => { + return fetch({ + url: options?.url ?? defaultOpts.url, + method: options?.method ?? defaultOpts.method, + body: { wishlistId, itemId }, + }) +} + +export function extendHook(customFetcher: typeof fetcher) { + const useRemoveItem = (wishlistId: string, item?: any) => { + const { mutate } = useWishlist(wishlistId) + const fn = useAction( + defaultOpts, + customFetcher + ) + + return useCallback( + async function removeItem(input: RemoveItemInput) { + const data = await fn({ wishlistId, itemId: input.id ?? item?.id }) + await mutate(data, false) + return data + }, + [fn, mutate] + ) + } + + useRemoveItem.extend = extendHook + + return useRemoveItem +} + +export default extendHook(fetcher) diff --git a/lib/bigcommerce/wishlist/use-wishlist-actions.tsx b/lib/bigcommerce/wishlist/use-wishlist-actions.tsx new file mode 100644 index 000000000..7301994b0 --- /dev/null +++ b/lib/bigcommerce/wishlist/use-wishlist-actions.tsx @@ -0,0 +1,11 @@ +import useAddItem from './use-add-item' +import useRemoveItem from './use-remove-item' + +// This hook is probably not going to be used, but it's here +// to show how a commerce should be structuring it +export default function useWishlistActions(wishlistId: string) { + const addItem = useAddItem(wishlistId) + const removeItem = useRemoveItem(wishlistId) + + return { addItem, removeItem } +} diff --git a/lib/bigcommerce/wishlist/use-wishlist.tsx b/lib/bigcommerce/wishlist/use-wishlist.tsx new file mode 100644 index 000000000..1d93609f0 --- /dev/null +++ b/lib/bigcommerce/wishlist/use-wishlist.tsx @@ -0,0 +1,41 @@ +import { HookFetcher } from '@lib/commerce/utils/types' +import useData from '@lib/commerce/utils/use-data' +import type { Wishlist } from '../api/wishlist' + +const defaultOpts = { + url: '/api/bigcommerce/wishlists', +} + +export type { Wishlist } + +export type WishlistInput = { + wishlistId: string | undefined +} + +export const fetcher: HookFetcher = ( + options, + { wishlistId }, + fetch +) => { + return fetch({ + url: options?.url, + body: { wishlistId }, + }) +} + +export function extendHook(customFetcher: typeof fetcher) { + const useWishlists = (wishlistId: string) => { + const fetchFn: typeof fetcher = (options, input, fetch) => { + return customFetcher(options, input, fetch) + } + const response = useData(defaultOpts, [['wishlistId', wishlistId]], fetchFn) + + return response + } + + useWishlists.extend = extendHook + + return useWishlists +} + +export default extendHook(fetcher)