From 5054f64fcf51085c6cdddb8e490e301c8f314148 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Mon, 25 Jan 2021 21:24:26 +0100 Subject: [PATCH] Improve error handling --- framework/vendure/auth/login.ts | 23 ++++-- framework/vendure/auth/use-login.tsx | 43 ++++++---- framework/vendure/auth/use-signup.tsx | 49 ++++++++---- framework/vendure/cart/use-add-item.tsx | 51 +++++++----- framework/vendure/cart/use-cart.tsx | 39 +++++---- framework/vendure/cart/use-remove-item.tsx | 32 +++++--- framework/vendure/cart/use-update-item.tsx | 33 +++++--- framework/vendure/schema.d.ts | 93 +++++++++++++++++----- 8 files changed, 249 insertions(+), 114 deletions(-) diff --git a/framework/vendure/auth/login.ts b/framework/vendure/auth/login.ts index 26b9a66c1..15ef00eb5 100644 --- a/framework/vendure/auth/login.ts +++ b/framework/vendure/auth/login.ts @@ -1,15 +1,21 @@ import type { ServerResponse } from 'http' import type { LoginMutation, LoginMutationVariables } from '../schema' -import type { RecursivePartial } from '../api/utils/types' 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 */ ` mutation loginServer($email: String!, $password: String!) { login(username: $email, password: $password) { - ...on CurrentUser { + __typename + ... on CurrentUser { id } + ... on ErrorResult { + errorCode + message + } } } ` @@ -44,10 +50,11 @@ async function login({ }): Promise { config = getConfig(config) - const { data, res } = await config.fetch>( - query, - { variables } - ) + const { data, res } = await config.fetch(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 let cookie = res.headers.get('Set-Cookie') @@ -68,7 +75,7 @@ async function login({ } return { - result: data.login?.result, + result: data.login.id.toString(), } } diff --git a/framework/vendure/auth/use-login.tsx b/framework/vendure/auth/use-login.tsx index 95be6103f..94c756f8f 100644 --- a/framework/vendure/auth/use-login.tsx +++ b/framework/vendure/auth/use-login.tsx @@ -3,17 +3,25 @@ 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 { LoginMutation, LoginMutationVariables } from '@framework/schema' +import { + ErrorResult, + LoginMutation, + LoginMutationVariables, +} from '@framework/schema' export const loginMutation = /* GraphQL */ ` - mutation login($username: String! $password: String!) { - login(username: $username, password: $password) { - __typename - ... on CurrentUser { - id - } + mutation login($username: String!, $password: String!) { + login(username: $username, password: $password) { + __typename + ... on CurrentUser { + id + } + ... on ErrorResult { + errorCode + message } } + } ` export const fetcher: HookFetcher = ( @@ -23,28 +31,35 @@ export const fetcher: HookFetcher = ( ) => { if (!(username && password)) { throw new CommerceError({ - message: - 'An email address and password are required to login' + message: 'An email address and password are required to login', }) } return fetch({ ...options, query: loginMutation, - variables: { username, password } + variables: { username, password }, }) } export function extendHook(customFetcher: typeof fetcher) { const useLogin = () => { const { revalidate } = useCustomer() - const fn = useCommerceLogin({}, customFetcher) + const fn = useCommerceLogin( + {}, + customFetcher + ) return useCallback( - async function login(input: { email: string; password: string; }) { - const data = await fn({ username: input.email, password: input.password }) + async function login(input: { email: string; password: string }) { + const data = await fn({ + username: input.email, + password: input.password, + }) 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() return data diff --git a/framework/vendure/auth/use-signup.tsx b/framework/vendure/auth/use-signup.tsx index 54686b925..c31ba3e9b 100644 --- a/framework/vendure/auth/use-signup.tsx +++ b/framework/vendure/auth/use-signup.tsx @@ -3,23 +3,32 @@ import type { HookFetcher } from '@commerce/utils/types' import { CommerceError } from '@commerce/utils/errors' import useCommerceSignup from '@commerce/use-signup' import useCustomer from '../customer/use-customer' -import { SignupMutation, SignupMutationVariables } from '@framework/schema' +import { + ErrorResult, + SignupMutation, + SignupMutationVariables, +} from '@framework/schema' export const signupMutation = /* GraphQL */ ` - mutation signup($input: RegisterCustomerInput!) { - registerCustomerAccount(input: $input) { - ... on Success { - success - } + mutation signup($input: RegisterCustomerInput!) { + registerCustomerAccount(input: $input) { + __typename + ... on Success { + success + } + ... on ErrorResult { + errorCode + message } } -`; + } +` export type SignupInput = { - email: string; - firstName: string; - lastName: string; - password: string; + email: string + firstName: string + lastName: string + password: string } export const fetcher: HookFetcher = ( @@ -27,7 +36,7 @@ export const fetcher: HookFetcher = ( { input }, fetch ) => { - const { firstName, lastName, emailAddress, password } = input; + const { firstName, lastName, emailAddress, password } = input if (!(firstName && lastName && emailAddress && password)) { throw new CommerceError({ message: @@ -45,20 +54,28 @@ export const fetcher: HookFetcher = ( export function extendHook(customFetcher: typeof fetcher) { const useSignup = () => { const { revalidate } = useCustomer() - const fn = useCommerceSignup({}, customFetcher) + const fn = useCommerceSignup( + {}, + customFetcher + ) return useCallback( async function signup(input: SignupInput) { - const data = await fn({ + const { registerCustomerAccount } = await fn({ input: { firstName: input.firstName, lastName: input.lastName, emailAddress: input.email, password: input.password, - } + }, }) + if (registerCustomerAccount.__typename !== 'Success') { + throw new CommerceError({ + message: (registerCustomerAccount as ErrorResult).message, + }) + } await revalidate() - return data + return { registerCustomerAccount } }, [fn] ) diff --git a/framework/vendure/cart/use-add-item.tsx b/framework/vendure/cart/use-add-item.tsx index 3ca93020f..a94ab518c 100644 --- a/framework/vendure/cart/use-add-item.tsx +++ b/framework/vendure/cart/use-add-item.tsx @@ -5,28 +5,37 @@ import useCartAddItem from '@commerce/cart/use-add-item' import useCart from './use-cart' import { useCallback } from 'react' import { cartFragment } from '../api/fragments/cart' -import { AddItemToOrderMutation, AddItemToOrderMutationVariables } from '@framework/schema' +import { + AddItemToOrderMutation, + AddItemToOrderMutationVariables, + ErrorResult, +} from '@framework/schema' export const addItemToOrderMutation = /* GraphQL */ ` mutation addItemToOrder($variantId: ID!, $quantity: Int!) { addItemToOrder(productVariantId: $variantId, quantity: $quantity) { - ... Cart + __typename + ...Cart + ... on ErrorResult { + errorCode + message + } } } ${cartFragment} ` -export type AddItemInput = { productId?: number; variantId: number; quantity?: number; }; +export type AddItemInput = { + productId?: number + variantId: number + quantity?: number +} -export const fetcher: HookFetcher = ( - options, - { variantId, quantity }, - fetch -) => { - if ( - quantity && - (!Number.isInteger(quantity) || quantity! < 1) - ) { +export const fetcher: HookFetcher< + AddItemToOrderMutation, + AddItemToOrderMutationVariables +> = (options, { variantId, quantity }, fetch) => { + if (quantity && (!Number.isInteger(quantity) || quantity! < 1)) { throw new CommerceError({ message: 'The item quantity has to be a valid integer greater than 0', }) @@ -36,9 +45,6 @@ export const fetcher: HookFetcher { - console.log({ res }); - return res; }) } @@ -49,9 +55,18 @@ export function extendHook(customFetcher: typeof fetcher) { return useCallback( async function addItem(input: AddItemInput) { - const data = await fn({ quantity: input.quantity || 1, variantId: input.variantId }) - await mutate(data, false) - return data + const { addItemToOrder } = await fn({ + quantity: input.quantity || 1, + variantId: input.variantId, + }) + if (addItemToOrder.__typename === 'Order') { + await mutate({ addItemToOrder }, false) + } else { + throw new CommerceError({ + message: (addItemToOrder as ErrorResult).message, + }) + } + return { addItemToOrder } }, [fn, mutate] ) diff --git a/framework/vendure/cart/use-cart.tsx b/framework/vendure/cart/use-cart.tsx index ade9b6a91..98c49f924 100644 --- a/framework/vendure/cart/use-cart.tsx +++ b/framework/vendure/cart/use-cart.tsx @@ -14,19 +14,15 @@ export const getCartQuery = /* GraphQL */ ` ${cartFragment} ` -export const fetcher: HookFetcher = ( - options, - input, - fetch -) => { +export const fetcher: HookFetcher = (options, input, fetch) => { return fetch({ ...options, query: getCartQuery }) } export type CartResult = { - activeOrder?: CartFragment; - addItemToOrder?: CartFragment; - adjustOrderLine?: CartFragment; - removeOrderLine?: CartFragment; + activeOrder?: CartFragment + addItemToOrder?: CartFragment + adjustOrderLine?: CartFragment + removeOrderLine?: CartFragment } export function extendHook( @@ -34,20 +30,29 @@ export function extendHook( swrOptions?: SwrOptions ) { const useCart = () => { - const response = useData({ query: getCartQuery }, [], customFetcher, swrOptions) + const response = useData( + { query: getCartQuery }, + [], + customFetcher, + swrOptions + ) const res = useResponse(response, { - normalizer: (data => { - const order = data?.activeOrder || data?.addItemToOrder || data?.adjustOrderLine || data?.removeOrderLine; - return (order ? normalizeCart(order) : null) - }), + normalizer: (data) => { + const order = + data?.activeOrder || + data?.addItemToOrder || + data?.adjustOrderLine || + data?.removeOrderLine + return order ? normalizeCart(order) : null + }, descriptors: { isEmpty: { get() { return response.data?.activeOrder?.totalQuantity === 0 }, - enumerable: true - } - } + enumerable: true, + }, + }, }) return res diff --git a/framework/vendure/cart/use-remove-item.tsx b/framework/vendure/cart/use-remove-item.tsx index 0ddff2077..d243099ef 100644 --- a/framework/vendure/cart/use-remove-item.tsx +++ b/framework/vendure/cart/use-remove-item.tsx @@ -3,23 +3,31 @@ import { HookFetcher } from '@commerce/utils/types' import useCartRemoveItem from '@commerce/cart/use-remove-item' import useCart from './use-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 */ ` mutation removeOrderLine($orderLineId: ID!) { removeOrderLine(orderLineId: $orderLineId) { __typename ...Cart + ... on ErrorResult { + errorCode + message + } } } ${cartFragment} ` -export const fetcher: HookFetcher = ( - options, - { orderLineId }, - fetch -) => { +export const fetcher: HookFetcher< + RemoveOrderLineMutation, + RemoveOrderLineMutationVariables +> = (options, { orderLineId }, fetch) => { return fetch({ ...options, query: removeOrderLineMutation, @@ -30,16 +38,20 @@ export const fetcher: HookFetcher { const { mutate } = useCart() - const fn = useCartRemoveItem( - {}, - customFetcher - ) + const fn = useCartRemoveItem< + RemoveOrderLineMutation, + RemoveOrderLineMutationVariables + >({}, customFetcher) return useCallback( async function removeItem(input: any) { const { removeOrderLine } = await fn({ orderLineId: input.id }) if (removeOrderLine.__typename === 'Order') { await mutate({ removeOrderLine }, false) + } else { + throw new CommerceError({ + message: (removeOrderLine as ErrorResult).message, + }) } return { removeOrderLine } }, diff --git a/framework/vendure/cart/use-update-item.tsx b/framework/vendure/cart/use-update-item.tsx index a768c21e4..4c617b6ce 100644 --- a/framework/vendure/cart/use-update-item.tsx +++ b/framework/vendure/cart/use-update-item.tsx @@ -4,21 +4,30 @@ import type { HookFetcher } from '@commerce/utils/types' import useCartUpdateItem from '@commerce/cart/use-update-item' import useCart from './use-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 */ ` mutation adjustOrderLine($orderLineId: ID!, $quantity: Int!) { adjustOrderLine(orderLineId: $orderLineId, quantity: $quantity) { + __typename ...Cart + ... on ErrorResult { + errorCode + message + } } } ${cartFragment} ` -export const fetcher: HookFetcher = ( - options, - { orderLineId, quantity }, - fetch -) => { +export const fetcher: HookFetcher< + AdjustOrderLineMutation, + AdjustOrderLineMutationVariables +> = (options, { orderLineId, quantity }, fetch) => { return fetch({ ...options, query: adjustOrderLineMutation, @@ -29,10 +38,10 @@ export const fetcher: HookFetcher { const { mutate } = useCart() - const fn = useCartUpdateItem( - {}, - customFetcher - ) + const fn = useCartUpdateItem< + AdjustOrderLineMutation, + AdjustOrderLineMutationVariables + >({}, customFetcher) return useCallback( debounce(async (input: any) => { @@ -42,6 +51,10 @@ function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) { }) if (adjustOrderLine.__typename === 'Order') { await mutate({ adjustOrderLine }, false) + } else { + throw new CommerceError({ + message: (adjustOrderLine as ErrorResult).message, + }) } return { adjustOrderLine } }, cfg?.wait ?? 500), diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 6dd1bdb1c..a9361440a 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -2843,10 +2843,19 @@ export type LoginServerMutationVariables = Exact<{ export type LoginServerMutation = { __typename?: 'Mutation' } & { login: - | ({ __typename?: 'CurrentUser' } & Pick) - | { __typename?: 'InvalidCredentialsError' } - | { __typename?: 'NotVerifiedError' } - | { __typename?: 'NativeAuthStrategyError' } + | ({ __typename: 'CurrentUser' } & Pick) + | ({ __typename: 'InvalidCredentialsError' } & Pick< + InvalidCredentialsError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NotVerifiedError' } & Pick< + NotVerifiedError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NativeAuthStrategyError' } & Pick< + NativeAuthStrategyError, + 'errorCode' | 'message' + >) } export type LoginMutationVariables = Exact<{ @@ -2857,9 +2866,18 @@ export type LoginMutationVariables = Exact<{ export type LoginMutation = { __typename?: 'Mutation' } & { login: | ({ __typename: 'CurrentUser' } & Pick) - | { __typename: 'InvalidCredentialsError' } - | { __typename: 'NotVerifiedError' } - | { __typename: 'NativeAuthStrategyError' } + | ({ __typename: 'InvalidCredentialsError' } & Pick< + InvalidCredentialsError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NotVerifiedError' } & Pick< + NotVerifiedError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NativeAuthStrategyError' } & Pick< + NativeAuthStrategyError, + 'errorCode' | 'message' + >) } export type LogoutMutationVariables = Exact<{ [key: string]: never }> @@ -2874,9 +2892,15 @@ export type SignupMutationVariables = Exact<{ export type SignupMutation = { __typename?: 'Mutation' } & { registerCustomerAccount: - | ({ __typename?: 'Success' } & Pick) - | { __typename?: 'MissingPasswordError' } - | { __typename?: 'NativeAuthStrategyError' } + | ({ __typename: 'Success' } & Pick) + | ({ __typename: 'MissingPasswordError' } & Pick< + MissingPasswordError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NativeAuthStrategyError' } & Pick< + NativeAuthStrategyError, + 'errorCode' | 'message' + >) } export type AddItemToOrderMutationVariables = Exact<{ @@ -2886,11 +2910,23 @@ export type AddItemToOrderMutationVariables = Exact<{ export type AddItemToOrderMutation = { __typename?: 'Mutation' } & { addItemToOrder: - | ({ __typename?: 'Order' } & CartFragment) - | { __typename?: 'OrderModificationError' } - | { __typename?: 'OrderLimitError' } - | { __typename?: 'NegativeQuantityError' } - | { __typename?: 'InsufficientStockError' } + | ({ __typename: 'Order' } & CartFragment) + | ({ __typename: 'OrderModificationError' } & Pick< + OrderModificationError, + 'errorCode' | 'message' + >) + | ({ __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 }> @@ -2906,7 +2942,10 @@ export type RemoveOrderLineMutationVariables = Exact<{ export type RemoveOrderLineMutation = { __typename?: 'Mutation' } & { removeOrderLine: | ({ __typename: 'Order' } & CartFragment) - | { __typename: 'OrderModificationError' } + | ({ __typename: 'OrderModificationError' } & Pick< + OrderModificationError, + 'errorCode' | 'message' + >) } export type AdjustOrderLineMutationVariables = Exact<{ @@ -2916,11 +2955,23 @@ export type AdjustOrderLineMutationVariables = Exact<{ export type AdjustOrderLineMutation = { __typename?: 'Mutation' } & { adjustOrderLine: - | ({ __typename?: 'Order' } & CartFragment) - | { __typename?: 'OrderModificationError' } - | { __typename?: 'OrderLimitError' } - | { __typename?: 'NegativeQuantityError' } - | { __typename?: 'InsufficientStockError' } + | ({ __typename: 'Order' } & CartFragment) + | ({ __typename: 'OrderModificationError' } & Pick< + OrderModificationError, + 'errorCode' | 'message' + >) + | ({ __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 }>