feat: add wishlist (WIP) aquilacms don't have a wishlist, it's just to not fail to compile

This commit is contained in:
Gérard Le Cloerec 2021-04-13 14:25:06 +02:00
parent 08edd1ec80
commit 237bd3c3bc
13 changed files with 437 additions and 12 deletions

View File

@ -2,10 +2,10 @@ import React, { FC, useState } from 'react'
import cn from 'classnames'
import { useUI } from '@components/ui'
import { Heart } from '@components/icons'
// import useAddItem from '@framework/wishlist/use-add-item'
import useAddItem from '@framework/wishlist/use-add-item'
import useCustomer from '@framework/customer/use-customer'
// import useWishlist from '@framework/wishlist/use-wishlist'
// import useRemoveItem from '@framework/wishlist/use-remove-item'
import useWishlist from '@framework/wishlist/use-wishlist'
import useRemoveItem from '@framework/wishlist/use-remove-item'
import type { Product, ProductVariant } from '@commerce/types'
type Props = {
@ -19,11 +19,8 @@ const WishlistButton: FC<Props> = ({
className,
...props
}) => {
// @ts-ignore
const { data } = useWishlist()
// @ts-ignore
const addItem = useAddItem()
// @ts-ignore
const removeItem = useRemoveItem()
const { data: customer } = useCustomer()
const { openModal, setModalView } = useUI()

View File

@ -10,7 +10,7 @@ import { useUI } from '@components/ui/context'
import type { Product } from '@commerce/types'
import usePrice from '@framework/product/use-price'
import useAddItem from '@framework/cart/use-add-item'
// import useRemoveItem from '@framework/wishlist/use-remove-item'
import useRemoveItem from '@framework/wishlist/use-remove-item'
interface Props {
product: Product

View File

@ -0,0 +1,56 @@
import type { WishlistHandlers } from '..'
import getCustomerId from '../../../customer/get-customer-id'
import getCustomerWishlist from '../../../customer/get-customer-wishlist'
import { parseWishlistItem } from '../../utils/parse-item'
// Returns the wishlist of the signed customer
const addItem: WishlistHandlers['addItem'] = async ({
res,
body: { customerToken, item },
config,
}) => {
if (!item) {
return res.status(400).json({
data: null,
errors: [{ message: 'Missing item' }],
})
}
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(
wishlist
? {
items: [parseWishlistItem(item)],
}
: {
name: 'Wishlist',
customer_id: customerId,
items: [parseWishlistItem(item)],
is_public: false,
}
),
}
const { data } = wishlist
? await config.storeApiFetch(`/v3/wishlists/${wishlist.id}/items`, options)
: await config.storeApiFetch('/v3/wishlists', options)
res.status(200).json({ data })
}
export default addItem

View File

@ -0,0 +1,37 @@
import getCustomerId from '../../../customer/get-customer-id'
import getCustomerWishlist from '../../../customer/get-customer-wishlist'
import type { Wishlist, WishlistHandlers } from '..'
// Return wishlist info
const getWishlist: WishlistHandlers['getWishlist'] = async ({
res,
body: { customerToken, includeProducts },
config,
}) => {
let result: { data?: Wishlist } = {}
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 },
includeProducts,
config,
})
result = { data: wishlist }
}
res.status(200).json({ data: result.data ?? null })
}
export default getWishlist

View File

@ -0,0 +1,39 @@
import getCustomerId from '../../../customer/get-customer-id'
import getCustomerWishlist, {
Wishlist,
} from '../../../customer/get-customer-wishlist'
import type { WishlistHandlers } from '..'
// Return current wishlist info
const removeItem: WishlistHandlers['removeItem'] = async ({
res,
body: { customerToken, itemId },
config,
}) => {
const customerId =
customerToken && (await getCustomerId({ customerToken, config }))
const { wishlist } =
(customerId &&
(await getCustomerWishlist({
variables: { customerId },
config,
}))) ||
{}
if (!wishlist || !itemId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Invalid request' }],
})
}
const result = await config.storeApiFetch<{ data: Wishlist } | null>(
`/v3/wishlists/${wishlist.id}/items/${itemId}`,
{ method: 'DELETE' }
)
const data = result?.data ?? null
res.status(200).json({ data })
}
export default removeItem

