Skip to content

Commit

Permalink
feat(client): handle GQL errors (#1914)
Browse files Browse the repository at this point in the history
* feat(client): handle GQL errors

* fix: add createFromResponse class to BigCommerceAPIError

* fix: return array of errors in BigCommerceGQLError

* feat: handle BigCommerceGQLError in addToCart action

* feat: update auth actions to show GQL errors

* feat: update account actions to show GQL errors

* feat: update cart actions to show GQL errors

* feat: update product action to show GQL errors

* feat: update header action to show GQL errors

* feat: update webpages action to show GQL errors

* chore: add changeset

* fix: minor details in error catch

* fix: remove unused import

* fix: form return in login action

* fix: return formatted message in product action

* fix: parse errors and send as message

* fix: remove assert in product action
  • Loading branch information
jorgemoya authored Jan 16, 2025
1 parent 2ba55d0 commit f039b2c
Show file tree
Hide file tree
Showing 23 changed files with 340 additions and 124 deletions.
7 changes: 7 additions & 0 deletions .changeset/dry-frogs-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@bigcommerce/catalyst-client": minor
---

GQL requests that respond as `200` but have an `errors` field will now be properly handled by the client and throw a proper `BigCommerceGQLError` response with the message reason from the API. This will provide a more detailed description of why the GQL request errored out.

API errors will still be handled and attribute the errored status as the message with this change as `BigCommerceAPIError`.
5 changes: 5 additions & 0 deletions .changeset/healthy-ears-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": minor
---

Properly handle `BigCommerceGQLError` in actions, by returning the error messages from the request.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use server';

import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { SubmissionResult } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { getTranslations } from 'next-intl/server';
Expand Down Expand Up @@ -63,7 +64,18 @@ export async function changePassword(
lastResult: submission.reply(),
successMessage: t('Form.successMessage'),
};
} catch (error: unknown) {
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);

if (error instanceof BigCommerceGQLError) {
return {
lastResult: submission.reply({
formErrors: error.errors.map(({ message }) => message),
}),
};
}

if (error instanceof Error) {
return {
lastResult: submission.reply({ formErrors: [error.message] }),
Expand Down
16 changes: 15 additions & 1 deletion core/app/[locale]/(default)/(auth)/login/_actions/login.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use server';

import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { SubmissionResult } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { getLocale, getTranslations } from 'next-intl/server';
Expand Down Expand Up @@ -31,7 +32,20 @@ export const login = async (_lastResult: SubmissionResult | null, formData: Form
redirect: false,
},
);
} catch {
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);

if (error instanceof BigCommerceGQLError) {
return submission.reply({
formErrors: error.errors.map(({ message }) => message),
});
}

if (error instanceof Error) {
return submission.reply({ formErrors: [error.message] });
}

return submission.reply({ formErrors: [t('Form.error')] });
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use server';

import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { SubmissionResult } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { getTranslations } from 'next-intl/server';
Expand Down Expand Up @@ -66,6 +67,17 @@ export const resetPassword = async (
lastResult: submission.reply({ formErrors: result.errors.map((error) => error.message) }),
};
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);

if (error instanceof BigCommerceGQLError) {
return {
lastResult: submission.reply({
formErrors: error.errors.map(({ message }) => message),
}),
};
}

if (error instanceof Error) {
return { lastResult: submission.reply({ formErrors: [error.message] }) };
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use server';

import { BigCommerceAPIError } from '@bigcommerce/catalyst-client';
import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { SubmissionResult } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { getLocale, getTranslations } from 'next-intl/server';
Expand Down Expand Up @@ -212,15 +212,6 @@ export async function registerCustomer<F extends Field>(
};
}

const result = response.data.customer.registerCustomer;

if (result.errors.length > 0) {
return {
lastResult: submission.reply({ formErrors: result.errors.map((error) => error.message) }),
fields: prevState.fields,
};
}

