4
0
forked from crowetic/commerce

start customer auth & signup

This commit is contained in:
cond0r 2021-02-05 00:02:39 +02:00
parent 2a29218285
commit 57ebfe42f5
12 changed files with 173 additions and 90 deletions

View File

@ -8,7 +8,6 @@ const fetchGraphqlApi: GraphQLFetcher = async (
{ variables, preview } = {},
fetchOptions
) => {
// log.warn(query)
const config = getConfig()
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
...fetchOptions,

View File

@ -2,17 +2,15 @@ import { useCallback } from 'react'
import type { HookFetcher } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
import useCommerceSignup from '@commerce/use-signup'
import type { SignupBody } from '../api/customers/signup'
import useCustomer from '../customer/use-customer'
import customerCreateMutation from '@framework/utils/mutations/customer-create'
import { CustomerCreateInput } from '@framework/schema'
const defaultOpts = {
url: '/api/bigcommerce/customers/signup',
method: 'POST',
query: customerCreateMutation,
}
export type SignupInput = SignupBody
export const fetcher: HookFetcher<null, SignupBody> = (
export const fetcher: HookFetcher<null, CustomerCreateInput> = (
options,
{ firstName, lastName, email, password },
fetch
@ -27,17 +25,20 @@ export const fetcher: HookFetcher<null, SignupBody> = (
return fetch({
...defaultOpts,
...options,
body: { firstName, lastName, email, password },
variables: { firstName, lastName, email, password },
})
}
export function extendHook(customFetcher: typeof fetcher) {
const useSignup = () => {
const { revalidate } = useCustomer()
const fn = useCommerceSignup<null, SignupInput>(defaultOpts, customFetcher)
const fn = useCommerceSignup<null, CustomerCreateInput>(
defaultOpts,
customFetcher
)
return useCallback(
async function signup(input: SignupInput) {
async function signup(input: CustomerCreateInput) {
const data = await fn(input)
await revalidate()
return data

View File

@ -1,9 +1,9 @@
import type { CommerceAPIConfig } from '@commerce/api'
import { SHOPIFY_CHECKOUT_ID_COOKIE } from '@framework/const'
import fetchGraphqlApi from '../utils/fetch-graphql-api'
export interface ShopifyConfig extends CommerceAPIConfig {}
// No I don't like this - will fix it later
const API_URL = process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN
const API_TOKEN = process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN
@ -38,9 +38,13 @@ export class Config {
}
}
const ONE_DAY = 60 * 60 * 24
const config = new Config({
commerceUrl: API_URL,
apiToken: API_TOKEN,
cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE,
cartCookieMaxAge: ONE_DAY * 30,
fetch: fetchGraphqlApi,
customerCookie: 'SHOP_TOKEN',
})

View File

@ -1,13 +1,55 @@
import { useCallback } from 'react'
import type { HookFetcher } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
import useCommerceLogin from '@commerce/use-login'
import useCustomer from '../customer/use-customer'
import createCustomerAccessTokenMutation from '../utils/mutations/customer-acces-token-create'
import { CustomerAccessTokenCreateInput } from '@framework/schema'
export function emptyHook() {
const useEmptyHook = async (options = {}) => {
return useCallback(async function () {
return Promise.resolve()
}, [])
}
return useEmptyHook
const defaultOpts = {
query: createCustomerAccessTokenMutation,
}
export default emptyHook
export const fetcher: HookFetcher<null, CustomerAccessTokenCreateInput> = (
options,
{ email, password },
fetch
) => {
if (!(email && password)) {
throw new CommerceError({
message:
'A first name, last name, email and password are required to login',
})
}
return fetch({
...defaultOpts,
...options,
body: { email, password },
})
}
export function extendHook(customFetcher: typeof fetcher) {
const useLogin = () => {
const { revalidate } = useCustomer()
const fn = useCommerceLogin<null, CustomerAccessTokenCreateInput>(
defaultOpts,
customFetcher
)
return useCallback(
async function login(input: CustomerAccessTokenCreateInput) {
const data = await fn(input)
await revalidate()
return data
},
[fn]
)
}
useLogin.extend = extendHook
return useLogin
}
export default extendHook(fetcher)

View File

@ -7,6 +7,7 @@ import getCheckoutQuery from '@framework/utils/queries/get-checkout-query'
import { Cart } from '@commerce/types'
import { checkoutToCart, checkoutCreate } from './utils'
import { getConfig } from '@framework/api'
const defaultOpts = {
query: getCheckoutQuery,

View File

@ -0,0 +1,44 @@
import { CommerceError, FetcherError } from '@commerce/utils/errors'
import { SHOPIFY_CHECKOUT_ID_COOKIE } from './const'
import type { CommerceConfig } from '@commerce'
export type ShopifyConfig = {
locale: string
cartCookie: string
storeDomain: string | undefined
} & CommerceConfig
export const STORE_DOMAIN = process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN
export const API_URL = `https://${STORE_DOMAIN}/api/2021-01/graphql.json`
export const API_TOKEN = process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN
const shopifyConfig: ShopifyConfig = {
locale: 'en-us',
cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE,
storeDomain: STORE_DOMAIN,
async fetcher({ method = 'POST', variables, query }) {
const res = await fetch(API_URL, {
method,
body: JSON.stringify({ query, variables }),
headers: {
'X-Shopify-Storefront-Access-Token': API_TOKEN!,
'Content-Type': 'application/json',
},
})
const json = await res.json()
if (json.errors) {
throw new FetcherError({
errors: json.errors ?? [
{ message: 'Failed to fetch Shopify Storefront API' },
],
status: res.status,
})
}
return { data: json.data, res }
},
}
export default shopifyConfig

View File

@ -2,18 +2,17 @@ import type { HookFetcher } from '@commerce/utils/types'
import type { SwrOptions } from '@commerce/utils/use-data'
import useCommerceCustomer from '@commerce/use-customer'
const defaultOpts = {}
export type Customer = {
entityId: number
firstName: string
lastName: string
email: string
const defaultOpts = {
query: '/api/bigcommerce/customers',
}
export type CustomerData = {}
export const fetcher: HookFetcher<Customer | null> = async () => {
return null
export const fetcher: HookFetcher<Customer | null> = async (
options,
_,
fetch
) => {
const data = await fetch<CustomerData | null>({ ...defaultOpts, ...options })
return data?.customer ?? null
}
export function extendHook(
@ -21,7 +20,10 @@ export function extendHook(
swrOptions?: SwrOptions<Customer | null>
) {
const useCustomer = () => {
return { data: { firstName: null, lastName: null, email: null } }
return useCommerceCustomer(defaultOpts, [], customFetcher, {
revalidateOnFocus: false,
...swrOptions,
})
}
useCustomer.extend = extendHook

View File

@ -2,64 +2,11 @@ import { ReactNode } from 'react'
import * as React from 'react'
import {
CommerceConfig,
CommerceProvider as CoreCommerceProvider,
useCommerce as useCoreCommerce,
} from '@commerce'
import { CommerceError, FetcherError } from '@commerce/utils/errors'
import { SHOPIFY_CHECKOUT_ID_COOKIE } from './const'
async function getText(res: Response) {
try {
return (await res.text()) || res.statusText
} catch (error) {
return res.statusText
}
}
async function getError(res: Response) {
if (res.headers.get('Content-Type')?.includes('application/json')) {
const data = await res.json()
return new FetcherError({ errors: data.errors, status: res.status })
}
return new FetcherError({ message: await getText(res), status: res.status })
}
export type ShopifyConfig = Partial<CommerceConfig>
export const shopifyConfig: ShopifyConfig = {
locale: 'en-us',
cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE,
async fetcher({ method = 'POST', variables, query }) {
const res = await fetch(
`https://${process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN}/api/2021-01/graphql.json`,
{
method,
body: JSON.stringify({ query, variables }),
headers: {
'X-Shopify-Storefront-Access-Token':
process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN,
'Content-Type': 'application/json',
},
}
)
if (res.ok) {
const { data, errors } = await res.json()
if (errors && errors.length) {
throw new CommerceError({
message: errors[0].message,
})
}
return data
}
throw await getError(res)
},
}
import shopifyConfig, { ShopifyConfig } from './config'
export type ShopifyProps = {
children?: ReactNode

View File

@ -1,20 +1,31 @@
import type { GraphQLFetcher } from '@commerce/api'
import { FetcherError } from '@commerce/utils/errors'
import { getConfig } from '../api/index'
import fetch from './fetch'
import { STORE_DOMAIN, API_URL, API_TOKEN } from '../config'
if (!STORE_DOMAIN) {
throw new Error(
`The environment variable NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN is missing and it's required to access your store`
)
}
if (!API_TOKEN) {
throw new Error(
`The environment variable NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN is missing and it's required to access your store`
)
}
const fetchGraphqlApi: GraphQLFetcher = async (
query: string,
{ variables } = {},
fetchOptions
) => {
const { commerceUrl, apiToken } = getConfig()
const res = await fetch(`https://${commerceUrl}/api/2021-01/graphql.json`, {
const res = await fetch(API_URL, {
...fetchOptions,
method: 'POST',
headers: {
'X-Shopify-Storefront-Access-Token': apiToken,
'X-Shopify-Storefront-Access-Token': API_TOKEN!,
...fetchOptions?.headers,
'Content-Type': 'application/json',
},

View File

@ -0,0 +1,16 @@
const customerAccessTokenCreateMutation = /* GraphQL */ `
mutation customerAccessTokenCreate($input: CustomerAccessTokenCreateInput!) {
customerAccessTokenCreate(input: $input) {
customerAccessToken {
accessToken
expiresAt
}
customerUserErrors {
code
field
message
}
}
}
`
export default customerAccessTokenCreateMutation

View File

@ -0,0 +1,15 @@
const customerCreateMutation = /* GraphQL */ `
mutation customerCreate($input: CustomerCreateInput!) {
customerCreate(input: $input) {
customerUserErrors {
code
field
message
}
customer {
id
}
}
}
`
export default customerCreateMutation

View File

@ -1,3 +1,4 @@
export { default as createCustomerMutation } from './customer-create'
export { default as checkoutCreateMutation } from './checkout-create'
export { default as checkoutLineItemAddMutation } from './checkout-line-item-add'
export { default as checkoutLineItemUpdateMutation } from './checkout-create'