View File

@ -0,0 +1,104 @@
import isAllowedMethod from '../utils/is-allowed-method'
import createApiHandler, {
AquilacmsApiHandler,
AquilacmsHandler,
} from '../utils/create-api-handler'
import { AquilacmsApiError } from '../utils/errors'
import type {
Wishlist,
WishlistItem,
} from '../../customer/get-customer-wishlist'
import getWishlist from './handlers/get-wishlist'
import addItem from './handlers/add-item'
import removeItem from './handlers/remove-item'
import type { Product, ProductVariant, Customer } from '@commerce/types'
export type { Wishlist, WishlistItem }
export type ItemBody = {
productId: Product['id']
variantId: ProductVariant['id']
}
export type AddItemBody = { item: ItemBody }
export type RemoveItemBody = { itemId: Product['id'] }
export type WishlistBody = {
customer_id: Customer['entityId']
is_public: number
name: string
items: any[]
}
export type AddWishlistBody = { wishlist: WishlistBody }
export type WishlistHandlers = {
getWishlist: AquilacmsHandler<
Wishlist,
{ customerToken?: string; includeProducts?: boolean }
>
addItem: AquilacmsHandler<
Wishlist,
{ customerToken?: string } & Partial<AddItemBody>
>
removeItem: AquilacmsHandler<
Wishlist,
{ customerToken?: string } & Partial<RemoveItemBody>
>
}
const METHODS = ['GET', 'POST', 'DELETE']
// TODO: a complete implementation should have schema validation for `req.body`
const wishlistApi: AquilacmsApiHandler<Wishlist, WishlistHandlers> = async (
req,
res,
config,
handlers
) => {
if (!isAllowedMethod(req, res, METHODS)) return
const { cookies } = req
const customerToken = cookies[config.customerCookie]
try {
// Return current wishlist info
if (req.method === 'GET') {
const body = {
customerToken,
includeProducts: req.query.products === '1',
}
return await handlers['getWishlist']({ req, res, config, body })
}
// Add an item to the wishlist
if (req.method === 'POST') {
const body = { ...req.body, customerToken }
return await handlers['addItem']({ req, res, config, body })
}
// Remove an item from the wishlist
if (req.method === 'DELETE') {
const body = { ...req.body, customerToken }
return await handlers['removeItem']({ req, res, config, body })
}
} catch (error) {
console.error(error)
const message =
error instanceof AquilacmsApiError
? '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,
removeItem,
}
export default createApiHandler(wishlistApi, handlers, {})

View File

@ -0,0 +1,50 @@
import { AquilacmsConfig, getConfig } from '../api'
export type Wishlist = Omit<any, 'items'> & {
items?: WishlistItem[]
}
export type WishlistItem = NonNullable<any>[0] & {
product?: any
}
export type GetCustomerWishlistResult<
T extends { wishlist?: any } = { wishlist?: Wishlist }
> = T
export type GetCustomerWishlistVariables = {
customerId: number
}
async function getCustomerWishlist(opts: {
variables: GetCustomerWishlistVariables
config?: AquilacmsConfig
includeProducts?: boolean
}): Promise<GetCustomerWishlistResult>
async function getCustomerWishlist<
T extends { wishlist?: any },
V = any
>(opts: {
url: string
variables: V
config?: AquilacmsConfig
includeProducts?: boolean
}): Promise<GetCustomerWishlistResult<T>>
async function getCustomerWishlist({
config,
variables,
includeProducts,
}: {
url?: string
variables: GetCustomerWishlistVariables
config?: AquilacmsConfig
includeProducts?: boolean
}): Promise<GetCustomerWishlistResult> {
config = getConfig(config)
return { wishlist: [] }
}
export default getCustomerWishlist

View File

@ -0,0 +1,3 @@
export { default as useAddItem } from './use-add-item'
export { default as useWishlist } from './use-wishlist'
export { default as useRemoveItem } from './use-remove-item'

View File

@ -0,0 +1,37 @@
import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
import useAddItem, { UseAddItem } from '@commerce/wishlist/use-add-item'
import type { ItemBody, AddItemBody } from '../api/wishlist'
import useCustomer from '../customer/use-customer'
import useWishlist from './use-wishlist'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<any, {}, ItemBody, AddItemBody> = {
fetchOptions: {
url: '/api/bigcommerce/wishlist',
method: 'POST',
},
useHook: ({ fetch }) => () => {
const { data: customer } = useCustomer()
const { revalidate } = useWishlist()
return useCallback(
async function addItem(item) {
if (!customer) {
// A signed customer is required in order to have a wishlist
throw new CommerceError({
message: 'Signed customer not found',
})
}
// TODO: add validations before doing the fetch
const data = await fetch({ input: { item } })
await revalidate()
return data
},
[fetch, revalidate, customer]
)
},
}

View File

@ -0,0 +1,44 @@
import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
import useRemoveItem, {
RemoveItemInput,
UseRemoveItem,
} from '@commerce/wishlist/use-remove-item'
import type { RemoveItemBody, Wishlist } from '../api/wishlist'
import useCustomer from '../customer/use-customer'
import useWishlist, { UseWishlistInput } from './use-wishlist'
export default useRemoveItem as UseRemoveItem<typeof handler>
export const handler: MutationHook<
Wishlist | null,
{ wishlist?: UseWishlistInput },
RemoveItemInput,
RemoveItemBody
> = {
fetchOptions: {
url: '/api/bigcommerce/wishlist',
method: 'DELETE',
},
useHook: ({ fetch }) => ({ wishlist } = {}) => {
const { data: customer } = useCustomer()
const { revalidate } = useWishlist(wishlist)
return useCallback(
async function removeItem(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 fetch({ input: { itemId: String(input.id) } })
await revalidate()
return data
},
[fetch, revalidate, customer]
)
},
}

View File

@ -0,0 +1,60 @@
import { useMemo } from 'react'
import { SWRHook } from '@commerce/utils/types'
import useWishlist, { UseWishlist } from '@commerce/wishlist/use-wishlist'
import type { Wishlist } from '../api/wishlist'
import useCustomer from '../customer/use-customer'
export type UseWishlistInput = { includeProducts?: boolean }
export default useWishlist as UseWishlist<typeof handler>
export const handler: SWRHook<
Wishlist | null,
UseWishlistInput,
{ customerId?: number } & UseWishlistInput,
{ isEmpty?: boolean }
> = {
fetchOptions: {
url: '/api/bigcommerce/wishlist',
method: 'GET',
},
async fetcher({ input: { customerId, includeProducts }, options, fetch }) {
if (!customerId) return null
// Use a dummy base as we only care about the relative path
const url = new URL(options.url!, 'http://a')
if (includeProducts) url.searchParams.set('products', '1')
return fetch({
url: url.pathname + url.search,
method: options.method,
})
},
useHook: ({ useData }) => (input) => {
const { data: customer } = useCustomer()
const response = useData({
input: [
['customerId', customer?.entityId],
['includeProducts', input?.includeProducts],
],
swrOptions: {
revalidateOnFocus: false,
...input?.swrOptions,
},
})
return useMemo(
() =>
Object.create(response, {
isEmpty: {
get() {
return (response.data?.items?.length || 0) <= 0
},
enumerable: true,
},
}),
[response]
)
},
}

View File

@ -1,4 +1,3 @@
export {}
// import wishlistApi from '@framework/api/wishlist'
import wishlistApi from '@framework/api/wishlist'
// export default wishlistApi()
export default wishlistApi()

View File

@ -6,7 +6,7 @@ import { defaultPageProps } from '@lib/defaults'
import { getConfig } from '@framework/api'
import { useCustomer } from '@framework/customer'
import { WishlistCard } from '@components/wishlist'
// import useWishlist from '@framework/wishlist/use-wishlist'
import useWishlist from '@framework/wishlist/use-wishlist'
import getAllPages from '@framework/common/get-all-pages'
export async function getStaticProps({
@ -56,7 +56,6 @@ export default function Wishlist() {
data &&
// @ts-ignore Shopify - Fix this types
data.items?.map((item) => (
// @ts-ignore
<WishlistCard key={item.id} product={item.product! as any} />
))
)}