await signIn(
{
type: 'password',
Expand All @@ -237,9 +228,18 @@ export async function registerCustomer<F extends Field>(
// eslint-disable-next-line no-console
console.error(error);

if (error instanceof BigCommerceAPIError) {
if (error instanceof BigCommerceGQLError) {
return {
lastResult: submission.reply({
formErrors: error.errors.map(({ message }) => message),
}),
fields: prevState.fields,
};
}

if (error instanceof Error) {
return {
lastResult: submission.reply({ formErrors: [t('Errors.apiError')] }),
lastResult: submission.reply({ formErrors: [error.message] }),
fields: prevState.fields,
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigCommerceAPIError } from '@bigcommerce/catalyst-client';
import { BigCommerceAPIError, BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { parseWithZod } from '@conform-to/zod';
import { unstable_expireTag as expireTag } from 'next/cache';
import { getTranslations } from 'next-intl/server';
Expand Down Expand Up @@ -248,10 +248,19 @@ export async function createAddress(prevState: Awaited<State>, formData: FormDat
// eslint-disable-next-line no-console
console.error(error);

if (error instanceof BigCommerceGQLError) {
return {
...prevState,
lastResult: submission.reply({
formErrors: error.errors.map(({ message }) => message),
}),
};
}

if (error instanceof BigCommerceAPIError) {
return {
...prevState,
lastResult: submission.reply({ formErrors: [t('Errors.apiError')] }),
lastResult: submission.reply({ formErrors: [error.message] }),
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigCommerceAPIError } from '@bigcommerce/catalyst-client';
import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { parseWithZod } from '@conform-to/zod';
import { unstable_expireTag as expireTag } from 'next/cache';
import { getTranslations } from 'next-intl/server';
Expand Down Expand Up @@ -88,14 +88,23 @@ export async function deleteAddress(prevState: Awaited<State>, formData: FormDat
defaultAddress: prevState.defaultAddress,
fields: prevState.fields,
};
} catch (error: unknown) {
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);

if (error instanceof BigCommerceAPIError) {
if (error instanceof BigCommerceGQLError) {
return {
...prevState,
lastResult: submission.reply({ formErrors: [t('Errors.apiError')] }),
lastResult: submission.reply({
formErrors: error.errors.map(({ message }) => message),
}),
};
}

if (error instanceof Error) {
return {
...prevState,
lastResult: submission.reply({ formErrors: [error.message] }),
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigCommerceAPIError } from '@bigcommerce/catalyst-client';
import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { parseWithZod } from '@conform-to/zod';
import { unstable_expireTag as expireTag } from 'next/cache';
import { getTranslations } from 'next-intl/server';
Expand Down Expand Up @@ -257,10 +257,19 @@ export async function updateAddress(prevState: Awaited<State>, formData: FormDat
// eslint-disable-next-line no-console
console.error(error);

if (error instanceof BigCommerceAPIError) {
if (error instanceof BigCommerceGQLError) {
return {
...prevState,
lastResult: submission.reply({ formErrors: [t('Errors.apiError')] }),
lastResult: submission.reply({
formErrors: error.errors.map(({ message }) => message),
}),
};
}

if (error instanceof Error) {
return {
...prevState,
lastResult: submission.reply({ formErrors: [error.message] }),
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use server';

import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { parseWithZod } from '@conform-to/zod';
import { getTranslations } from 'next-intl/server';

Expand Down Expand Up @@ -68,6 +69,16 @@ export const changePassword: ChangePasswordAction = async (prevState, formData)
// eslint-disable-next-line no-console
console.error(error);

if (error instanceof BigCommerceGQLError) {
return submission.reply({
formErrors: error.errors.map(({ message }) => message),
});
}

if (error instanceof Error) {
return submission.reply({ formErrors: [error.message] });
}

return submission.reply({ formErrors: [t('error')] });
}
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use server';

import { BigCommerceAPIError } from '@bigcommerce/catalyst-client';
import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { parseWithZod } from '@conform-to/zod';
import { unstable_expireTag } from 'next/cache';
import { getTranslations } from 'next-intl/server';
Expand Down Expand Up @@ -87,10 +87,19 @@ export const updateCustomer: UpdateAccountAction = async (prevState, formData) =
// eslint-disable-next-line no-console
console.error(error);

if (error instanceof BigCommerceAPIError) {
if (error instanceof BigCommerceGQLError) {
return {
account: prevState.account,
lastResult: submission.reply({ formErrors: [t('Errors.apiError')] }),
lastResult: submission.reply({
formErrors: error.errors.map(({ message }) => message),
}),
};
}

if (error instanceof Error) {
return {
account: prevState.account,
lastResult: submission.reply({ formErrors: [error.message] }),
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use server';

import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { SubmissionResult } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { cookies } from 'next/headers';
Expand Down Expand Up @@ -53,11 +54,20 @@ export const redirectToCheckout = async (

url = data.cart.createCartRedirectUrls.redirectUrls?.redirectedCheckoutUrl;
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);

if (error instanceof BigCommerceGQLError) {
return submission.reply({
formErrors: error.errors.map(({ message }) => message),
});
}

if (error instanceof Error) {
return submission.reply({ formErrors: [error.message] });
}

return submission.reply({ formErrors: [String(error)] });
return submission.reply({ formErrors: [t('failedToRedirectToCheckout')] });
}

if (!url) {
Expand Down
62 changes: 27 additions & 35 deletions core/app/[locale]/(default)/cart/_actions/remove-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,47 +31,39 @@ export async function removeItem({

const customerAccessToken = await getSessionCustomerAccessToken();

try {
const cookieStore = await cookies();
const cartId = cookieStore.get('cartId')?.value;
const cookieStore = await cookies();
const cartId = cookieStore.get('cartId')?.value;

if (!cartId) {
throw new Error(t('cartNotFound'));
}
if (!cartId) {
throw new Error(t('cartNotFound'));
}

if (!lineItemEntityId) {
throw new Error(t('lineItemNotFound'));
}
if (!lineItemEntityId) {
throw new Error(t('lineItemNotFound'));
}

const response = await client.fetch({
document: DeleteCartLineItemMutation,
variables: {
input: {
cartEntityId: cartId,
lineItemEntityId,
},
const response = await client.fetch({
document: DeleteCartLineItemMutation,
variables: {
input: {
cartEntityId: cartId,
lineItemEntityId,
},
customerAccessToken,
fetchOptions: { cache: 'no-store' },
});
},
customerAccessToken,
fetchOptions: { cache: 'no-store' },
});

const cart = response.data.cart.deleteCartLineItem?.cart;
const cart = response.data.cart.deleteCartLineItem?.cart;

// If we remove the last item in a cart the cart is deleted
// so we need to remove the cartId cookie
// TODO: We need to figure out if it actually failed.
if (!cart) {
cookieStore.delete('cartId');
}

unstable_expireTag(TAGS.cart);
// If we remove the last item in a cart the cart is deleted
// so we need to remove the cartId cookie
// TODO: We need to figure out if it actually failed.
if (!cart) {
cookieStore.delete('cartId');
}

return cart;
} catch (error: unknown) {
if (error instanceof Error) {
throw new Error(error.message);
}
unstable_expireTag(TAGS.cart);

throw new Error(t('somethingWentWrong'));
}
return cart;
}
Loading

0 comments on commit f039b2c

Please sign in to comment.