mirror of
https://github.com/vercel/commerce.git
synced 2025-06-19 05:31:22 +00:00
feat: add wishlist (WIP) aquilacms don't have a wishlist, it's just to not fail to compile
This commit is contained in:
parent
08edd1ec80
commit
237bd3c3bc
@ -2,10 +2,10 @@ import React, { FC, useState } from 'react'
|
|||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { useUI } from '@components/ui'
|
import { useUI } from '@components/ui'
|
||||||
import { Heart } from '@components/icons'
|
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 useCustomer from '@framework/customer/use-customer'
|
||||||
// import useWishlist from '@framework/wishlist/use-wishlist'
|
import useWishlist from '@framework/wishlist/use-wishlist'
|
||||||
// import useRemoveItem from '@framework/wishlist/use-remove-item'
|
import useRemoveItem from '@framework/wishlist/use-remove-item'
|
||||||
import type { Product, ProductVariant } from '@commerce/types'
|
import type { Product, ProductVariant } from '@commerce/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -19,11 +19,8 @@ const WishlistButton: FC<Props> = ({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
// @ts-ignore
|
|
||||||
const { data } = useWishlist()
|
const { data } = useWishlist()
|
||||||
// @ts-ignore
|
|
||||||
const addItem = useAddItem()
|
const addItem = useAddItem()
|
||||||
// @ts-ignore
|
|
||||||
const removeItem = useRemoveItem()
|
const removeItem = useRemoveItem()
|
||||||
const { data: customer } = useCustomer()
|
const { data: customer } = useCustomer()
|
||||||
const { openModal, setModalView } = useUI()
|
const { openModal, setModalView } = useUI()
|
||||||
|
@ -10,7 +10,7 @@ import { useUI } from '@components/ui/context'
|
|||||||
import type { Product } from '@commerce/types'
|
import type { Product } from '@commerce/types'
|
||||||
import usePrice from '@framework/product/use-price'
|
import usePrice from '@framework/product/use-price'
|
||||||
import useAddItem from '@framework/cart/use-add-item'
|
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 {
|
interface Props {
|
||||||
product: Product
|
product: Product
|
||||||
|
56
framework/aquilacms/api/wishlist/handlers/add-item.ts
Normal file
56
framework/aquilacms/api/wishlist/handlers/add-item.ts
Normal 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
|
37
framework/aquilacms/api/wishlist/handlers/get-wishlist.ts
Normal file
37
framework/aquilacms/api/wishlist/handlers/get-wishlist.ts
Normal 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
|
39
framework/aquilacms/api/wishlist/handlers/remove-item.ts
Normal file
39
framework/aquilacms/api/wishlist/handlers/remove-item.ts
Normal 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
|
104
framework/aquilacms/api/wishlist/index.ts
Normal file
104
framework/aquilacms/api/wishlist/index.ts
Normal 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, {})
|
50
framework/aquilacms/customer/get-customer-wishlist.ts
Normal file
50
framework/aquilacms/customer/get-customer-wishlist.ts
Normal 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
|
3
framework/aquilacms/wishlist/index.ts
Normal file
3
framework/aquilacms/wishlist/index.ts
Normal 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'
|
37
framework/aquilacms/wishlist/use-add-item.tsx
Normal file
37
framework/aquilacms/wishlist/use-add-item.tsx
Normal 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]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
44
framework/aquilacms/wishlist/use-remove-item.tsx
Normal file
44
framework/aquilacms/wishlist/use-remove-item.tsx
Normal 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]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
60
framework/aquilacms/wishlist/use-wishlist.tsx
Normal file
60
framework/aquilacms/wishlist/use-wishlist.tsx
Normal 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]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
export {}
|
import wishlistApi from '@framework/api/wishlist'
|
||||||
// import wishlistApi from '@framework/api/wishlist'
|
|
||||||
|
|
||||||
// export default wishlistApi()
|
export default wishlistApi()
|
||||||
|
@ -6,7 +6,7 @@ import { defaultPageProps } from '@lib/defaults'
|
|||||||
import { getConfig } from '@framework/api'
|
import { getConfig } from '@framework/api'
|
||||||
import { useCustomer } from '@framework/customer'
|
import { useCustomer } from '@framework/customer'
|
||||||
import { WishlistCard } from '@components/wishlist'
|
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'
|
import getAllPages from '@framework/common/get-all-pages'
|
||||||
|
|
||||||
export async function getStaticProps({
|
export async function getStaticProps({
|
||||||
@ -56,7 +56,6 @@ export default function Wishlist() {
|
|||||||
data &&
|
data &&
|
||||||
// @ts-ignore Shopify - Fix this types
|
// @ts-ignore Shopify - Fix this types
|
||||||
data.items?.map((item) => (
|
data.items?.map((item) => (
|
||||||
// @ts-ignore
|
|
||||||
<WishlistCard key={item.id} product={item.product! as any} />
|
<WishlistCard key={item.id} product={item.product! as any} />
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user