forked from crowetic/commerce
Merge branch 'master' of github.com:okbel/e-comm-example
This commit is contained in:
commit
98a1b7e8f0
@ -26,7 +26,7 @@ const getLoggedInCustomer: CustomersHandlers['getLoggedInCustomer'] = async ({
|
||||
res,
|
||||
config,
|
||||
}) => {
|
||||
const data = await config.fetch<GetLoggedInCustomerQuery>(
|
||||
const { data } = await config.fetch<GetLoggedInCustomerQuery>(
|
||||
getLoggedInCustomerQuery
|
||||
)
|
||||
const { customer } = data
|
||||
|
@ -18,51 +18,45 @@ const signup: SignupHandlers['signup'] = async ({
|
||||
// Passwords must be at least 7 characters and contain both alphabetic
|
||||
// and numeric characters.
|
||||
|
||||
let result: { data?: any } = {}
|
||||
try {
|
||||
await config.storeApiFetch('/v3/customers', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify([
|
||||
{
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
email,
|
||||
authentication: {
|
||||
new_password: password,
|
||||
},
|
||||
},
|
||||
]),
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof BigcommerceApiError && error.status === 422) {
|
||||
const hasEmailError = '0.email' in error.data?.errors
|
||||
|
||||
// try {
|
||||
// result = await config.storeApiFetch('/v3/customers', {
|
||||
// method: 'POST',
|
||||
// body: JSON.stringify([
|
||||
// {
|
||||
// first_name: firstName,
|
||||
// last_name: lastName,
|
||||
// email,
|
||||
// authentication: {
|
||||
// new_password: password,
|
||||
// },
|
||||
// },
|
||||
// ]),
|
||||
// })
|
||||
// } 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) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
message: 'The email is already in use',
|
||||
code: 'duplicated_email',
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// // If there's an error with the email, it most likely means it's duplicated
|
||||
// if (hasEmailError) {
|
||||
// return res.status(400).json({
|
||||
// data: null,
|
||||
// errors: [
|
||||
// {
|
||||
// message: 'The email is already in use',
|
||||
// code: 'duplicated_email',
|
||||
// },
|
||||
// ],
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
throw error
|
||||
}
|
||||
|
||||
// throw error
|
||||
// }
|
||||
// Login the customer right after creating it
|
||||
await login({ variables: { email, password }, res, config })
|
||||
|
||||
console.log('DATA', result.data)
|
||||
|
||||
// TODO: Currently not working, fix this asap.
|
||||
const loginData = await login({ variables: { email, password }, config })
|
||||
|
||||
console.log('LOGIN DATA', loginData)
|
||||
|
||||
res.status(200).json({ data: result.data ?? null })
|
||||
res.status(200).json({ data: null })
|
||||
}
|
||||
|
||||
export default signup
|
||||
|
@ -49,9 +49,9 @@ async function getAllProductPaths({
|
||||
config = getConfig(config)
|
||||
// RecursivePartial forces the method to check for every prop in the data, which is
|
||||
// required in case there's a custom `query`
|
||||
const data = await config.fetch<RecursivePartial<GetAllProductPathsQuery>>(
|
||||
query
|
||||
)
|
||||
const { data } = await config.fetch<
|
||||
RecursivePartial<GetAllProductPathsQuery>
|
||||
>(query)
|
||||
const products = data.site?.products?.edges
|
||||
|
||||
return {
|
||||
|
@ -111,7 +111,7 @@ async function getAllProducts({
|
||||
|
||||
// RecursivePartial forces the method to check for every prop in the data, which is
|
||||
// required in case there's a custom `query`
|
||||
const data = await config.fetch<RecursivePartial<GetAllProductsQuery>>(
|
||||
const { data } = await config.fetch<RecursivePartial<GetAllProductsQuery>>(
|
||||
query,
|
||||
{ variables }
|
||||
)
|
||||
|
@ -71,9 +71,10 @@ async function getProduct({
|
||||
...vars,
|
||||
path: slug ? `/${slug}/` : vars.path!,
|
||||
}
|
||||
const data = await config.fetch<RecursivePartial<GetProductQuery>>(query, {
|
||||
variables,
|
||||
})
|
||||
const { data } = await config.fetch<RecursivePartial<GetProductQuery>>(
|
||||
query,
|
||||
{ variables }
|
||||
)
|
||||
const product = data.site?.route?.node
|
||||
|
||||
if (product?.__typename === 'Product') {
|
||||
|
@ -90,9 +90,10 @@ async function getSiteInfo({
|
||||
config = getConfig(config)
|
||||
// RecursivePartial forces the method to check for every prop in the data, which is
|
||||
// required in case there's a custom `query`
|
||||
const data = await config.fetch<RecursivePartial<GetSiteInfoQuery>>(query, {
|
||||
variables,
|
||||
})
|
||||
const { data } = await config.fetch<RecursivePartial<GetSiteInfoQuery>>(
|
||||
query,
|
||||
{ variables }
|
||||
)
|
||||
const categories = data.site?.categoryTree
|
||||
const brands = data.site?.brands?.edges
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import type { ServerResponse } from 'http'
|
||||
import type {
|
||||
LoginMutation,
|
||||
LoginMutationVariables,
|
||||
} from 'lib/bigcommerce/schema'
|
||||
import type { RecursivePartial } from '../utils/types'
|
||||
import concatHeader from '../utils/concat-cookie'
|
||||
import { BigcommerceConfig, getConfig } from '..'
|
||||
|
||||
export const loginMutation = /* GraphQL */ `
|
||||
@ -20,28 +22,41 @@ export type LoginVariables = LoginMutationVariables
|
||||
async function login(opts: {
|
||||
variables: LoginVariables
|
||||
config?: BigcommerceConfig
|
||||
res: ServerResponse
|
||||
}): Promise<LoginResult>
|
||||
|
||||
async function login<T extends { result?: any }, V = any>(opts: {
|
||||
query: string
|
||||
variables: V
|
||||
res: ServerResponse
|
||||
config?: BigcommerceConfig
|
||||
}): Promise<LoginResult<T>>
|
||||
|
||||
async function login({
|
||||
query = loginMutation,
|
||||
variables,
|
||||
res: response,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables: LoginVariables
|
||||
res: ServerResponse
|
||||
config?: BigcommerceConfig
|
||||
}): Promise<LoginResult> {
|
||||
config = getConfig(config)
|
||||
|
||||
const data = await config.fetch<RecursivePartial<LoginMutation>>(query, {
|
||||
variables,
|
||||
})
|
||||
const { data, res } = await config.fetch<RecursivePartial<LoginMutation>>(
|
||||
query,
|
||||
{ variables }
|
||||
)
|
||||
const cookie = res.headers.get('Set-Cookie')
|
||||
|
||||
if (cookie && typeof cookie === 'string') {
|
||||
response.setHeader(
|
||||
'Set-Cookie',
|
||||
concatHeader(response.getHeader('Set-Cookie'), cookie)!
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
result: data.login?.result,
|
||||
|
14
lib/bigcommerce/api/utils/concat-cookie.ts
Normal file
14
lib/bigcommerce/api/utils/concat-cookie.ts
Normal file
@ -0,0 +1,14 @@
|
||||
type Header = string | number | string[] | undefined
|
||||
|
||||
export default function concatHeader(prev: Header, val: Header) {
|
||||
if (!val) return prev
|
||||
if (!prev) return val
|
||||
|
||||
if (Array.isArray(prev)) return prev.concat(String(val))
|
||||
|
||||
prev = String(prev)
|
||||
|
||||
if (Array.isArray(val)) return [prev].concat(val)
|
||||
|
||||
return [prev, String(val)]
|
||||
}
|
@ -1,18 +1,21 @@
|
||||
import { CommerceAPIFetchOptions } from 'lib/commerce/api'
|
||||
import type { GraphQLFetcher } from 'lib/commerce/api'
|
||||
import { getConfig } from '..'
|
||||
import log from '@lib/logger'
|
||||
|
||||
export default async function fetchGraphqlApi<Q, V = any>(
|
||||
const fetchGraphqlApi: GraphQLFetcher = async (
|
||||
query: string,
|
||||
{ variables, preview }: CommerceAPIFetchOptions<V> = {}
|
||||
): Promise<Q> {
|
||||
{ variables, preview } = {},
|
||||
fetchOptions
|
||||
) => {
|
||||
// log.warn(query)
|
||||
const config = getConfig()
|
||||
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
||||
...fetchOptions,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${config.apiToken}`,
|
||||
...fetchOptions?.headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
@ -20,22 +23,13 @@ export default async function fetchGraphqlApi<Q, V = any>(
|
||||
}),
|
||||
})
|
||||
|
||||
// console.log('HEADERS', getRawHeaders(res))
|
||||
|
||||
const json = await res.json()
|
||||
if (json.errors) {
|
||||
console.error(json.errors)
|
||||
throw new Error('Failed to fetch BigCommerce API')
|
||||
}
|
||||
return json.data
|
||||
|
||||
return { data: json.data, res }
|
||||
}
|
||||
|
||||
function getRawHeaders(res: Response) {
|
||||
const headers: { [key: string]: string } = {}
|
||||
|
||||
res.headers.forEach((value, key) => {
|
||||
headers[key] = value
|
||||
})
|
||||
|
||||
return headers
|
||||
}
|
||||
export default fetchGraphqlApi
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
CommerceProvider as CoreCommerceProvider,
|
||||
useCommerce as useCoreCommerce,
|
||||
} from 'lib/commerce'
|
||||
import { FetcherError } from '@lib/commerce/utils/errors'
|
||||
|
||||
async function getText(res: Response) {
|
||||
try {
|
||||
@ -16,9 +17,9 @@ async function getText(res: Response) {
|
||||
async function getError(res: Response) {
|
||||
if (res.headers.get('Content-Type')?.includes('application/json')) {
|
||||
const data = await res.json()
|
||||
return data.errors[0]
|
||||
return new FetcherError({ errors: data.errors, status: res.status })
|
||||
}
|
||||
return { message: await getText(res) }
|
||||
return new FetcherError({ message: await getText(res), status: res.status })
|
||||
}
|
||||
|
||||
export const bigcommerceConfig: CommerceConfig = {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useCallback } from 'react'
|
||||
import { HookFetcher } from '@lib/commerce/utils/types'
|
||||
import { CommerceError } from '@lib/commerce/utils/errors'
|
||||
import type { HookFetcher } from '@lib/commerce/utils/types'
|
||||
import useCommerceSignup from '@lib/commerce/use-signup'
|
||||
import type { SignupBody } from './api/customers/signup'
|
||||
|
||||
@ -16,9 +17,10 @@ export const fetcher: HookFetcher<null, SignupBody> = (
|
||||
fetch
|
||||
) => {
|
||||
if (!(firstName && lastName && email && password)) {
|
||||
throw new Error(
|
||||
'A first name, last name, email and password are required to signup'
|
||||
)
|
||||
throw new CommerceError({
|
||||
message:
|
||||
'A first name, last name, email and password are required to signup',
|
||||
})
|
||||
}
|
||||
|
||||
return fetch({
|
||||
|
@ -3,14 +3,29 @@ export interface CommerceAPIConfig {
|
||||
apiToken: string
|
||||
cartCookie: string
|
||||
cartCookieMaxAge: number
|
||||
fetch<Q, V = any>(
|
||||
fetch<Data = any, Variables = any>(
|
||||
query: string,
|
||||
queryData?: CommerceAPIFetchOptions<V>
|
||||
): Promise<Q>
|
||||
queryData?: CommerceAPIFetchOptions<Variables>,
|
||||
fetchOptions?: RequestInit
|
||||
): Promise<GraphQLFetcherResult<Data>>
|
||||
}
|
||||
|
||||
export interface CommerceAPIFetchOptions<V> {
|
||||
variables?: V
|
||||
export type GraphQLFetcher<
|
||||
Data extends GraphQLFetcherResult = GraphQLFetcherResult,
|
||||
Variables = any
|
||||
> = (
|
||||
query: string,
|
||||
queryData?: CommerceAPIFetchOptions<Variables>,
|
||||
fetchOptions?: RequestInit
|
||||
) => Promise<Data>
|
||||
|
||||
export interface GraphQLFetcherResult<Data = any> {
|
||||
data: Data
|
||||
res: Response
|
||||
}
|
||||
|
||||
export interface CommerceAPIFetchOptions<Variables> {
|
||||
variables?: Variables
|
||||
preview?: boolean
|
||||
}
|
||||
|
||||
|
40
lib/commerce/utils/errors.ts
Normal file
40
lib/commerce/utils/errors.ts
Normal file
@ -0,0 +1,40 @@
|
||||
export type ErrorData = {
|
||||
message: string
|
||||
code?: string
|
||||
}
|
||||
|
||||
export type ErrorProps = {
|
||||
code?: string
|
||||
} & (
|
||||
| { message: string; errors?: never }
|
||||
| { message?: never; errors: ErrorData[] }
|
||||
)
|
||||
|
||||
export class CommerceError extends Error {
|
||||
code?: string
|
||||
errors: ErrorData[]
|
||||
|
||||
constructor({ message, code, errors }: ErrorProps) {
|
||||
const error: ErrorData = message
|
||||
? { message, ...(code ? { code } : {}) }
|
||||
: errors![0]
|
||||
|
||||
super(error.message)
|
||||
this.errors = message ? [error] : errors!
|
||||
|
||||
if (error.code) this.code = error.code
|
||||
}
|
||||
}
|
||||
|
||||
export class FetcherError extends CommerceError {
|
||||
status: number
|
||||
|
||||
constructor(
|
||||
options: {
|
||||
status: number
|
||||
} & ErrorProps
|
||||
) {
|
||||
super(options)
|
||||
this.status = options.status
|
||||
}
|
||||
}
|
@ -1,7 +1,29 @@
|
||||
import useSignup from '@lib/bigcommerce/use-signup'
|
||||
import { Layout } from '@components/core'
|
||||
import { Logo, Modal, Button } from '@components/ui'
|
||||
|
||||
export default function Login() {
|
||||
const signup = useSignup()
|
||||
// TODO: use this method
|
||||
const handleSignup = async () => {
|
||||
// TODO: validate the password and email before calling the signup
|
||||
// Passwords must be at least 7 characters and contain both alphabetic
|
||||
// and numeric characters.
|
||||
try {
|
||||
await signup({
|
||||
// This account already exists, so it will throw the "duplicated_email" error
|
||||
email: 'luis@vercel.com',
|
||||
firstName: 'Luis',
|
||||
lastName: 'Alvarez',
|
||||
password: 'luis123',
|
||||
})
|
||||
} catch (error) {
|
||||
if (error.code === 'duplicated_email') {
|
||||
// TODO: handle duplicated email
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pb-20">
|
||||
<Modal close={() => {}}>
|
||||
@ -22,7 +44,9 @@ export default function Login() {
|
||||
className="focus:outline-none focus:shadow-outline-gray border-none py-2 px-6 w-full appearance-none transition duration-150 ease-in-out placeholder-accents-5 pr-10"
|
||||
/>
|
||||
</div>
|
||||
<Button variant="slim">Log In</Button>
|
||||
<Button variant="slim" onClick={handleSignup}>
|
||||
Log In
|
||||
</Button>
|
||||
<span className="pt-3 text-center text-sm">
|
||||
<span className="text-accents-7">Don't have an account?</span>
|
||||
{` `}
|
||||
|
Loading…
x
Reference in New Issue
Block a user