mirror of
https://github.com/vercel/commerce.git
synced 2025-03-14 22:42:33 +00:00
Updated wishlist hooks, added more, updated API.
This commit is contained in:
parent
ada42e0f84
commit
bf71d1d41a
@ -1,4 +1,4 @@
|
||||
import parseItem from '../../utils/parse-item'
|
||||
import { parseCartItem } from '../../utils/parse-item'
|
||||
import getCartCookie from '../../utils/get-cart-cookie'
|
||||
import type { CartHandlers } from '..'
|
||||
|
||||
@ -19,7 +19,7 @@ const addItem: CartHandlers['addItem'] = async ({
|
||||
const options = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
line_items: [parseItem(item)],
|
||||
line_items: [parseCartItem(item)],
|
||||
}),
|
||||
}
|
||||
const { data } = cartId
|
||||
|
@ -1,4 +1,4 @@
|
||||
import parseItem from '../../utils/parse-item'
|
||||
import { parseCartItem } from '../../utils/parse-item'
|
||||
import getCartCookie from '../../utils/get-cart-cookie'
|
||||
import type { CartHandlers } from '..'
|
||||
|
||||
@ -20,7 +20,7 @@ const updateItem: CartHandlers['updateItem'] = async ({
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
line_item: parseItem(item),
|
||||
line_item: parseCartItem(item),
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
@ -13,7 +13,7 @@ async function getAllPages(opts?: {
|
||||
preview?: boolean
|
||||
}): Promise<GetAllPagesResult>
|
||||
|
||||
async function getAllPages<T extends { pages: any[] }, V = any>(opts: {
|
||||
async function getAllPages<T extends { pages: any[] }>(opts: {
|
||||
url: string
|
||||
config?: BigcommerceConfig
|
||||
preview?: boolean
|
||||
|
34
lib/bigcommerce/api/operations/get-customer-id.ts
Normal file
34
lib/bigcommerce/api/operations/get-customer-id.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { GetCustomerIdQuery } from '@lib/bigcommerce/schema'
|
||||
import { BigcommerceConfig, getConfig } from '..'
|
||||
|
||||
export const getCustomerIdQuery = /* GraphQL */ `
|
||||
query getCustomerId {
|
||||
customer {
|
||||
entityId
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
async function getCustomerId({
|
||||
customerToken,
|
||||
config,
|
||||
}: {
|
||||
customerToken: string
|
||||
config?: BigcommerceConfig
|
||||
}): Promise<number | undefined> {
|
||||
config = getConfig(config)
|
||||
|
||||
const { data } = await config.fetch<GetCustomerIdQuery>(
|
||||
getCustomerIdQuery,
|
||||
undefined,
|
||||
{
|
||||
headers: {
|
||||
cookie: `${config.customerCookie}=${customerToken}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return data?.customer?.entityId
|
||||
}
|
||||
|
||||
export default getCustomerId
|
51
lib/bigcommerce/api/operations/get-customer-wishlist.ts
Normal file
51
lib/bigcommerce/api/operations/get-customer-wishlist.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
||||
import { BigcommerceConfig, getConfig } from '..'
|
||||
import { definitions } from '../definitions/wishlist'
|
||||
|
||||
export type Wishlist = definitions['wishlist_Full']
|
||||
|
||||
export type GetCustomerWishlistResult<
|
||||
T extends { wishlist?: any } = { wishlist?: Wishlist }
|
||||
> = T
|
||||
|
||||
export type GetCustomerWishlistVariables = {
|
||||
customerId: number
|
||||
}
|
||||
|
||||
async function getCustomerWishlist(opts: {
|
||||
variables: GetCustomerWishlistVariables
|
||||
config?: BigcommerceConfig
|
||||
preview?: boolean
|
||||
}): Promise<GetCustomerWishlistResult>
|
||||
|
||||
async function getCustomerWishlist<
|
||||
T extends { wishlist?: any },
|
||||
V = any
|
||||
>(opts: {
|
||||
url: string
|
||||
variables: V
|
||||
config?: BigcommerceConfig
|
||||
preview?: boolean
|
||||
}): Promise<GetCustomerWishlistResult<T>>
|
||||
|
||||
async function getCustomerWishlist({
|
||||
config,
|
||||
variables,
|
||||
}: {
|
||||
url?: string
|
||||
variables: GetCustomerWishlistVariables
|
||||
config?: BigcommerceConfig
|
||||
preview?: boolean
|
||||
}): Promise<GetCustomerWishlistResult> {
|
||||
config = getConfig(config)
|
||||
|
||||
const { data } = await config.storeApiFetch<
|
||||
RecursivePartial<{ data: Wishlist[] }>
|
||||
>(`/v3/wishlists/customer_id=${variables.customerId}`)
|
||||
const wishlists = (data as RecursiveRequired<typeof data>) ?? []
|
||||
const wishlist = wishlists[0]
|
||||
|
||||
return { wishlist }
|
||||
}
|
||||
|
||||
export default getCustomerWishlist
|
@ -1,9 +1,13 @@
|
||||
import type { ItemBody as WishlistItemBody } from '../wishlist'
|
||||
import type { ItemBody } from '../cart'
|
||||
|
||||
const parseItem = (item: ItemBody) => ({
|
||||
quantity: item.quantity,
|
||||
export const parseWishlistItem = (item: WishlistItemBody) => ({
|
||||
product_id: item.productId,
|
||||
variant_id: item.variantId,
|
||||
})
|
||||
|
||||
export default parseItem
|
||||
export const parseCartItem = (item: ItemBody) => ({
|
||||
quantity: item.quantity,
|
||||
product_id: item.productId,
|
||||
variant_id: item.variantId,
|
||||
})
|
||||
|
@ -1,9 +1,12 @@
|
||||
import type { WishlistHandlers } from '..'
|
||||
import getCustomerId from '../../operations/get-customer-id'
|
||||
import getCustomerWishlist from '../../operations/get-customer-wishlist'
|
||||
import { parseWishlistItem } from '../../utils/parse-item'
|
||||
|
||||
// Return current wishlist info
|
||||
// Returns the wishlist of the signed customer
|
||||
const addItem: WishlistHandlers['addItem'] = async ({
|
||||
res,
|
||||
body: { wishlistId, item },
|
||||
body: { customerToken, item },
|
||||
config,
|
||||
}) => {
|
||||
if (!item) {
|
||||
@ -13,16 +16,36 @@ const addItem: WishlistHandlers['addItem'] = async ({
|
||||
})
|
||||
}
|
||||
|
||||
const customerId =
|
||||
customerToken && (await getCustomerId({ customerToken, config }))
|
||||
|
||||
if (!customerId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
|
||||
const { wishlist } = await getCustomerWishlist({
|
||||
variables: { customerId },
|
||||
config,
|
||||
})
|
||||
const options = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
items: [item],
|
||||
}),
|
||||
body: JSON.stringify(
|
||||
wishlist
|
||||
? {
|
||||
items: [parseWishlistItem(item)],
|
||||
}
|
||||
: {
|
||||
customer_id: customerId,
|
||||
items: [parseWishlistItem(item)],
|
||||
}
|
||||
),
|
||||
}
|
||||
const { data } = await config.storeApiFetch(
|
||||
`/v3/wishlists/${wishlistId}/items`,
|
||||
options
|
||||
)
|
||||
const { data } = wishlist
|
||||
? await config.storeApiFetch(`/v3/wishlists/${wishlist.id}/items`, options)
|
||||
: await config.storeApiFetch('/v3/wishlists', options)
|
||||
|
||||
res.status(200).json({ data })
|
||||
}
|
||||
|
@ -1,17 +1,32 @@
|
||||
import getCustomerId from '../../operations/get-customer-id'
|
||||
import type { Wishlist, WishlistHandlers } from '..'
|
||||
import getCustomerWishlist from '../../operations/get-customer-wishlist'
|
||||
|
||||
// Return wishlist info
|
||||
const getWishlist: WishlistHandlers['getWishlist'] = async ({
|
||||
res,
|
||||
body: { wishlistId },
|
||||
body: { customerToken },
|
||||
config,
|
||||
}) => {
|
||||
let result: { data?: Wishlist } = {}
|
||||
|
||||
try {
|
||||
result = await config.storeApiFetch(`/v3/wishlists/${wishlistId}`)
|
||||
} catch (error) {
|
||||
throw error
|
||||
if (customerToken) {
|
||||
const customerId =
|
||||
customerToken && (await getCustomerId({ customerToken, config }))
|
||||
|
||||
if (!customerId) {
|
||||
// If the customerToken is invalid, then this request is too
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Wishlist not found' }],
|
||||
})
|
||||
}
|
||||
|
||||
const { wishlist } = await getCustomerWishlist({
|
||||
variables: { customerId },
|
||||
config,
|
||||
})
|
||||
result = { data: wishlist }
|
||||
}
|
||||
|
||||
res.status(200).json({ data: result.data ?? null })
|
||||
|
@ -11,15 +11,16 @@ import removeItem from './handlers/remove-item'
|
||||
import updateWishlist from './handlers/update-wishlist'
|
||||
import removeWishlist from './handlers/remove-wishlist'
|
||||
import addWishlist from './handlers/add-wishlist'
|
||||
import { definitions } from '../definitions/wishlist'
|
||||
|
||||
type Body<T> = Partial<T> | undefined
|
||||
|
||||
export type ItemBody = {
|
||||
product_id: number
|
||||
variant_id: number
|
||||
productId: number
|
||||
variantId: number
|
||||
}
|
||||
|
||||
export type AddItemBody = { wishlistId: string; item: ItemBody }
|
||||
export type AddItemBody = { item: ItemBody }
|
||||
|
||||
export type RemoveItemBody = { wishlistId: string; itemId: string }
|
||||
|
||||
@ -32,21 +33,11 @@ export type WishlistBody = {
|
||||
|
||||
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 Wishlist = definitions['wishlist_Full']
|
||||
|
||||
export type WishlistHandlers = {
|
||||
getAllWishlists: BigcommerceHandler<Wishlist[], { customerId?: string }>
|
||||
getWishlist: BigcommerceHandler<Wishlist, { wishlistId?: string }>
|
||||
getWishlist: BigcommerceHandler<Wishlist, { customerToken?: string }>
|
||||
addWishlist: BigcommerceHandler<
|
||||
Wishlist,
|
||||
{ wishlistId: string } & Body<AddWishlistBody>
|
||||
@ -57,7 +48,7 @@ export type WishlistHandlers = {
|
||||
>
|
||||
addItem: BigcommerceHandler<
|
||||
Wishlist,
|
||||
{ wishlistId: string } & Body<AddItemBody>
|
||||
{ customerToken?: string } & Body<AddItemBody>
|
||||
>
|
||||
removeItem: BigcommerceHandler<
|
||||
Wishlist,
|
||||
@ -77,17 +68,21 @@ const wishlistApi: BigcommerceApiHandler<Wishlist, WishlistHandlers> = async (
|
||||
) => {
|
||||
if (!isAllowedMethod(req, res, METHODS)) return
|
||||
|
||||
const { cookies } = req
|
||||
const customerToken = cookies[config.customerCookie]
|
||||
|
||||
try {
|
||||
const { wishlistId, itemId, customerId } = req.body
|
||||
|
||||
// Return current wishlist info
|
||||
if (req.method === 'GET' && wishlistId) {
|
||||
const body = { wishlistId: wishlistId as string }
|
||||
const body = { customerToken }
|
||||
return await handlers['getWishlist']({ req, res, config, body })
|
||||
}
|
||||
|
||||
// Add an item to the wishlist
|
||||
if (req.method === 'POST' && wishlistId) {
|
||||
const body = { ...req.body, wishlistId }
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, customerToken }
|
||||
return await handlers['addItem']({ req, res, config, body })
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ export const fetcher: HookFetcher<Cart, AddItemBody> = (
|
||||
export function extendHook(customFetcher: typeof fetcher) {
|
||||
const useAddItem = () => {
|
||||
const { mutate } = useCart()
|
||||
const fn = useCartAddItem<Cart, AddItemBody>(defaultOpts, customFetcher)
|
||||
const fn = useCartAddItem(defaultOpts, customFetcher)
|
||||
|
||||
return useCallback(
|
||||
async function addItem(input: AddItemInput) {
|
||||
|
@ -23,23 +23,23 @@ export function extendHook(
|
||||
swrOptions?: SwrOptions<Cart | null, CartInput>
|
||||
) {
|
||||
const useCart = () => {
|
||||
const cart = useCommerceCart(defaultOpts, [], customFetcher, {
|
||||
const response = useCommerceCart(defaultOpts, [], customFetcher, {
|
||||
revalidateOnFocus: false,
|
||||
...swrOptions,
|
||||
})
|
||||
|
||||
// Uses a getter to only calculate the prop when required
|
||||
// cart.data is also a getter and it's better to not trigger it early
|
||||
Object.defineProperty(cart, 'isEmpty', {
|
||||
// response.data is also a getter and it's better to not trigger it early
|
||||
Object.defineProperty(response, 'isEmpty', {
|
||||
get() {
|
||||
return Object.values(cart.data?.line_items ?? {}).every(
|
||||
return Object.values(response.data?.line_items ?? {}).every(
|
||||
(items) => !items.length
|
||||
)
|
||||
},
|
||||
set: (x) => x,
|
||||
})
|
||||
|
||||
return cart
|
||||
return response
|
||||
}
|
||||
|
||||
useCart.extend = extendHook
|
||||
|
6
lib/bigcommerce/schema.d.ts
vendored
6
lib/bigcommerce/schema.d.ts
vendored
@ -1886,6 +1886,12 @@ export type GetAllProductsQuery = { __typename?: 'Query' } & {
|
||||
}
|
||||
}
|
||||
|
||||
export type GetCustomerIdQueryVariables = Exact<{ [key: string]: never }>
|
||||
|
||||
export type GetCustomerIdQuery = { __typename?: 'Query' } & {
|
||||
customer?: Maybe<{ __typename?: 'Customer' } & Pick<Customer, 'entityId'>>
|
||||
}
|
||||
|
||||
export type GetProductQueryVariables = Exact<{
|
||||
hasLocale?: Maybe<Scalars['Boolean']>
|
||||
locale?: Maybe<Scalars['String']>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { useCallback } from 'react'
|
||||
import { HookFetcher } from '@lib/commerce/utils/types'
|
||||
import useAction from '@lib/commerce/utils/use-action'
|
||||
import { CommerceError } from '@lib/commerce/utils/errors'
|
||||
import useWishlistAddItem from '@lib/commerce/wishlist/use-add-item'
|
||||
import type { ItemBody, AddItemBody } from '../api/wishlist'
|
||||
import useCustomer from '../use-customer'
|
||||
import useWishlist, { Wishlist } from './use-wishlist'
|
||||
|
||||
const defaultOpts = {
|
||||
@ -13,24 +15,33 @@ export type AddItemInput = ItemBody
|
||||
|
||||
export const fetcher: HookFetcher<Wishlist, AddItemBody> = (
|
||||
options,
|
||||
{ wishlistId, item },
|
||||
{ item },
|
||||
fetch
|
||||
) => {
|
||||
// TODO: add validations before doing the fetch
|
||||
return fetch({
|
||||
...defaultOpts,
|
||||
...options,
|
||||
body: { wishlistId, item },
|
||||
body: { item },
|
||||
})
|
||||
}
|
||||
|
||||
export function extendHook(customFetcher: typeof fetcher) {
|
||||
const useAddItem = (wishlistId: string) => {
|
||||
const { mutate } = useWishlist(wishlistId)
|
||||
const fn = useAction<Wishlist, AddItemBody>(defaultOpts, customFetcher)
|
||||
const useAddItem = () => {
|
||||
const { data: customer } = useCustomer()
|
||||
const { mutate } = useWishlist()
|
||||
const fn = useWishlistAddItem(defaultOpts, customFetcher)
|
||||
|
||||
return useCallback(
|
||||
async function addItem(input: AddItemInput) {
|
||||
const data = await fn({ wishlistId, item: input })
|
||||
if (!customer) {
|
||||
// A signed customer is required in order to have a wishlist
|
||||
throw new CommerceError({
|
||||
message: 'Signed customer not found',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fn({ item: input })
|
||||
await mutate(data, false)
|
||||
return data
|
||||
},
|
||||
|
@ -1,36 +1,48 @@
|
||||
import { HookFetcher } from '@lib/commerce/utils/types'
|
||||
import useData from '@lib/commerce/utils/use-data'
|
||||
import { SwrOptions } from '@lib/commerce/utils/use-data'
|
||||
import useCommerceWishlist from '@lib/commerce/wishlist/use-wishlist'
|
||||
import type { Wishlist } from '../api/wishlist'
|
||||
import useCustomer from '../use-customer'
|
||||
|
||||
const defaultOpts = {
|
||||
url: '/api/bigcommerce/wishlists',
|
||||
url: '/api/bigcommerce/wishlist',
|
||||
method: 'GET',
|
||||
}
|
||||
|
||||
export type { Wishlist }
|
||||
|
||||
export type WishlistInput = {
|
||||
wishlistId: string | undefined
|
||||
}
|
||||
|
||||
export const fetcher: HookFetcher<Wishlist | null, WishlistInput> = (
|
||||
export const fetcher: HookFetcher<Wishlist | null, { customerId?: number }> = (
|
||||
options,
|
||||
{ wishlistId },
|
||||
{ customerId },
|
||||
fetch
|
||||
) => {
|
||||
return fetch({
|
||||
...defaultOpts,
|
||||
...options,
|
||||
body: { wishlistId },
|
||||
})
|
||||
return customerId ? fetch({ ...defaultOpts, ...options }) : null
|
||||
}
|
||||
|
||||
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)
|
||||
export function extendHook(
|
||||
customFetcher: typeof fetcher,
|
||||
swrOptions?: SwrOptions<Wishlist | null, { customerId?: number }>
|
||||
) {
|
||||
const useWishlists = () => {
|
||||
const { data: customer } = useCustomer()
|
||||
const response = useCommerceWishlist(
|
||||
defaultOpts,
|
||||
[['customerId', customer?.entityId]],
|
||||
customFetcher,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
...swrOptions,
|
||||
}
|
||||
)
|
||||
|
||||
// Uses a getter to only calculate the prop when required
|
||||
// response.data is also a getter and it's better to not trigger it early
|
||||
Object.defineProperty(response, 'isEmpty', {
|
||||
get() {
|
||||
return (response.data?.items?.length || 0) > 0
|
||||
},
|
||||
set: (x) => x,
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
|
||||
import useData, { SwrOptions } from '../utils/use-data'
|
||||
import { useCommerce } from '..'
|
||||
|
||||
export type CartResponse<C> = responseInterface<C, Error> & {
|
||||
export type CartResponse<Result> = responseInterface<Result, Error> & {
|
||||
isEmpty: boolean
|
||||
}
|
||||
|
||||
|
5
lib/commerce/wishlist/use-add-item.tsx
Normal file
5
lib/commerce/wishlist/use-add-item.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import useAction from '../utils/use-action'
|
||||
|
||||
const useAddItem = useAction
|
||||
|
||||
export default useAddItem
|
17
lib/commerce/wishlist/use-wishlist.tsx
Normal file
17
lib/commerce/wishlist/use-wishlist.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import type { responseInterface } from 'swr'
|
||||
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
|
||||
import useData, { SwrOptions } from '../utils/use-data'
|
||||
|
||||
export type WishlistResponse<Result> = responseInterface<Result, Error> & {
|
||||
isEmpty: boolean
|
||||
}
|
||||
|
||||
export default function useWishlist<Result, Input = null>(
|
||||
options: HookFetcherOptions,
|
||||
input: HookInput,
|
||||
fetcherFn: HookFetcher<Result, Input>,
|
||||
swrOptions?: SwrOptions<Result, Input>
|
||||
) {
|
||||
const response = useData(options, input, fetcherFn, swrOptions)
|
||||
return Object.assign(response, { isEmpty: true }) as WishlistResponse<Result>
|
||||
}
|
3
pages/api/bigcommerce/wishlist.ts
Normal file
3
pages/api/bigcommerce/wishlist.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import wishlistApi from '@lib/bigcommerce/api/wishlist'
|
||||
|
||||
export default wishlistApi()
|
Loading…
x
Reference in New Issue
Block a user