Fix auth & wishlist (#918)

* Fix auth & wishlist

* Revert files

* Update signup.ts

* Update signup.ts

* Requested changes

* Revert fetch options
This commit is contained in:
Catalin Pinte 2023-01-30 17:50:25 +02:00 committed by GitHub
parent 252355717d
commit d1d9e8c434
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 203 additions and 146 deletions

View File

@ -34,7 +34,9 @@ const getLoggedInCustomer: CustomerEndpoint['handlers']['getLoggedInCustomer'] =
getLoggedInCustomerQuery,
undefined,
{
'Set-Cookie': `${config.customerCookie}=${token}`,
headers: {
cookie: `${config.customerCookie}=${token}`,
},
}
)
const { customer } = data

View File

@ -11,12 +11,12 @@ const login: LoginEndpoint['handlers']['login'] = async ({
commerce,
}) => {
try {
const res = new Response()
await commerce.login({ variables: { email, password }, config, res })
return {
status: res.status,
headers: res.headers,
}
const response = await commerce.login({
variables: { email, password },
config,
})
return response
} catch (error) {
// Check if the email and password didn't match an existing account
if (error instanceof FetcherError) {
@ -24,7 +24,7 @@ const login: LoginEndpoint['handlers']['login'] = async ({
invalidCredentials.test(error.message)
? 'Cannot find an account that matches the provided credentials'
: error.message,
{ status: error.status || 401 }
{ status: 401 }
)
} else {
throw error

View File

@ -1,6 +1,5 @@
import type { SignupEndpoint } from '.'
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
import { BigcommerceApiError } from '../../utils/errors'
const signup: SignupEndpoint['handlers']['signup'] = async ({
@ -22,28 +21,27 @@ const signup: SignupEndpoint['handlers']['signup'] = async ({
},
]),
})
// Login the customer right after creating it
const response = await commerce.login({
variables: { email, password },
config,
})
return response
} catch (error) {
if (error instanceof BigcommerceApiError && error.status === 422) {
const hasEmailError = '0.email' in error.data?.errors
// If there's an error with the email, it most likely means it's duplicated
if (hasEmailError) {
throw new CommerceAPIError('Email already in use', {
// Display all validation errors from BigCommerce in a single error message
if (error instanceof BigcommerceApiError && error.status >= 400) {
const message = Object.values(error.data.errors).join('<br />')
if (message) {
throw new CommerceAPIError(message, {
status: 400,
code: 'duplicated_email',
code: 'invalid_request',
})
}
} else {
throw error
}
}
const res = new Response()
// Login the customer right after creating it
await commerce.login({ variables: { email, password }, res, config })
return {
headers: res.headers,
throw error
}
}

View File

@ -1,6 +1,7 @@
import { parseWishlistItem } from '../../utils/parse-item'
import getCustomerId from '../../utils/get-customer-id'
import type { WishlistEndpoint } from '.'
import { normalizeWishlist } from '../../../lib/normalize'
const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
body: { customerToken, item },
@ -31,7 +32,7 @@ const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
}),
})
return {
data,
data: normalizeWishlist(data),
}
}
@ -47,7 +48,9 @@ const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
)
// Returns Wishlist
return { data }
return {
data: normalizeWishlist(data),
}
}
export default addItem

View File

@ -1,5 +1,4 @@
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
import type { Wishlist } from '@vercel/commerce/types/wishlist'
import type { WishlistEndpoint } from '.'
import getCustomerId from '../../utils/get-customer-id'
@ -9,8 +8,6 @@ const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({
config,
commerce,
}) => {
let result: { data?: Wishlist } = {}
if (customerToken) {
const customerId =
customerToken && (await getCustomerId({ customerToken, config }))
@ -25,10 +22,10 @@ const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({
config,
})
result = { data: wishlist }
return { data: wishlist }
}
return { data: result.data ?? null }
return { data: null }
}
export default getWishlist

View File

