mirror of
https://github.com/vercel/commerce.git
synced 2025-03-14 14:42:31 +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,
|
||||
undefined,
|
||||
{
|
||||
'Set-Cookie': `${config.customerCookie}=${token}`,
|
||||
headers: {
|
||||
cookie: `${config.customerCookie}=${token}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
const { customer } = data
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}),
|
||||
|
@ -20,7 +20,9 @@ async function getCustomerId({
|
||||
getCustomerIdQuery,
|
||||
undefined,
|
||||
{
|
||||
'Set-Cookie': `${config.customerCookie}=${customerToken}`,
|
||||
headers: {
|
||||
cookie: `${config.customerCookie}=${customerToken}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
@ -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>>
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
})
|
||||
|
@ -26,6 +26,6 @@ export type LoginSchema = {
|
||||
}
|
||||
|
||||
export type LoginOperation = {
|
||||
data: { result?: string }
|
||||
data: { result?: string; status?: number; headers?: Headers }
|
||||
variables: unknown
|
||||
}
|
||||
|
@ -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,
|
||||
}),
|
||||
|
@ -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
|
||||
|
@ -30,7 +30,9 @@ export default function getAllPagesOperation({
|
||||
{ variables },
|
||||
{
|
||||
...(locale && {
|
||||
'Accept-Language': locale,
|
||||
headers: {
|
||||
'Accept-Language': locale,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
@ -38,9 +38,11 @@ export default function getAllProductsOperation({
|
||||
query,
|
||||
{ variables },
|
||||
{
|
||||
...(locale && {
|
||||
'Accept-Language': locale,
|
||||
}),
|
||||
headers: {
|
||||
...(locale && {
|
||||
'Accept-Language': locale,
|
||||
}),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -28,7 +28,9 @@ export default function getPageOperation({
|
||||
{ variables },
|
||||
{
|
||||
...(locale && {
|
||||
'Accept-Language': locale,
|
||||
headers: {
|
||||
'Accept-Language': locale,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
@ -32,7 +32,9 @@ export default function getProductOperation({
|
||||
{ variables },
|
||||
{
|
||||
...(locale && {
|
||||
'Accept-Language': locale,
|
||||
headers: {
|
||||
'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 { 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,
|
||||
}),
|
||||
|
@ -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,
|
||||
}),
|
||||
|
@ -51,7 +51,9 @@ export default function getAllPagesOperation({
|
||||
},
|
||||
{
|
||||
...(locale && {
|
||||
'Accept-Language': locale,
|
||||
headers: {
|
||||
'Accept-Language': locale,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
@ -49,7 +49,9 @@ export default function getAllProductsOperation({
|
||||
{ variables },
|
||||
{
|
||||
...(locale && {
|
||||
'Accept-Language': locale,
|
||||
headers: {
|
||||
'Accept-Language': locale,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
@ -50,7 +50,9 @@ export default function getPageOperation({
|
||||
},
|
||||
{
|
||||
...(locale && {
|
||||
'Accept-Language': locale,
|
||||
headers: {
|
||||
'Accept-Language': locale,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
@ -48,7 +48,9 @@ export default function getProductOperation({
|
||||
},
|
||||
{
|
||||
...(locale && {
|
||||
'Accept-Language': locale,
|
||||
headers: {
|
||||
'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 { 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,
|
||||
}),
|
||||
|
@ -17,7 +17,9 @@ const getCategories = async ({
|
||||
},
|
||||
{
|
||||
...(locale && {
|
||||
'Accept-Language': locale,
|
||||
headers: {
|
||||
'Accept-Language': locale,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
@ -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,
|
||||
}),
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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) => {
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user