Improve error handling

This commit is contained in:
Michael Bromley 2021-01-25 21:24:26 +01:00
parent f08ade6c39
commit 5054f64fcf
8 changed files with 249 additions and 114 deletions

View File

@ -1,15 +1,21 @@
import type { ServerResponse } from 'http' import type { ServerResponse } from 'http'
import type { LoginMutation, LoginMutationVariables } from '../schema' import type { LoginMutation, LoginMutationVariables } from '../schema'
import type { RecursivePartial } from '../api/utils/types'
import concatHeader from '../api/utils/concat-cookie' import concatHeader from '../api/utils/concat-cookie'
import { VendureConfig, getConfig } from '../api' import { getConfig, VendureConfig } from '../api'
import { CommerceError } from '@commerce/utils/errors'
import { ErrorResult } from '../schema'
export const loginMutation = /* GraphQL */ ` export const loginMutation = /* GraphQL */ `
mutation loginServer($email: String!, $password: String!) { mutation loginServer($email: String!, $password: String!) {
login(username: $email, password: $password) { login(username: $email, password: $password) {
...on CurrentUser { __typename
... on CurrentUser {
id id
} }
... on ErrorResult {
errorCode
message
}
} }
} }
` `
@ -44,10 +50,11 @@ async function login({
}): Promise<LoginResult> { }): Promise<LoginResult> {
config = getConfig(config) config = getConfig(config)
const { data, res } = await config.fetch<RecursivePartial<LoginMutation>>( const { data, res } = await config.fetch<LoginMutation>(query, { variables })
query,
{ variables } if (data.login.__typename !== 'CurrentUser') {
) throw new CommerceError({ message: (data.login as ErrorResult).message })
}
// 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')
@ -68,7 +75,7 @@ async function login({
} }
return { return {
result: data.login?.result, result: data.login.id.toString(),
} }
} }

View File