@ -1,7 +1,9 @@
import type { Wishlist } from '@vercel/commerce/types/wishlist'
import getCustomerId from '../../utils/get-customer-id'
import type { WishlistEndpoint } from '.'
import type { BCWishlist } from '../../utils/types'
import getCustomerId from '../../utils/get-customer-id'
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
import { normalizeWishlist } from '../../../lib/normalize'
// Return wishlist info
const removeItem: WishlistEndpoint['handlers']['removeItem'] = async ({
@ -11,6 +13,7 @@ const removeItem: WishlistEndpoint['handlers']['removeItem'] = async ({
}) => {
const customerId =
customerToken && (await getCustomerId({ customerToken, config }))
const { wishlist } =
(customerId &&
(await commerce.getCustomerWishlist({
@ -23,13 +26,12 @@ const removeItem: WishlistEndpoint['handlers']['removeItem'] = async ({
throw new CommerceAPIError('Wishlist not found', { status: 400 })
}
const result = await config.storeApiFetch<{ data: Wishlist } | null>(
const result = await config.storeApiFetch<{ data: BCWishlist } | null>(
`/v3/wishlists/${wishlist.id}/items/${itemId}`,
{ method: 'DELETE' }
)
const data = result?.data ?? null
return { data }
return { data: result?.data ? normalizeWishlist(result.data) : null }
}
export default removeItem

View File

@ -2,13 +2,11 @@ import type {
OperationContext,
OperationOptions,
} from '@vercel/commerce/api/operations'
import type {
GetCustomerWishlistOperation,
Wishlist,
} from '@vercel/commerce/types/wishlist'
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import type { GetCustomerWishlistOperation } from '@vercel/commerce/types/wishlist'
import type { RecursivePartial, BCWishlist } from '../utils/types'
import { BigcommerceConfig, Provider } from '..'
import getAllProducts, { ProductEdge } from './get-all-products'
import { ProductEdge } from './get-all-products'
import { normalizeWishlist } from '../../lib/normalize'
export default function getCustomerWishlistOperation({
commerce,
@ -41,18 +39,22 @@ export default function getCustomerWishlistOperation({
}): Promise<T['data']> {
config = commerce.getConfig(config)
const { data = [] } = await config.storeApiFetch<
RecursivePartial<{ data: Wishlist[] }>
>(`/v3/wishlists?customer_id=${variables.customerId}`)
const { data = [] } = await config.storeApiFetch<{ data: BCWishlist[] }>(
`/v3/wishlists?customer_id=${variables.customerId}`
)
const wishlist = data[0]
if (includeProducts && wishlist?.items?.length) {
const ids = wishlist.items
?.map((item) => (item?.productId ? String(item?.productId) : null))
.filter((id): id is string => !!id)
const ids = []
if (ids?.length) {
for (const wishlistItem of wishlist.items) {
if (wishlistItem.product_id) {
ids.push(String(wishlistItem.product_id))
}
}
if (ids.length) {
const graphqlData = await commerce.getAllProducts({
variables: { first: 50, ids },
config,
@ -66,7 +68,7 @@ export default function getCustomerWishlistOperation({
}, {})
// Populate the wishlist items with the graphql products
wishlist.items.forEach((item) => {
const product = item && productsById[Number(item.productId)]
const product = item && productsById[Number(item.product_id)]
if (item && product) {
// @ts-ignore Fix this type when the wishlist type is properly defined
item.product = product
@ -75,7 +77,7 @@ export default function getCustomerWishlistOperation({
}
}
return { wishlist: wishlist as RecursiveRequired<typeof wishlist> }
return { wishlist: wishlist && normalizeWishlist(wishlist) }
}
return getCustomerWishlist

View File

@ -5,7 +5,6 @@ import type {
import type { LoginOperation } from '@vercel/commerce/types/login'
import type { LoginMutation } from '../../../schema'
import type { RecursivePartial } from '../utils/types'
import concatHeader from '../utils/concat-cookie'
import type { BigcommerceConfig, Provider } from '..'
export const loginMutation = /* GraphQL */ `
@ -22,26 +21,23 @@ export default function loginOperation({
async function login<T extends LoginOperation>(opts: {
variables: T['variables']
config?: BigcommerceConfig
res: Response
}): Promise<T['data']>
async function login<T extends LoginOperation>(
opts: {
variables: T['variables']
config?: BigcommerceConfig
res: Response
} & OperationOptions
): Promise<T['data']>
async function login<T extends LoginOperation>({
query = loginMutation,
variables,
res: response,
config,
}: {
query?: string
variables: T['variables']
res: Response
config?: BigcommerceConfig
}): Promise<T['data']> {
config = commerce.getConfig(config)
@ -50,6 +46,9 @@ export default function loginOperation({
query,
{ variables }
)
const headers = new Headers()
// Bigcommerce returns a Set-Cookie header with the auth cookie
let cookie = res.headers.get('Set-Cookie')
@ -63,19 +62,13 @@ export default function loginOperation({
cookie = cookie.replace(/; SameSite=none/gi, '; SameSite=lax')
}
const prevCookie = response.headers.get('Set-Cookie')
const newCookie = concatHeader(prevCookie, cookie)
if (newCookie) {
res.headers.set(
'Set-Cookie',
String(Array.isArray(newCookie) ? newCookie.join(',') : newCookie)
)
}
headers.set('Set-Cookie', cookie)
}
return {
result: data.login?.result,
headers,
status: res.status,
}
}

View File

@ -1,5 +1,5 @@
import { FetcherError } from '@vercel/commerce/utils/errors'
import type { GraphQLFetcher } from '@vercel/commerce/api'
import type { FetchOptions, GraphQLFetcher } from '@vercel/commerce/api'
import type { BigcommerceConfig } from '../index'
const fetchGraphqlApi: (getConfig: () => BigcommerceConfig) => GraphQLFetcher =
@ -7,19 +7,20 @@ const fetchGraphqlApi: (getConfig: () => BigcommerceConfig) => GraphQLFetcher =
async (
query: string,
{ variables, preview } = {},
options: { headers?: HeadersInit } = {}
options?: FetchOptions
): Promise<any> => {
// log.warn(query)
const config = getConfig()
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
method: 'POST',
method: options?.method || 'POST',
headers: {
Authorization: `Bearer ${config.apiToken}`,
...options.headers,
...options?.headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
...options?.body,
query,
variables,
}),

View File

@ -20,7 +20,9 @@ async function getCustomerId({
getCustomerIdQuery,
undefined,
{
'Set-Cookie': `${config.customerCookie}=${customerToken}`,
headers: {
cookie: `${config.customerCookie}=${customerToken}`,
},
}
)

View File

@ -5,3 +5,15 @@ export type RecursivePartial<T> = {
export type RecursiveRequired<T> = {
[P in keyof T]-?: RecursiveRequired<T[P]>
}
export interface BCWishlist {
id: number
items: {
id: number
customer_id: number
is_public: boolean
product_id: number
variant_id: number
}[]
token: string
}

View File

@ -5,8 +5,10 @@ import type { Category, Brand } from '@vercel/commerce/types/site'
import type { BigcommerceCart, BCCategory, BCBrand } from '../types'
import type { ProductNode } from '../api/operations/get-all-products'
import type { definitions } from '../api/definitions/store-content'
import type { BCWishlist } from '../api/utils/types'
import getSlug from './get-slug'
import { Wishlist } from '@vercel/commerce/types/wishlist'
function normalizeProductOption(productOption: any) {
const {
@ -137,3 +139,16 @@ export function normalizeBrand(brand: BCBrand): Brand {
path: `/${slug}`,
}
}
export function normalizeWishlist(wishlist: BCWishlist): Wishlist {
return {
id: String(wishlist.id),
token: wishlist.token,
items: wishlist.items.map((item: any) => ({
id: String(item.id),
productId: String(item.product_id),
variantId: String(item.variant_id),
product: item.product,
})),
}
}

View File

@ -73,6 +73,12 @@ export type EndpointHandlers<
>
}
export type FetchOptions<Body = any> = {
method?: string
body?: Body
headers?: HeadersInit
}
export type APIProvider = {
config: CommerceAPIConfig
operations: APIOperations<any>
@ -165,7 +171,7 @@ export interface CommerceAPIConfig {
fetch<Data = any, Variables = any>(
query: string,
queryData?: CommerceAPIFetchOptions<Variables>,
headers?: HeadersInit
options?: FetchOptions
): Promise<GraphQLFetcherResult<Data>>
}

View File

@ -5,7 +5,7 @@ export const wishlistSchemaItem = z.object({
id: z.string(),
productId: z.string(),
variantId: z.string(),
product: productSchema,
product: productSchema.optional(),
})
export const wishlistSchema = z.object({
@ -15,7 +15,7 @@ export const wishlistSchema = z.object({
})
export const getWishlistBodySchema = z.object({
customerAccessToken: z.string(),
customerToken: z.string().optional(),
includeProducts: z.boolean(),
})
@ -25,17 +25,17 @@ export const wishlistItemBodySchema = z.object({
})
export const addItemBodySchema = z.object({
cartId: z.string().optional(),
customerToken: z.string(),
item: wishlistItemBodySchema,
})
export const updateItemBodySchema = z.object({
cartId: z.string(),
customerToken: z.string(),
itemId: z.string(),
item: wishlistItemBodySchema,
})
export const removeItemBodySchema = z.object({
cartId: z.string(),
customerToken: z.string(),
itemId: z.string(),
})

View File

@ -26,6 +26,6 @@ export type LoginSchema = {
}
export type LoginOperation = {
data: { result?: string }
data: { result?: string; status?: number; headers?: Headers }
variables: unknown
}

View File

@ -1,5 +1,5 @@
import { FetcherError } from '@vercel/commerce/utils/errors'
import type { GraphQLFetcher } from '@vercel/commerce/api'
import type { FetchOptions, GraphQLFetcher } from '@vercel/commerce/api'
import type { KiboCommerceConfig } from '../index'
import { APIAuthenticationHelper } from './api-auth-helper'
@ -8,18 +8,23 @@ const fetchGraphqlApi: (
getConfig: () => KiboCommerceConfig
) => GraphQLFetcher =
(getConfig) =>
async (query: string, { variables, preview } = {}, headers?: HeadersInit) => {
async (
query: string,
{ variables, preview } = {},
options?: FetchOptions
) => {
const config = getConfig()
const authHelper = new APIAuthenticationHelper(config)
const apiToken = await authHelper.getAccessToken()
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
method: 'POST',
method: options?.method || 'POST',
headers: {
...headers,
...options?.headers,
Authorization: `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
...options?.body,
query,
variables,
}),

View File

@ -13,7 +13,9 @@ async function getCustomerId({
: null
const accessToken = token ? JSON.parse(token).accessToken : null
const { data } = await config.fetch(getCustomerAccountQuery, undefined, {
'x-vol-user-claims': accessToken,
headers: {
'x-vol-user-claims': accessToken,
},
})
return data?.customerAccount?.id

View File

@ -30,7 +30,9 @@ export default function getAllPagesOperation({
{ variables },
{
...(locale && {
'Accept-Language': locale,
headers: {
'Accept-Language': locale,
},
}),
}
)

View File

@ -38,9 +38,11 @@ export default function getAllProductsOperation({
query,
{ variables },
{
...(locale && {
'Accept-Language': locale,
}),
headers: {
...(locale && {
'Accept-Language': locale,
}),
},
}
)

View File

@ -28,7 +28,9 @@ export default function getPageOperation({
{ variables },
{
...(locale && {
'Accept-Language': locale,
headers: {
'Accept-Language': locale,
},
}),
}
)

View File

@ -32,7 +32,9 @@ export default function getProductOperation({
{ variables },
{
...(locale && {
'Accept-Language': locale,
headers: {
'Accept-Language': locale,
},
}),
}
)

View File

@ -1,4 +1,4 @@
import type { GraphQLFetcher } from '@vercel/commerce/api'
import type { FetchOptions, GraphQLFetcher } from '@vercel/commerce/api'
import { API_URL } from '../../const'
import { getError } from '../../utils/handle-fetch-response'
@ -7,20 +7,21 @@ import { getToken } from '../../utils/index'
const fetchGraphqlApi: GraphQLFetcher = async (
query: string,
{ variables } = {},
headers?: HeadersInit
options?: FetchOptions
) => {
const token = getToken()
const res = await fetch(API_URL!, {
method: 'POST',
method: options?.method || 'POST',
headers: {
...(token && {
Authorization: `Bearer ${token}`,
}),
...headers,
...options?.headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
...options?.body,
query,
variables,
}),

View File

@ -1,18 +1,23 @@
import { FetcherError } from '@vercel/commerce/utils/errors'
import type { GraphQLFetcher } from '@vercel/commerce/api'
import type { FetchOptions, GraphQLFetcher } from '@vercel/commerce/api'
import type { SFCCConfig } from '../index'
const fetchGraphqlApi: (getConfig: () => SFCCConfig) => GraphQLFetcher =
(getConfig) =>
async (query: string, { variables, preview } = {}, headers?: HeadersInit) => {
async (
query: string,
{ variables, preview } = {},
options?: FetchOptions
) => {
const config = getConfig()
const res = await fetch(config.commerceUrl, {
method: 'POST',
method: options?.method || 'POST',
headers: {
...headers,
...options?.headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
...options?.body,
query,
variables,
}),

View File

@ -51,7 +51,9 @@ export default function getAllPagesOperation({
},
{
...(locale && {
'Accept-Language': locale,
headers: {
'Accept-Language': locale,
},
}),
}
)

View File

@ -49,7 +49,9 @@ export default function getAllProductsOperation({
{ variables },
{
...(locale && {
'Accept-Language': locale,
headers: {
'Accept-Language': locale,
},
}),
}
)

View File

@ -50,7 +50,9 @@ export default function getPageOperation({
},
{
...(locale && {
'Accept-Language': locale,
headers: {
'Accept-Language': locale,
},
}),
}
)

View File

@ -48,7 +48,9 @@ export default function getProductOperation({
},
{
...(locale && {
'Accept-Language': locale,
headers: {
'Accept-Language': locale,
},
}),
}
)

View File

@ -1,4 +1,4 @@
import type { GraphQLFetcher } from '@vercel/commerce/api'
import type { FetchOptions, GraphQLFetcher } from '@vercel/commerce/api'
import { API_URL, API_TOKEN } from '../../const'
import { getError } from '../../utils/handle-fetch-response'
@ -6,17 +6,18 @@ import { getError } from '../../utils/handle-fetch-response'
const fetchGraphqlApi: GraphQLFetcher = async (
query: string,
{ variables } = {},
headers?: HeadersInit
options?: FetchOptions
) => {
try {
const res = await fetch(API_URL, {
method: 'POST',
method: options?.method || 'POST',
headers: {
'X-Shopify-Storefront-Access-Token': API_TOKEN!,
...headers,
...options?.headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
...options?.body,
query,
variables,
}),

View File

@ -17,7 +17,9 @@ const getCategories = async ({
},
{
...(locale && {
'Accept-Language': locale,
headers: {
'Accept-Language': locale,
},
}),
}
)

View File

@ -1,21 +1,22 @@
import { FetcherError } from '@vercel/commerce/utils/errors'
import type { GraphQLFetcher } from '@vercel/commerce/api'
import type { FetchOptions, GraphQLFetcher } from '@vercel/commerce/api'
import { getCommerceApi } from '../'
const fetchGraphqlApi: GraphQLFetcher = async (
query: string,
{ variables } = {},
headers?: HeadersInit
options?: FetchOptions
) => {
const config = getCommerceApi().getConfig()
const res = await fetch(config.commerceUrl, {
method: 'POST',
method: options?.method || 'POST',
headers: {
...headers,
...options?.headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
...options?.body,
query,
variables,
}),

View File

@ -1,4 +1,4 @@
import { FC, useEffect, useState, useCallback } from 'react'
import { useEffect, useState, useCallback } from 'react'
import { Logo, Button, Input } from '@components/ui'
import useLogin from '@framework/auth/use-login'
import { useUI } from '@components/ui/context'
@ -31,7 +31,6 @@ const LoginView: React.FC = () => {
email,
password,
})
setLoading(false)
closeModal()
} catch ({ errors }) {
if (errors instanceof Array) {
@ -39,15 +38,15 @@ const LoginView: React.FC = () => {
} else {
setMessage('Unexpected error')
}
setLoading(false)
setDisabled(false)
} finally {
setLoading(false)
}
}
const handleValidation = useCallback(() => {
// Test for Alphanumeric password
const validPassword = /^(?=.*[a-zA-Z])(?=.*[0-9])/.test(password)
// Unable to send form unless fields are valid.
if (dirty) {
setDisabled(!validate(email) || password.length < 7 || !validPassword)

View File

@ -38,7 +38,6 @@ const SignUpView: FC<Props> = () => {
lastName,
password,
})
setLoading(false)
closeModal()
} catch ({ errors }) {
console.error(errors)
@ -47,8 +46,9 @@ const SignUpView: FC<Props> = () => {
} else {
setMessage('Unexpected error')
}
setLoading(false)
setDisabled(false)
} finally {
setLoading(false)
}
}

View File

@ -4,7 +4,6 @@
z-index: 10;
height: 100vh;
min-width: 100vw;
transition: none;
}
@media screen(lg) {
@ -18,8 +17,7 @@
.link {
@apply text-primary flex cursor-pointer px-6 py-3
transition ease-in-out duration-150 leading-6
font-medium items-center capitalize w-full box-border
outline-0;
font-medium items-center capitalize w-full box-border outline-0;
}
.link:hover {

View File

@ -35,13 +35,7 @@ export default function CustomerMenuContent() {
}
return (
<DropdownContent
asChild
side="bottom"
sideOffset={10}
className={s.root}
id="CustomerMenuContent"
>
<DropdownContent sideOffset={10} id="CustomerMenuContent">
{LINKS.map(({ name, href }) => (
<DropdownMenuItem key={href}>
<a

View File

@ -7,12 +7,15 @@
}
.item {
@apply ml-6 cursor-pointer relative transition ease-in-out
duration-100 flex items-center outline-none text-primary;
@apply ml-6 flex items-center relative;
}
.item:hover {
@apply text-accent-6 transition scale-110 duration-100;
.item > button {
@apply cursor-pointer transition ease-in-out duration-100 outline-none text-primary;
}
.item > button:hover {
@apply text-accent-6 transition scale-110 outline-none;
}
.item:first-child {

View File

@ -23,13 +23,8 @@ const UserNav: React.FC<{
}> = ({ className }) => {
const { data } = useCart()
const { data: isCustomerLoggedIn } = useCustomer()
const {
toggleSidebar,
closeSidebarIfPresent,
openModal,
setSidebarView,
openSidebar,
} = useUI()
const { closeSidebarIfPresent, openModal, setSidebarView, openSidebar } =
useUI()
const itemsCount = data?.lineItems?.reduce(countItem, 0) ?? 0
const DropdownTrigger = isCustomerLoggedIn
@ -59,10 +54,10 @@ const UserNav: React.FC<{
)}
{process.env.COMMERCE_WISHLIST_ENABLED && (
<li className={s.item}>
<Link href="/wishlist" legacyBehavior>
<a onClick={closeSidebarIfPresent} aria-label="Wishlist">
<Link href="/wishlist">
<button onClick={closeSidebarIfPresent} aria-label="Wishlist">
<Heart />
</a>
</button>
</Link>
</li>
)}

View File

@ -30,7 +30,7 @@ const WishlistButton: FC<Props> = ({
// @ts-ignore Wishlist is not always enabled
const itemInWishlist = data?.items?.find(
// @ts-ignore Wishlist is not always enabled
(item) => item.product_id === productId && item.variant_id === variant.id
(item) => item.productId === productId && item.variantId === variant.id
)
const handleWishlistChange = async (e: any) => {

View File

@ -19,6 +19,7 @@ const WishlistCard: React.FC<{
item: WishlistItem
}> = ({ item }) => {
const product: Product = item.product
const { price } = usePrice({
amount: product.price?.value,
baseAmount: product.price?.retailPrice,

View File

@ -35,9 +35,10 @@ export async function getStaticProps({
}
export default function Wishlist() {
const { data: customer } = useCustomer()
// @ts-ignore Shopify - Fix this types
const { data, isLoading, isEmpty } = useWishlist({ includeProducts: true })
const { data, isLoading, isEmpty } = useWishlist({
includeProducts: true,
})
return (
<Container className="pt-4">
@ -45,10 +46,10 @@ export default function Wishlist() {
<Text variant="pageHeading">My Wishlist</Text>
<div className="group flex flex-col">
{isLoading ? (
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{rangeMap(12, (i) => (
<div className="grid grid-cols-1 gap-6">
{rangeMap(4, (i) => (
<Skeleton key={i}>
<div className="w-60 h-60" />
<div className="w-full h-[279px]" />
</Skeleton>
))}
</div>