mirror of
https://github.com/vercel/commerce.git
synced 2025-04-28 05:47:50 +00:00
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:
parent
252355717d
commit
d1d9e8c434
@ -34,7 +34,9 @@ const getLoggedInCustomer: CustomerEndpoint['handlers']['getLoggedInCustomer'] =
|
|||||||
getLoggedInCustomerQuery,
|
getLoggedInCustomerQuery,
|
||||||
undefined,
|
undefined,
|
||||||
{
|
{
|
||||||
'Set-Cookie': `${config.customerCookie}=${token}`,
|
headers: {
|
||||||
|
cookie: `${config.customerCookie}=${token}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const { customer } = data
|
const { customer } = data
|
||||||
|
@ -11,12 +11,12 @@ const login: LoginEndpoint['handlers']['login'] = async ({
|
|||||||
commerce,
|
commerce,
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const res = new Response()
|
const response = await commerce.login({
|
||||||
await commerce.login({ variables: { email, password }, config, res })
|
variables: { email, password },
|
||||||
return {
|
config,
|
||||||
status: res.status,
|
})
|
||||||
headers: res.headers,
|
|
||||||
}
|
return response
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Check if the email and password didn't match an existing account
|
// Check if the email and password didn't match an existing account
|
||||||
if (error instanceof FetcherError) {
|
if (error instanceof FetcherError) {
|
||||||
@ -24,7 +24,7 @@ const login: LoginEndpoint['handlers']['login'] = async ({
|
|||||||
invalidCredentials.test(error.message)
|
invalidCredentials.test(error.message)
|
||||||
? 'Cannot find an account that matches the provided credentials'
|
? 'Cannot find an account that matches the provided credentials'
|
||||||
: error.message,
|
: error.message,
|
||||||
{ status: error.status || 401 }
|
{ status: 401 }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import type { SignupEndpoint } from '.'
|
import type { SignupEndpoint } from '.'
|
||||||
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
||||||
|
|
||||||
import { BigcommerceApiError } from '../../utils/errors'
|
import { BigcommerceApiError } from '../../utils/errors'
|
||||||
|
|
||||||
const signup: SignupEndpoint['handlers']['signup'] = async ({
|
const signup: SignupEndpoint['handlers']['signup'] = async ({
|
||||||
@ -22,29 +21,28 @@ 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) {
|
} catch (error) {
|
||||||
if (error instanceof BigcommerceApiError && error.status === 422) {
|
// Display all validation errors from BigCommerce in a single error message
|
||||||
const hasEmailError = '0.email' in error.data?.errors
|
if (error instanceof BigcommerceApiError && error.status >= 400) {
|
||||||
// If there's an error with the email, it most likely means it's duplicated
|
const message = Object.values(error.data.errors).join('<br />')
|
||||||
if (hasEmailError) {
|
if (message) {
|
||||||
throw new CommerceAPIError('Email already in use', {
|
throw new CommerceAPIError(message, {
|
||||||
status: 400,
|
status: 400,
|
||||||
code: 'duplicated_email',
|
code: 'invalid_request',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
throw error
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default signup
|
export default signup
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { parseWishlistItem } from '../../utils/parse-item'
|
import { parseWishlistItem } from '../../utils/parse-item'
|
||||||
import getCustomerId from '../../utils/get-customer-id'
|
import getCustomerId from '../../utils/get-customer-id'
|
||||||
import type { WishlistEndpoint } from '.'
|
import type { WishlistEndpoint } from '.'
|
||||||
|
import { normalizeWishlist } from '../../../lib/normalize'
|
||||||
|
|
||||||
const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
|
const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
|
||||||
body: { customerToken, item },
|
body: { customerToken, item },
|
||||||
@ -31,7 +32,7 @@ const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
data,
|
data: normalizeWishlist(data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +48,9 @@ const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Returns Wishlist
|
// Returns Wishlist
|
||||||
return { data }
|
return {
|
||||||
|
data: normalizeWishlist(data),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default addItem
|
export default addItem
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
||||||
import type { Wishlist } from '@vercel/commerce/types/wishlist'
|
|
||||||
import type { WishlistEndpoint } from '.'
|
import type { WishlistEndpoint } from '.'
|
||||||
import getCustomerId from '../../utils/get-customer-id'
|
import getCustomerId from '../../utils/get-customer-id'
|
||||||
|
|
||||||
@ -9,8 +8,6 @@ const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({
|
|||||||
config,
|
config,
|
||||||
commerce,
|
commerce,
|
||||||
}) => {
|
}) => {
|
||||||
let result: { data?: Wishlist } = {}
|
|
||||||
|
|
||||||
if (customerToken) {
|
if (customerToken) {
|
||||||
const customerId =
|
const customerId =
|
||||||
customerToken && (await getCustomerId({ customerToken, config }))
|
customerToken && (await getCustomerId({ customerToken, config }))
|
||||||
@ -25,10 +22,10 @@ const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({
|
|||||||
config,
|
config,
|
||||||
})
|
})
|
||||||
|
|
||||||
result = { data: wishlist }
|
return { data: wishlist }
|
||||||
}
|
}
|
||||||
|
|
||||||
return { data: result.data ?? null }
|
return { data: null }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getWishlist
|
export default getWishlist
|
||||||
|
@ -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 { WishlistEndpoint } from '.'
|
||||||
|
import type { BCWishlist } from '../../utils/types'
|
||||||
|
|
||||||
|
import getCustomerId from '../../utils/get-customer-id'
|
||||||
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
||||||
|
import { normalizeWishlist } from '../../../lib/normalize'
|
||||||
|
|
||||||
// Return wishlist info
|
// Return wishlist info
|
||||||
const removeItem: WishlistEndpoint['handlers']['removeItem'] = async ({
|
const removeItem: WishlistEndpoint['handlers']['removeItem'] = async ({
|
||||||
@ -11,6 +13,7 @@ const removeItem: WishlistEndpoint['handlers']['removeItem'] = async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const customerId =
|
const customerId =
|
||||||
customerToken && (await getCustomerId({ customerToken, config }))
|
customerToken && (await getCustomerId({ customerToken, config }))
|
||||||
|
|
||||||
const { wishlist } =
|
const { wishlist } =
|
||||||
(customerId &&
|
(customerId &&
|
||||||
(await commerce.getCustomerWishlist({
|
(await commerce.getCustomerWishlist({
|
||||||
@ -23,13 +26,12 @@ const removeItem: WishlistEndpoint['handlers']['removeItem'] = async ({
|
|||||||
throw new CommerceAPIError('Wishlist not found', { status: 400 })
|
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}`,
|
`/v3/wishlists/${wishlist.id}/items/${itemId}`,
|
||||||
{ method: 'DELETE' }
|
{ method: 'DELETE' }
|
||||||
)
|
)
|
||||||
const data = result?.data ?? null
|
|
||||||
|
|
||||||
return { data }
|
return { data: result?.data ? normalizeWishlist(result.data) : null }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default removeItem
|
export default removeItem
|
||||||
|
@ -2,13 +2,11 @@ import type {
|
|||||||
OperationContext,
|
OperationContext,
|
||||||
OperationOptions,
|
OperationOptions,
|
||||||
} from '@vercel/commerce/api/operations'
|
} from '@vercel/commerce/api/operations'
|
||||||
import type {
|
import type { GetCustomerWishlistOperation } from '@vercel/commerce/types/wishlist'
|
||||||
GetCustomerWishlistOperation,
|
import type { RecursivePartial, BCWishlist } from '../utils/types'
|
||||||
Wishlist,
|
|
||||||
} from '@vercel/commerce/types/wishlist'
|
|
||||||
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
|
||||||
import { BigcommerceConfig, Provider } from '..'
|
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({
|
export default function getCustomerWishlistOperation({
|
||||||
commerce,
|
commerce,
|
||||||
@ -41,18 +39,22 @@ export default function getCustomerWishlistOperation({
|
|||||||
}): Promise<T['data']> {
|
}): Promise<T['data']> {
|
||||||
config = commerce.getConfig(config)
|
config = commerce.getConfig(config)
|
||||||
|
|
||||||
const { data = [] } = await config.storeApiFetch<
|
const { data = [] } = await config.storeApiFetch<{ data: BCWishlist[] }>(
|
||||||
RecursivePartial<{ data: Wishlist[] }>
|
`/v3/wishlists?customer_id=${variables.customerId}`
|
||||||
>(`/v3/wishlists?customer_id=${variables.customerId}`)
|
)
|
||||||
|
|
||||||
const wishlist = data[0]
|
const wishlist = data[0]
|
||||||
|
|
||||||
if (includeProducts && wishlist?.items?.length) {
|
if (includeProducts && wishlist?.items?.length) {
|
||||||
const ids = wishlist.items
|
const ids = []
|
||||||
?.map((item) => (item?.productId ? String(item?.productId) : null))
|
|
||||||
.filter((id): id is string => !!id)
|
|
||||||
|
|
||||||
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({
|
const graphqlData = await commerce.getAllProducts({
|
||||||
variables: { first: 50, ids },
|
variables: { first: 50, ids },
|
||||||
config,
|
config,
|
||||||
@ -66,7 +68,7 @@ export default function getCustomerWishlistOperation({
|
|||||||
}, {})
|
}, {})
|
||||||
// Populate the wishlist items with the graphql products
|
// Populate the wishlist items with the graphql products
|
||||||
wishlist.items.forEach((item) => {
|
wishlist.items.forEach((item) => {
|
||||||
const product = item && productsById[Number(item.productId)]
|
const product = item && productsById[Number(item.product_id)]
|
||||||
if (item && product) {
|
if (item && product) {
|
||||||
// @ts-ignore Fix this type when the wishlist type is properly defined
|
// @ts-ignore Fix this type when the wishlist type is properly defined
|
||||||
item.product = product
|
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
|
return getCustomerWishlist
|
||||||
|
@ -5,7 +5,6 @@ import type {
|
|||||||
import type { LoginOperation } from '@vercel/commerce/types/login'
|
import type { LoginOperation } from '@vercel/commerce/types/login'
|
||||||
import type { LoginMutation } from '../../../schema'
|
import type { LoginMutation } from '../../../schema'
|
||||||
import type { RecursivePartial } from '../utils/types'
|
import type { RecursivePartial } from '../utils/types'
|
||||||
import concatHeader from '../utils/concat-cookie'
|
|
||||||
import type { BigcommerceConfig, Provider } from '..'
|
import type { BigcommerceConfig, Provider } from '..'
|
||||||
|
|
||||||
export const loginMutation = /* GraphQL */ `
|
export const loginMutation = /* GraphQL */ `
|
||||||
@ -22,26 +21,23 @@ export default function loginOperation({
|
|||||||
async function login<T extends LoginOperation>(opts: {
|
async function login<T extends LoginOperation>(opts: {
|
||||||
variables: T['variables']
|
variables: T['variables']
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
res: Response
|
|
||||||
}): Promise<T['data']>
|
}): Promise<T['data']>
|
||||||
|
|
||||||
async function login<T extends LoginOperation>(
|
async function login<T extends LoginOperation>(
|
||||||
opts: {
|
opts: {
|
||||||
variables: T['variables']
|
variables: T['variables']
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
res: Response
|
|
||||||
} & OperationOptions
|
} & OperationOptions
|
||||||
): Promise<T['data']>
|
): Promise<T['data']>
|
||||||
|
|
||||||
async function login<T extends LoginOperation>({
|
async function login<T extends LoginOperation>({
|
||||||
query = loginMutation,
|
query = loginMutation,
|
||||||
variables,
|
variables,
|
||||||
res: response,
|
|
||||||
config,
|
config,
|
||||||
}: {
|
}: {
|
||||||
query?: string
|
query?: string
|
||||||
variables: T['variables']
|
variables: T['variables']
|
||||||
res: Response
|
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
}): Promise<T['data']> {
|
}): Promise<T['data']> {
|
||||||
config = commerce.getConfig(config)
|
config = commerce.getConfig(config)
|
||||||
@ -50,6 +46,9 @@ export default function loginOperation({
|
|||||||
query,
|
query,
|
||||||
{ variables }
|
{ variables }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const headers = new Headers()
|
||||||
|
|
||||||
// Bigcommerce returns a Set-Cookie header with the auth cookie
|
// Bigcommerce returns a Set-Cookie header with the auth cookie
|
||||||
let cookie = res.headers.get('Set-Cookie')
|
let cookie = res.headers.get('Set-Cookie')
|
||||||
|
|
||||||
@ -63,19 +62,13 @@ export default function loginOperation({
|
|||||||
cookie = cookie.replace(/; SameSite=none/gi, '; SameSite=lax')
|
cookie = cookie.replace(/; SameSite=none/gi, '; SameSite=lax')
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevCookie = response.headers.get('Set-Cookie')
|
headers.set('Set-Cookie', cookie)
|
||||||
const newCookie = concatHeader(prevCookie, cookie)
|
|
||||||
|
|
||||||
if (newCookie) {
|
|
||||||
res.headers.set(
|
|
||||||
'Set-Cookie',
|
|
||||||
String(Array.isArray(newCookie) ? newCookie.join(',') : newCookie)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: data.login?.result,
|
result: data.login?.result,
|
||||||
|
headers,
|
||||||
|
status: res.status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
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'
|
import type { BigcommerceConfig } from '../index'
|
||||||
|
|
||||||
const fetchGraphqlApi: (getConfig: () => BigcommerceConfig) => GraphQLFetcher =
|
const fetchGraphqlApi: (getConfig: () => BigcommerceConfig) => GraphQLFetcher =
|
||||||
@ -7,19 +7,20 @@ const fetchGraphqlApi: (getConfig: () => BigcommerceConfig) => GraphQLFetcher =
|
|||||||
async (
|
async (
|
||||||
query: string,
|
query: string,
|
||||||
{ variables, preview } = {},
|
{ variables, preview } = {},
|
||||||
options: { headers?: HeadersInit } = {}
|
options?: FetchOptions
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
// log.warn(query)
|
// log.warn(query)
|
||||||
const config = getConfig()
|
const config = getConfig()
|
||||||
|
|
||||||
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
||||||
method: 'POST',
|
method: options?.method || 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${config.apiToken}`,
|
Authorization: `Bearer ${config.apiToken}`,
|
||||||
...options.headers,
|
...options?.headers,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
...options?.body,
|
||||||
query,
|
query,
|
||||||
variables,
|
variables,
|
||||||
}),
|
}),
|
||||||
|
@ -20,7 +20,9 @@ async function getCustomerId({
|
|||||||
getCustomerIdQuery,
|
getCustomerIdQuery,
|
||||||
undefined,
|
undefined,
|
||||||
{
|
{
|
||||||
'Set-Cookie': `${config.customerCookie}=${customerToken}`,
|
headers: {
|
||||||
|
cookie: `${config.customerCookie}=${customerToken}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,3 +5,15 @@ export type RecursivePartial<T> = {
|
|||||||
export type RecursiveRequired<T> = {
|
export type RecursiveRequired<T> = {
|
||||||
[P in keyof T]-?: RecursiveRequired<T[P]>
|
[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
|
||||||
|
}
|
||||||
|
@ -5,8 +5,10 @@ import type { Category, Brand } from '@vercel/commerce/types/site'
|
|||||||
import type { BigcommerceCart, BCCategory, BCBrand } from '../types'
|
import type { BigcommerceCart, BCCategory, BCBrand } from '../types'
|
||||||
import type { ProductNode } from '../api/operations/get-all-products'
|
import type { ProductNode } from '../api/operations/get-all-products'
|
||||||
import type { definitions } from '../api/definitions/store-content'
|
import type { definitions } from '../api/definitions/store-content'
|
||||||
|
import type { BCWishlist } from '../api/utils/types'
|
||||||
|
|
||||||
import getSlug from './get-slug'
|
import getSlug from './get-slug'
|
||||||
|
import { Wishlist } from '@vercel/commerce/types/wishlist'
|
||||||
|
|
||||||
function normalizeProductOption(productOption: any) {
|
function normalizeProductOption(productOption: any) {
|
||||||
const {
|
const {
|
||||||
@ -137,3 +139,16 @@ export function normalizeBrand(brand: BCBrand): Brand {
|
|||||||
path: `/${slug}`,
|
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,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -73,6 +73,12 @@ export type EndpointHandlers<
|
|||||||
>
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FetchOptions<Body = any> = {
|
||||||
|
method?: string
|
||||||
|
body?: Body
|
||||||
|
headers?: HeadersInit
|
||||||
|
}
|
||||||
|
|
||||||
export type APIProvider = {
|
export type APIProvider = {
|
||||||
config: CommerceAPIConfig
|
config: CommerceAPIConfig
|
||||||
operations: APIOperations<any>
|
operations: APIOperations<any>
|
||||||
@ -165,7 +171,7 @@ export interface CommerceAPIConfig {
|
|||||||
fetch<Data = any, Variables = any>(
|
fetch<Data = any, Variables = any>(
|
||||||
query: string,
|
query: string,
|
||||||
queryData?: CommerceAPIFetchOptions<Variables>,
|
queryData?: CommerceAPIFetchOptions<Variables>,
|
||||||
headers?: HeadersInit
|
options?: FetchOptions
|
||||||
): Promise<GraphQLFetcherResult<Data>>
|
): Promise<GraphQLFetcherResult<Data>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ export const wishlistSchemaItem = z.object({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
productId: z.string(),
|
productId: z.string(),
|
||||||
variantId: z.string(),
|
variantId: z.string(),
|
||||||
product: productSchema,
|
product: productSchema.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const wishlistSchema = z.object({
|
export const wishlistSchema = z.object({
|
||||||
@ -15,7 +15,7 @@ export const wishlistSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const getWishlistBodySchema = z.object({
|
export const getWishlistBodySchema = z.object({
|
||||||
customerAccessToken: z.string(),
|
customerToken: z.string().optional(),
|
||||||
includeProducts: z.boolean(),
|
includeProducts: z.boolean(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -25,17 +25,17 @@ export const wishlistItemBodySchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const addItemBodySchema = z.object({
|
export const addItemBodySchema = z.object({
|
||||||
cartId: z.string().optional(),
|
customerToken: z.string(),
|
||||||
item: wishlistItemBodySchema,
|
item: wishlistItemBodySchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const updateItemBodySchema = z.object({
|
export const updateItemBodySchema = z.object({
|
||||||
cartId: z.string(),
|
customerToken: z.string(),
|
||||||
itemId: z.string(),
|
itemId: z.string(),
|
||||||
item: wishlistItemBodySchema,
|
item: wishlistItemBodySchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const removeItemBodySchema = z.object({
|
export const removeItemBodySchema = z.object({
|
||||||
cartId: z.string(),
|
customerToken: z.string(),
|
||||||
itemId: z.string(),
|
itemId: z.string(),
|
||||||
})
|
})
|
||||||
|
@ -26,6 +26,6 @@ export type LoginSchema = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type LoginOperation = {
|
export type LoginOperation = {
|
||||||
data: { result?: string }
|
data: { result?: string; status?: number; headers?: Headers }
|
||||||
variables: unknown
|
variables: unknown
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
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 type { KiboCommerceConfig } from '../index'
|
||||||
|
|
||||||
import { APIAuthenticationHelper } from './api-auth-helper'
|
import { APIAuthenticationHelper } from './api-auth-helper'
|
||||||
@ -8,18 +8,23 @@ const fetchGraphqlApi: (
|
|||||||
getConfig: () => KiboCommerceConfig
|
getConfig: () => KiboCommerceConfig
|
||||||
) => GraphQLFetcher =
|
) => GraphQLFetcher =
|
||||||
(getConfig) =>
|
(getConfig) =>
|
||||||
async (query: string, { variables, preview } = {}, headers?: HeadersInit) => {
|
async (
|
||||||
|
query: string,
|
||||||
|
{ variables, preview } = {},
|
||||||
|
options?: FetchOptions
|
||||||
|
) => {
|
||||||
const config = getConfig()
|
const config = getConfig()
|
||||||
const authHelper = new APIAuthenticationHelper(config)
|
const authHelper = new APIAuthenticationHelper(config)
|
||||||
const apiToken = await authHelper.getAccessToken()
|
const apiToken = await authHelper.getAccessToken()
|
||||||
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
||||||
method: 'POST',
|
method: options?.method || 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...options?.headers,
|
||||||
Authorization: `Bearer ${apiToken}`,
|
Authorization: `Bearer ${apiToken}`,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
...options?.body,
|
||||||
query,
|
query,
|
||||||
variables,
|
variables,
|
||||||
}),
|
}),
|
||||||
|
@ -13,7 +13,9 @@ async function getCustomerId({
|
|||||||
: null
|
: null
|
||||||
const accessToken = token ? JSON.parse(token).accessToken : null
|
const accessToken = token ? JSON.parse(token).accessToken : null
|
||||||
const { data } = await config.fetch(getCustomerAccountQuery, undefined, {
|
const { data } = await config.fetch(getCustomerAccountQuery, undefined, {
|
||||||
|
headers: {
|
||||||
'x-vol-user-claims': accessToken,
|
'x-vol-user-claims': accessToken,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return data?.customerAccount?.id
|
return data?.customerAccount?.id
|
||||||
|
@ -30,7 +30,9 @@ export default function getAllPagesOperation({
|
|||||||
{ variables },
|
{ variables },
|
||||||
{
|
{
|
||||||
...(locale && {
|
...(locale && {
|
||||||
|
headers: {
|
||||||
'Accept-Language': locale,
|
'Accept-Language': locale,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -38,9 +38,11 @@ export default function getAllProductsOperation({
|
|||||||
query,
|
query,
|
||||||
{ variables },
|
{ variables },
|
||||||
{
|
{
|
||||||
|
headers: {
|
||||||
...(locale && {
|
...(locale && {
|
||||||
'Accept-Language': locale,
|
'Accept-Language': locale,
|
||||||
}),
|
}),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,7 +28,9 @@ export default function getPageOperation({
|
|||||||
{ variables },
|
{ variables },
|
||||||
{
|
{
|
||||||
...(locale && {
|
...(locale && {
|
||||||
|
headers: {
|
||||||
'Accept-Language': locale,
|
'Accept-Language': locale,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -32,7 +32,9 @@ export default function getProductOperation({
|
|||||||
{ variables },
|
{ variables },
|
||||||
{
|
{
|
||||||
...(locale && {
|
...(locale && {
|
||||||
|
headers: {
|
||||||
'Accept-Language': locale,
|
'Accept-Language': locale,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -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 { API_URL } from '../../const'
|
||||||
import { getError } from '../../utils/handle-fetch-response'
|
import { getError } from '../../utils/handle-fetch-response'
|
||||||
@ -7,20 +7,21 @@ import { getToken } from '../../utils/index'
|
|||||||
const fetchGraphqlApi: GraphQLFetcher = async (
|
const fetchGraphqlApi: GraphQLFetcher = async (
|
||||||
query: string,
|
query: string,
|
||||||
{ variables } = {},
|
{ variables } = {},
|
||||||
headers?: HeadersInit
|
options?: FetchOptions
|
||||||
) => {
|
) => {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
|
|
||||||
const res = await fetch(API_URL!, {
|
const res = await fetch(API_URL!, {
|
||||||
method: 'POST',
|
method: options?.method || 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
...(token && {
|
...(token && {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
}),
|
}),
|
||||||
...headers,
|
...options?.headers,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
...options?.body,
|
||||||
query,
|
query,
|
||||||
variables,
|
variables,
|
||||||
}),
|
}),
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
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'
|
import type { SFCCConfig } from '../index'
|
||||||
|
|
||||||
const fetchGraphqlApi: (getConfig: () => SFCCConfig) => GraphQLFetcher =
|
const fetchGraphqlApi: (getConfig: () => SFCCConfig) => GraphQLFetcher =
|
||||||
(getConfig) =>
|
(getConfig) =>
|
||||||
async (query: string, { variables, preview } = {}, headers?: HeadersInit) => {
|
async (
|
||||||
|
query: string,
|
||||||
|
{ variables, preview } = {},
|
||||||
|
options?: FetchOptions
|
||||||
|
) => {
|
||||||
const config = getConfig()
|
const config = getConfig()
|
||||||
const res = await fetch(config.commerceUrl, {
|
const res = await fetch(config.commerceUrl, {
|
||||||
method: 'POST',
|
method: options?.method || 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...options?.headers,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
...options?.body,
|
||||||
query,
|
query,
|
||||||
variables,
|
variables,
|
||||||
}),
|
}),
|
||||||
|
@ -51,7 +51,9 @@ export default function getAllPagesOperation({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
...(locale && {
|
...(locale && {
|
||||||
|
headers: {
|
||||||
'Accept-Language': locale,
|
'Accept-Language': locale,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -49,7 +49,9 @@ export default function getAllProductsOperation({
|
|||||||
{ variables },
|
{ variables },
|
||||||
{
|
{
|
||||||
...(locale && {
|
...(locale && {
|
||||||
|
headers: {
|
||||||
'Accept-Language': locale,
|
'Accept-Language': locale,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -50,7 +50,9 @@ export default function getPageOperation({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
...(locale && {
|
...(locale && {
|
||||||
|
headers: {
|
||||||
'Accept-Language': locale,
|
'Accept-Language': locale,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -48,7 +48,9 @@ export default function getProductOperation({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
...(locale && {
|
...(locale && {
|
||||||
|
headers: {
|
||||||
'Accept-Language': locale,
|
'Accept-Language': locale,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -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 { API_URL, API_TOKEN } from '../../const'
|
||||||
import { getError } from '../../utils/handle-fetch-response'
|
import { getError } from '../../utils/handle-fetch-response'
|
||||||
@ -6,17 +6,18 @@ import { getError } from '../../utils/handle-fetch-response'
|
|||||||
const fetchGraphqlApi: GraphQLFetcher = async (
|
const fetchGraphqlApi: GraphQLFetcher = async (
|
||||||
query: string,
|
query: string,
|
||||||
{ variables } = {},
|
{ variables } = {},
|
||||||
headers?: HeadersInit
|
options?: FetchOptions
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(API_URL, {
|
const res = await fetch(API_URL, {
|
||||||
method: 'POST',
|
method: options?.method || 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'X-Shopify-Storefront-Access-Token': API_TOKEN!,
|
'X-Shopify-Storefront-Access-Token': API_TOKEN!,
|
||||||
...headers,
|
...options?.headers,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
...options?.body,
|
||||||
query,
|
query,
|
||||||
variables,
|
variables,
|
||||||
}),
|
}),
|
||||||
|
@ -17,7 +17,9 @@ const getCategories = async ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
...(locale && {
|
...(locale && {
|
||||||
|
headers: {
|
||||||
'Accept-Language': locale,
|
'Accept-Language': locale,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
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 '../'
|
import { getCommerceApi } from '../'
|
||||||
|
|
||||||
const fetchGraphqlApi: GraphQLFetcher = async (
|
const fetchGraphqlApi: GraphQLFetcher = async (
|
||||||
query: string,
|
query: string,
|
||||||
{ variables } = {},
|
{ variables } = {},
|
||||||
headers?: HeadersInit
|
options?: FetchOptions
|
||||||
) => {
|
) => {
|
||||||
const config = getCommerceApi().getConfig()
|
const config = getCommerceApi().getConfig()
|
||||||
|
|
||||||
const res = await fetch(config.commerceUrl, {
|
const res = await fetch(config.commerceUrl, {
|
||||||
method: 'POST',
|
method: options?.method || 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...options?.headers,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
...options?.body,
|
||||||
query,
|
query,
|
||||||
variables,
|
variables,
|
||||||
}),
|
}),
|
||||||
|
@ -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 { Logo, Button, Input } from '@components/ui'
|
||||||
import useLogin from '@framework/auth/use-login'
|
import useLogin from '@framework/auth/use-login'
|
||||||
import { useUI } from '@components/ui/context'
|
import { useUI } from '@components/ui/context'
|
||||||
@ -31,7 +31,6 @@ const LoginView: React.FC = () => {
|
|||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
setLoading(false)
|
|
||||||
closeModal()
|
closeModal()
|
||||||
} catch ({ errors }) {
|
} catch ({ errors }) {
|
||||||
if (errors instanceof Array) {
|
if (errors instanceof Array) {
|
||||||
@ -39,15 +38,15 @@ const LoginView: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
setMessage('Unexpected error')
|
setMessage('Unexpected error')
|
||||||
}
|
}
|
||||||
setLoading(false)
|
|
||||||
setDisabled(false)
|
setDisabled(false)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleValidation = useCallback(() => {
|
const handleValidation = useCallback(() => {
|
||||||
// Test for Alphanumeric password
|
// Test for Alphanumeric password
|
||||||
const validPassword = /^(?=.*[a-zA-Z])(?=.*[0-9])/.test(password)
|
const validPassword = /^(?=.*[a-zA-Z])(?=.*[0-9])/.test(password)
|
||||||
|
|
||||||
// Unable to send form unless fields are valid.
|
// Unable to send form unless fields are valid.
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
setDisabled(!validate(email) || password.length < 7 || !validPassword)
|
setDisabled(!validate(email) || password.length < 7 || !validPassword)
|
||||||
|
@ -38,7 +38,6 @@ const SignUpView: FC<Props> = () => {
|
|||||||
lastName,
|
lastName,
|
||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
setLoading(false)
|
|
||||||
closeModal()
|
closeModal()
|
||||||
} catch ({ errors }) {
|
} catch ({ errors }) {
|
||||||
console.error(errors)
|
console.error(errors)
|
||||||
@ -47,8 +46,9 @@ const SignUpView: FC<Props> = () => {
|
|||||||
} else {
|
} else {
|
||||||
setMessage('Unexpected error')
|
setMessage('Unexpected error')
|
||||||
}
|
}
|
||||||
setLoading(false)
|
|
||||||
setDisabled(false)
|
setDisabled(false)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
min-width: 100vw;
|
min-width: 100vw;
|
||||||
transition: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen(lg) {
|
@media screen(lg) {
|
||||||
@ -18,8 +17,7 @@
|
|||||||
.link {
|
.link {
|
||||||
@apply text-primary flex cursor-pointer px-6 py-3
|
@apply text-primary flex cursor-pointer px-6 py-3
|
||||||
transition ease-in-out duration-150 leading-6
|
transition ease-in-out duration-150 leading-6
|
||||||
font-medium items-center capitalize w-full box-border
|
font-medium items-center capitalize w-full box-border outline-0;
|
||||||
outline-0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.link:hover {
|
.link:hover {
|
||||||
|
@ -35,13 +35,7 @@ export default function CustomerMenuContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownContent
|
<DropdownContent sideOffset={10} id="CustomerMenuContent">
|
||||||
asChild
|
|
||||||
side="bottom"
|
|
||||||
sideOffset={10}
|
|
||||||
className={s.root}
|
|
||||||
id="CustomerMenuContent"
|
|
||||||
>
|
|
||||||
{LINKS.map(({ name, href }) => (
|
{LINKS.map(({ name, href }) => (
|
||||||
<DropdownMenuItem key={href}>
|
<DropdownMenuItem key={href}>
|
||||||
<a
|
<a
|
||||||
|
@ -7,12 +7,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
@apply ml-6 cursor-pointer relative transition ease-in-out
|
@apply ml-6 flex items-center relative;
|
||||||
duration-100 flex items-center outline-none text-primary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item:hover {
|
.item > button {
|
||||||
@apply text-accent-6 transition scale-110 duration-100;
|
@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 {
|
.item:first-child {
|
||||||
|
@ -23,13 +23,8 @@ const UserNav: React.FC<{
|
|||||||
}> = ({ className }) => {
|
}> = ({ className }) => {
|
||||||
const { data } = useCart()
|
const { data } = useCart()
|
||||||
const { data: isCustomerLoggedIn } = useCustomer()
|
const { data: isCustomerLoggedIn } = useCustomer()
|
||||||
const {
|
const { closeSidebarIfPresent, openModal, setSidebarView, openSidebar } =
|
||||||
toggleSidebar,
|
useUI()
|
||||||
closeSidebarIfPresent,
|
|
||||||
openModal,
|
|
||||||
setSidebarView,
|
|
||||||
openSidebar,
|
|
||||||
} = useUI()
|
|
||||||
|
|
||||||
const itemsCount = data?.lineItems?.reduce(countItem, 0) ?? 0
|
const itemsCount = data?.lineItems?.reduce(countItem, 0) ?? 0
|
||||||
const DropdownTrigger = isCustomerLoggedIn
|
const DropdownTrigger = isCustomerLoggedIn
|
||||||
@ -59,10 +54,10 @@ const UserNav: React.FC<{
|
|||||||
)}
|
)}
|
||||||
{process.env.COMMERCE_WISHLIST_ENABLED && (
|
{process.env.COMMERCE_WISHLIST_ENABLED && (
|
||||||
<li className={s.item}>
|
<li className={s.item}>
|
||||||
<Link href="/wishlist" legacyBehavior>
|
<Link href="/wishlist">
|
||||||
<a onClick={closeSidebarIfPresent} aria-label="Wishlist">
|
<button onClick={closeSidebarIfPresent} aria-label="Wishlist">
|
||||||
<Heart />
|
<Heart />
|
||||||
</a>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
@ -30,7 +30,7 @@ const WishlistButton: FC<Props> = ({
|
|||||||
// @ts-ignore Wishlist is not always enabled
|
// @ts-ignore Wishlist is not always enabled
|
||||||
const itemInWishlist = data?.items?.find(
|
const itemInWishlist = data?.items?.find(
|
||||||
// @ts-ignore Wishlist is not always enabled
|
// @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) => {
|
const handleWishlistChange = async (e: any) => {
|
||||||
|
@ -19,6 +19,7 @@ const WishlistCard: React.FC<{
|
|||||||
item: WishlistItem
|
item: WishlistItem
|
||||||
}> = ({ item }) => {
|
}> = ({ item }) => {
|
||||||
const product: Product = item.product
|
const product: Product = item.product
|
||||||
|
|
||||||
const { price } = usePrice({
|
const { price } = usePrice({
|
||||||
amount: product.price?.value,
|
amount: product.price?.value,
|
||||||
baseAmount: product.price?.retailPrice,
|
baseAmount: product.price?.retailPrice,
|
||||||
|
@ -35,9 +35,10 @@ export async function getStaticProps({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Wishlist() {
|
export default function Wishlist() {
|
||||||
const { data: customer } = useCustomer()
|
|
||||||
// @ts-ignore Shopify - Fix this types
|
// @ts-ignore Shopify - Fix this types
|
||||||
const { data, isLoading, isEmpty } = useWishlist({ includeProducts: true })
|
const { data, isLoading, isEmpty } = useWishlist({
|
||||||
|
includeProducts: true,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4">
|
<Container className="pt-4">
|
||||||
@ -45,10 +46,10 @@ export default function Wishlist() {
|
|||||||
<Text variant="pageHeading">My Wishlist</Text>
|
<Text variant="pageHeading">My Wishlist</Text>
|
||||||
<div className="group flex flex-col">
|
<div className="group flex flex-col">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-6">
|
||||||
{rangeMap(12, (i) => (
|
{rangeMap(4, (i) => (
|
||||||
<Skeleton key={i}>
|
<Skeleton key={i}>
|
||||||
<div className="w-60 h-60" />
|
<div className="w-full h-[279px]" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user