@ -3,17 +3,25 @@ import type { HookFetcher } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors' import { CommerceError } from '@commerce/utils/errors'
import useCommerceLogin from '@commerce/use-login' import useCommerceLogin from '@commerce/use-login'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
import { LoginMutation, LoginMutationVariables } from '@framework/schema' import {
ErrorResult,
LoginMutation,
LoginMutationVariables,
} from '@framework/schema'
export const loginMutation = /* GraphQL */ ` export const loginMutation = /* GraphQL */ `
mutation login($username: String! $password: String!) { mutation login($username: String!, $password: String!) {
login(username: $username, password: $password) { login(username: $username, password: $password) {
__typename __typename
... on CurrentUser { ... on CurrentUser {
id id
} }
... on ErrorResult {
errorCode
message
} }
} }
}
` `
export const fetcher: HookFetcher<LoginMutation, LoginMutationVariables> = ( export const fetcher: HookFetcher<LoginMutation, LoginMutationVariables> = (
@ -23,28 +31,35 @@ export const fetcher: HookFetcher<LoginMutation, LoginMutationVariables> = (
) => { ) => {
if (!(username && password)) { if (!(username && password)) {
throw new CommerceError({ throw new CommerceError({
message: message: 'An email address and password are required to login',
'An email address and password are required to login'
}) })
} }
return fetch({ return fetch({
...options, ...options,
query: loginMutation, query: loginMutation,
variables: { username, password } variables: { username, password },
}) })
} }
export function extendHook(customFetcher: typeof fetcher) { export function extendHook(customFetcher: typeof fetcher) {
const useLogin = () => { const useLogin = () => {
const { revalidate } = useCustomer() const { revalidate } = useCustomer()
const fn = useCommerceLogin<LoginMutation, LoginMutationVariables>({}, customFetcher) const fn = useCommerceLogin<LoginMutation, LoginMutationVariables>(
{},
customFetcher
)
return useCallback( return useCallback(
async function login(input: { email: string; password: string; }) { async function login(input: { email: string; password: string }) {
const data = await fn({ username: input.email, password: input.password }) const data = await fn({
username: input.email,
password: input.password,
})
if (data.login.__typename !== 'CurrentUser') { if (data.login.__typename !== 'CurrentUser') {
throw new CommerceError({ message: 'The credentials are not valid' }); throw new CommerceError({
message: (data.login as ErrorResult).message,
})
} }
await revalidate() await revalidate()
return data return data

View File

@ -3,23 +3,32 @@ import type { HookFetcher } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors' import { CommerceError } from '@commerce/utils/errors'
import useCommerceSignup from '@commerce/use-signup' import useCommerceSignup from '@commerce/use-signup'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
import { SignupMutation, SignupMutationVariables } from '@framework/schema' import {
ErrorResult,
SignupMutation,
SignupMutationVariables,
} from '@framework/schema'
export const signupMutation = /* GraphQL */ ` export const signupMutation = /* GraphQL */ `
mutation signup($input: RegisterCustomerInput!) { mutation signup($input: RegisterCustomerInput!) {
registerCustomerAccount(input: $input) { registerCustomerAccount(input: $input) {
... on Success { __typename
success ... on Success {
} success
}
... on ErrorResult {
errorCode
message
} }
} }
`; }
`
export type SignupInput = { export type SignupInput = {
email: string; email: string
firstName: string; firstName: string
lastName: string; lastName: string
password: string; password: string
} }
export const fetcher: HookFetcher<SignupMutation, SignupMutationVariables> = ( export const fetcher: HookFetcher<SignupMutation, SignupMutationVariables> = (
@ -27,7 +36,7 @@ export const fetcher: HookFetcher<SignupMutation, SignupMutationVariables> = (
{ input }, { input },
fetch fetch
) => { ) => {
const { firstName, lastName, emailAddress, password } = input; const { firstName, lastName, emailAddress, password } = input
if (!(firstName && lastName && emailAddress && password)) { if (!(firstName && lastName && emailAddress && password)) {
throw new CommerceError({ throw new CommerceError({
message: message:
@ -45,20 +54,28 @@ export const fetcher: HookFetcher<SignupMutation, SignupMutationVariables> = (
export function extendHook(customFetcher: typeof fetcher) { export function extendHook(customFetcher: typeof fetcher) {
const useSignup = () => { const useSignup = () => {
const { revalidate } = useCustomer() const { revalidate } = useCustomer()
const fn = useCommerceSignup<SignupMutation, SignupMutationVariables>({}, customFetcher) const fn = useCommerceSignup<SignupMutation, SignupMutationVariables>(
{},
customFetcher
)
return useCallback( return useCallback(
async function signup(input: SignupInput) { async function signup(input: SignupInput) {
const data = await fn({ const { registerCustomerAccount } = await fn({
input: { input: {
firstName: input.firstName, firstName: input.firstName,
lastName: input.lastName, lastName: input.lastName,
emailAddress: input.email, emailAddress: input.email,
password: input.password, password: input.password,
} },
}) })
if (registerCustomerAccount.__typename !== 'Success') {
throw new CommerceError({
message: (registerCustomerAccount as ErrorResult).message,
})
}
await revalidate() await revalidate()
return data return { registerCustomerAccount }
}, },
[fn] [fn]
) )

View File

@ -5,28 +5,37 @@ import useCartAddItem from '@commerce/cart/use-add-item'
import useCart from './use-cart' import useCart from './use-cart'
import { useCallback } from 'react' import { useCallback } from 'react'
import { cartFragment } from '../api/fragments/cart' import { cartFragment } from '../api/fragments/cart'
import { AddItemToOrderMutation, AddItemToOrderMutationVariables } from '@framework/schema' import {
AddItemToOrderMutation,
AddItemToOrderMutationVariables,
ErrorResult,
} from '@framework/schema'
export const addItemToOrderMutation = /* GraphQL */ ` export const addItemToOrderMutation = /* GraphQL */ `
mutation addItemToOrder($variantId: ID!, $quantity: Int!) { mutation addItemToOrder($variantId: ID!, $quantity: Int!) {
addItemToOrder(productVariantId: $variantId, quantity: $quantity) { addItemToOrder(productVariantId: $variantId, quantity: $quantity) {
... Cart __typename
...Cart
... on ErrorResult {
errorCode
message
}
} }
} }
${cartFragment} ${cartFragment}
` `
export type AddItemInput = { productId?: number; variantId: number; quantity?: number; }; export type AddItemInput = {
productId?: number
variantId: number
quantity?: number
}
export const fetcher: HookFetcher<AddItemToOrderMutation, AddItemToOrderMutationVariables> = ( export const fetcher: HookFetcher<
options, AddItemToOrderMutation,
{ variantId, quantity }, AddItemToOrderMutationVariables
fetch > = (options, { variantId, quantity }, fetch) => {
) => { if (quantity && (!Number.isInteger(quantity) || quantity! < 1)) {
if (
quantity &&
(!Number.isInteger(quantity) || quantity! < 1)
) {
throw new CommerceError({ throw new CommerceError({
message: 'The item quantity has to be a valid integer greater than 0', message: 'The item quantity has to be a valid integer greater than 0',
}) })
@ -36,9 +45,6 @@ export const fetcher: HookFetcher<AddItemToOrderMutation, AddItemToOrderMutation
...options, ...options,
query: addItemToOrderMutation, query: addItemToOrderMutation,
variables: { variantId, quantity: quantity || 1 }, variables: { variantId, quantity: quantity || 1 },
}).then(res => {
console.log({ res });
return res;
}) })
} }
@ -49,9 +55,18 @@ export function extendHook(customFetcher: typeof fetcher) {
return useCallback( return useCallback(
async function addItem(input: AddItemInput) { async function addItem(input: AddItemInput) {
const data = await fn({ quantity: input.quantity || 1, variantId: input.variantId }) const { addItemToOrder } = await fn({
await mutate(data, false) quantity: input.quantity || 1,
return data variantId: input.variantId,
})
if (addItemToOrder.__typename === 'Order') {
await mutate({ addItemToOrder }, false)
} else {
throw new CommerceError({
message: (addItemToOrder as ErrorResult).message,
})
}
return { addItemToOrder }
}, },
[fn, mutate] [fn, mutate]
) )

View File

@ -14,19 +14,15 @@ export const getCartQuery = /* GraphQL */ `
${cartFragment} ${cartFragment}
` `
export const fetcher: HookFetcher<any, null> = ( export const fetcher: HookFetcher<any, null> = (options, input, fetch) => {
options,
input,
fetch
) => {
return fetch({ ...options, query: getCartQuery }) return fetch({ ...options, query: getCartQuery })
} }
export type CartResult = { export type CartResult = {
activeOrder?: CartFragment; activeOrder?: CartFragment
addItemToOrder?: CartFragment; addItemToOrder?: CartFragment
adjustOrderLine?: CartFragment; adjustOrderLine?: CartFragment
removeOrderLine?: CartFragment; removeOrderLine?: CartFragment
} }
export function extendHook( export function extendHook(
@ -34,20 +30,29 @@ export function extendHook(
swrOptions?: SwrOptions<any | null> swrOptions?: SwrOptions<any | null>
) { ) {
const useCart = () => { const useCart = () => {
const response = useData<Cart>({ query: getCartQuery }, [], customFetcher, swrOptions) const response = useData<CartResult>(
{ query: getCartQuery },
[],
customFetcher,
swrOptions
)
const res = useResponse(response, { const res = useResponse(response, {
normalizer: (data => { normalizer: (data) => {
const order = data?.activeOrder || data?.addItemToOrder || data?.adjustOrderLine || data?.removeOrderLine; const order =
return (order ? normalizeCart(order) : null) data?.activeOrder ||
}), data?.addItemToOrder ||
data?.adjustOrderLine ||
data?.removeOrderLine
return order ? normalizeCart(order) : null
},
descriptors: { descriptors: {
isEmpty: { isEmpty: {
get() { get() {
return response.data?.activeOrder?.totalQuantity === 0 return response.data?.activeOrder?.totalQuantity === 0
}, },
enumerable: true enumerable: true,
} },
} },
}) })
return res return res

View File

@ -3,23 +3,31 @@ import { HookFetcher } from '@commerce/utils/types'
import useCartRemoveItem from '@commerce/cart/use-remove-item' import useCartRemoveItem from '@commerce/cart/use-remove-item'
import useCart from './use-cart' import useCart from './use-cart'
import { cartFragment } from '@framework/api/fragments/cart' import { cartFragment } from '@framework/api/fragments/cart'
import { RemoveOrderLineMutation, RemoveOrderLineMutationVariables } from '@framework/schema' import {
ErrorResult,
RemoveOrderLineMutation,
RemoveOrderLineMutationVariables,
} from '@framework/schema'
import { CommerceError } from '@commerce/utils/errors'
export const removeOrderLineMutation = /* GraphQL */ ` export const removeOrderLineMutation = /* GraphQL */ `
mutation removeOrderLine($orderLineId: ID!) { mutation removeOrderLine($orderLineId: ID!) {
removeOrderLine(orderLineId: $orderLineId) { removeOrderLine(orderLineId: $orderLineId) {
__typename __typename
...Cart ...Cart
... on ErrorResult {
errorCode
message
}
} }
} }
${cartFragment} ${cartFragment}
` `
export const fetcher: HookFetcher<RemoveOrderLineMutation, RemoveOrderLineMutationVariables> = ( export const fetcher: HookFetcher<
options, RemoveOrderLineMutation,
{ orderLineId }, RemoveOrderLineMutationVariables
fetch > = (options, { orderLineId }, fetch) => {
) => {
return fetch({ return fetch({
...options, ...options,
query: removeOrderLineMutation, query: removeOrderLineMutation,
@ -30,16 +38,20 @@ export const fetcher: HookFetcher<RemoveOrderLineMutation, RemoveOrderLineMutati
export function extendHook(customFetcher: typeof fetcher) { export function extendHook(customFetcher: typeof fetcher) {
const useRemoveItem = (item?: any) => { const useRemoveItem = (item?: any) => {
const { mutate } = useCart() const { mutate } = useCart()
const fn = useCartRemoveItem<RemoveOrderLineMutation, RemoveOrderLineMutationVariables>( const fn = useCartRemoveItem<
{}, RemoveOrderLineMutation,
customFetcher RemoveOrderLineMutationVariables
) >({}, customFetcher)
return useCallback( return useCallback(
async function removeItem(input: any) { async function removeItem(input: any) {
const { removeOrderLine } = await fn({ orderLineId: input.id }) const { removeOrderLine } = await fn({ orderLineId: input.id })
if (removeOrderLine.__typename === 'Order') { if (removeOrderLine.__typename === 'Order') {
await mutate({ removeOrderLine }, false) await mutate({ removeOrderLine }, false)
} else {
throw new CommerceError({
message: (removeOrderLine as ErrorResult).message,
})
} }
return { removeOrderLine } return { removeOrderLine }
}, },

View File

@ -4,21 +4,30 @@ import type { HookFetcher } from '@commerce/utils/types'
import useCartUpdateItem from '@commerce/cart/use-update-item' import useCartUpdateItem from '@commerce/cart/use-update-item'
import useCart from './use-cart' import useCart from './use-cart'
import { cartFragment } from '@framework/api/fragments/cart' import { cartFragment } from '@framework/api/fragments/cart'
import { AdjustOrderLineMutation, AdjustOrderLineMutationVariables } from '@framework/schema' import {
AdjustOrderLineMutation,
AdjustOrderLineMutationVariables,
ErrorResult,
} from '@framework/schema'
import { CommerceError } from '@commerce/utils/errors'
export const adjustOrderLineMutation = /* GraphQL */ ` export const adjustOrderLineMutation = /* GraphQL */ `
mutation adjustOrderLine($orderLineId: ID!, $quantity: Int!) { mutation adjustOrderLine($orderLineId: ID!, $quantity: Int!) {
adjustOrderLine(orderLineId: $orderLineId, quantity: $quantity) { adjustOrderLine(orderLineId: $orderLineId, quantity: $quantity) {
__typename
...Cart ...Cart
... on ErrorResult {
errorCode
message
}
} }
} }
${cartFragment} ${cartFragment}
` `
export const fetcher: HookFetcher<AdjustOrderLineMutation, AdjustOrderLineMutationVariables> = ( export const fetcher: HookFetcher<
options, AdjustOrderLineMutation,
{ orderLineId, quantity }, AdjustOrderLineMutationVariables
fetch > = (options, { orderLineId, quantity }, fetch) => {
) => {
return fetch({ return fetch({
...options, ...options,
query: adjustOrderLineMutation, query: adjustOrderLineMutation,
@ -29,10 +38,10 @@ export const fetcher: HookFetcher<AdjustOrderLineMutation, AdjustOrderLineMutati
function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) { function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) {
const useUpdateItem = (item?: any) => { const useUpdateItem = (item?: any) => {
const { mutate } = useCart() const { mutate } = useCart()
const fn = useCartUpdateItem<AdjustOrderLineMutation, AdjustOrderLineMutationVariables>( const fn = useCartUpdateItem<
{}, AdjustOrderLineMutation,
customFetcher AdjustOrderLineMutationVariables
) >({}, customFetcher)
return useCallback( return useCallback(
debounce(async (input: any) => { debounce(async (input: any) => {
@ -42,6 +51,10 @@ function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) {
}) })
if (adjustOrderLine.__typename === 'Order') { if (adjustOrderLine.__typename === 'Order') {
await mutate({ adjustOrderLine }, false) await mutate({ adjustOrderLine }, false)
} else {
throw new CommerceError({
message: (adjustOrderLine as ErrorResult).message,
})
} }
return { adjustOrderLine } return { adjustOrderLine }
}, cfg?.wait ?? 500), }, cfg?.wait ?? 500),

View File

@ -2843,10 +2843,19 @@ export type LoginServerMutationVariables = Exact<{
export type LoginServerMutation = { __typename?: 'Mutation' } & { export type LoginServerMutation = { __typename?: 'Mutation' } & {
login: login:
| ({ __typename?: 'CurrentUser' } & Pick<CurrentUser, 'id'>) | ({ __typename: 'CurrentUser' } & Pick<CurrentUser, 'id'>)
| { __typename?: 'InvalidCredentialsError' } | ({ __typename: 'InvalidCredentialsError' } & Pick<
| { __typename?: 'NotVerifiedError' } InvalidCredentialsError,
| { __typename?: 'NativeAuthStrategyError' } 'errorCode' | 'message'
>)
| ({ __typename: 'NotVerifiedError' } & Pick<
NotVerifiedError,
'errorCode' | 'message'
>)
| ({ __typename: 'NativeAuthStrategyError' } & Pick<
NativeAuthStrategyError,
'errorCode' | 'message'
>)
} }
export type LoginMutationVariables = Exact<{ export type LoginMutationVariables = Exact<{
@ -2857,9 +2866,18 @@ export type LoginMutationVariables = Exact<{
export type LoginMutation = { __typename?: 'Mutation' } & { export type LoginMutation = { __typename?: 'Mutation' } & {
login: login:
| ({ __typename: 'CurrentUser' } & Pick<CurrentUser, 'id'>) | ({ __typename: 'CurrentUser' } & Pick<CurrentUser, 'id'>)
| { __typename: 'InvalidCredentialsError' } | ({ __typename: 'InvalidCredentialsError' } & Pick<
| { __typename: 'NotVerifiedError' } InvalidCredentialsError,
| { __typename: 'NativeAuthStrategyError' } 'errorCode' | 'message'
>)
| ({ __typename: 'NotVerifiedError' } & Pick<
NotVerifiedError,
'errorCode' | 'message'
>)
| ({ __typename: 'NativeAuthStrategyError' } & Pick<
NativeAuthStrategyError,
'errorCode' | 'message'
>)
} }
export type LogoutMutationVariables = Exact<{ [key: string]: never }> export type LogoutMutationVariables = Exact<{ [key: string]: never }>
@ -2874,9 +2892,15 @@ export type SignupMutationVariables = Exact<{
export type SignupMutation = { __typename?: 'Mutation' } & { export type SignupMutation = { __typename?: 'Mutation' } & {
registerCustomerAccount: registerCustomerAccount:
| ({ __typename?: 'Success' } & Pick<Success, 'success'>) | ({ __typename: 'Success' } & Pick<Success, 'success'>)
| { __typename?: 'MissingPasswordError' } | ({ __typename: 'MissingPasswordError' } & Pick<
| { __typename?: 'NativeAuthStrategyError' } MissingPasswordError,
'errorCode' | 'message'
>)
| ({ __typename: 'NativeAuthStrategyError' } & Pick<
NativeAuthStrategyError,
'errorCode' | 'message'
>)
} }
export type AddItemToOrderMutationVariables = Exact<{ export type AddItemToOrderMutationVariables = Exact<{
@ -2886,11 +2910,23 @@ export type AddItemToOrderMutationVariables = Exact<{
export type AddItemToOrderMutation = { __typename?: 'Mutation' } & { export type AddItemToOrderMutation = { __typename?: 'Mutation' } & {
addItemToOrder: addItemToOrder:
| ({ __typename?: 'Order' } & CartFragment) | ({ __typename: 'Order' } & CartFragment)
| { __typename?: 'OrderModificationError' } | ({ __typename: 'OrderModificationError' } & Pick<
| { __typename?: 'OrderLimitError' } OrderModificationError,
| { __typename?: 'NegativeQuantityError' } 'errorCode' | 'message'
| { __typename?: 'InsufficientStockError' } >)
| ({ __typename: 'OrderLimitError' } & Pick<
OrderLimitError,
'errorCode' | 'message'
>)
| ({ __typename: 'NegativeQuantityError' } & Pick<
NegativeQuantityError,
'errorCode' | 'message'
>)
| ({ __typename: 'InsufficientStockError' } & Pick<
InsufficientStockError,
'errorCode' | 'message'
>)
} }
export type ActiveOrderQueryVariables = Exact<{ [key: string]: never }> export type ActiveOrderQueryVariables = Exact<{ [key: string]: never }>
@ -2906,7 +2942,10 @@ export type RemoveOrderLineMutationVariables = Exact<{
export type RemoveOrderLineMutation = { __typename?: 'Mutation' } & { export type RemoveOrderLineMutation = { __typename?: 'Mutation' } & {
removeOrderLine: removeOrderLine:
| ({ __typename: 'Order' } & CartFragment) | ({ __typename: 'Order' } & CartFragment)
| { __typename: 'OrderModificationError' } | ({ __typename: 'OrderModificationError' } & Pick<
OrderModificationError,
'errorCode' | 'message'
>)
} }
export type AdjustOrderLineMutationVariables = Exact<{ export type AdjustOrderLineMutationVariables = Exact<{
@ -2916,11 +2955,23 @@ export type AdjustOrderLineMutationVariables = Exact<{
export type AdjustOrderLineMutation = { __typename?: 'Mutation' } & { export type AdjustOrderLineMutation = { __typename?: 'Mutation' } & {
adjustOrderLine: adjustOrderLine:
| ({ __typename?: 'Order' } & CartFragment) | ({ __typename: 'Order' } & CartFragment)
| { __typename?: 'OrderModificationError' } | ({ __typename: 'OrderModificationError' } & Pick<
| { __typename?: 'OrderLimitError' } OrderModificationError,
| { __typename?: 'NegativeQuantityError' } 'errorCode' | 'message'
| { __typename?: 'InsufficientStockError' } >)
| ({ __typename: 'OrderLimitError' } & Pick<
OrderLimitError,
'errorCode' | 'message'
>)
| ({ __typename: 'NegativeQuantityError' } & Pick<
NegativeQuantityError,
'errorCode' | 'message'
>)
| ({ __typename: 'InsufficientStockError' } & Pick<
InsufficientStockError,
'errorCode' | 'message'
>)
} }
export type GetCollectionsQueryVariables = Exact<{ [key: string]: never }> export type GetCollectionsQueryVariables = Exact<{ [key: string]: never }>