From 753613eb7af397d087c277dc66c0666f89137d59 Mon Sep 17 00:00:00 2001 From: Yurii Venher <79912799+yurii-ve@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:50:34 +0300 Subject: [PATCH] Update error handling (#72) * Remove app board (#70) (#71) * Update error handling --- app/root.tsx | 2 +- app/routes/category.$categorySlug/route.tsx | 34 ++++++++----------- app/routes/products.$productSlug/route.tsx | 34 ++++++++----------- app/routes/thank-you/route.tsx | 12 +++++-- src/utils/common.ts | 37 +++++++++++++-------- 5 files changed, 62 insertions(+), 57 deletions(-) diff --git a/app/root.tsx b/app/root.tsx index 77888cc..7a0039a 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -86,7 +86,7 @@ export function ErrorBoundary() { title={isPageNotFoundError ? 'Page Not Found' : 'Oops, something went wrong'} message={isPageNotFoundError ? undefined : getErrorMessage(error)} actionButtonText="Back to shopping" - onActionButtonClick={() => navigate(ROUTES.category.to())} + onActionButtonClick={() => navigate(ROUTES.category.to('all-products'))} /> ); diff --git a/app/routes/category.$categorySlug/route.tsx b/app/routes/category.$categorySlug/route.tsx index 4c17eed..f562f39 100644 --- a/app/routes/category.$categorySlug/route.tsx +++ b/app/routes/category.$categorySlug/route.tsx @@ -6,7 +6,7 @@ import { EcomApiErrorCodes } from '~/api/types'; import { getImageHttpUrl } from '~/api/wix-image'; import { ProductCard } from '~/components/product-card/product-card'; import { ROUTES } from '~/router/config'; -import { getUrlOriginWithPath, isOutOfStock } from '~/utils'; +import { getErrorMessage, getUrlOriginWithPath, isOutOfStock } from '~/utils'; import { ErrorComponent } from '~/components/error-component/error-component'; import styles from './category.module.scss'; @@ -96,28 +96,22 @@ export function ErrorBoundary() { const error = useRouteError(); const navigate = useNavigate(); - if (isRouteErrorResponse(error)) { - let title: string; - let message: string | undefined; - if (error.data.code === EcomApiErrorCodes.CategoryNotFound) { - title = 'Category Not Found'; - message = "Unfortunately category you trying to open doesn't exist"; - } else { - title = 'Failed to load category products'; - message = error.data.message; - } + let title = 'Error'; + let message = getErrorMessage(error); - return ( - navigate(ROUTES.category.to())} - /> - ); + if (isRouteErrorResponse(error) && error.data.code === EcomApiErrorCodes.CategoryNotFound) { + title = 'Category Not Found'; + message = "Unfortunately, the category page you're trying to open does not exist"; } - throw error; + return ( + navigate(ROUTES.category.to('all-products'))} + /> + ); } export const meta: MetaFunction = ({ data }) => { diff --git a/app/routes/products.$productSlug/route.tsx b/app/routes/products.$productSlug/route.tsx index 64df7f1..5a42181 100644 --- a/app/routes/products.$productSlug/route.tsx +++ b/app/routes/products.$productSlug/route.tsx @@ -13,7 +13,7 @@ import { ProductOption } from '~/components/product-option/product-option'; import { UnsafeRichText } from '~/components/rich-text/rich-text'; import { getChoiceValue } from '~/components/product-option/product-option-utils'; import { ROUTES } from '~/router/config'; -import { getPriceData, getSelectedVariant, getSKU, getUrlOriginWithPath, isOutOfStock } from '~/utils'; +import { getErrorMessage, getPriceData, getSelectedVariant, getSKU, getUrlOriginWithPath, isOutOfStock } from '~/utils'; import { AddToCartOptions, EcomApiErrorCodes } from '~/api/types'; import styles from './product-details.module.scss'; @@ -166,28 +166,22 @@ export function ErrorBoundary() { const error = useRouteError(); const navigate = useNavigate(); - if (isRouteErrorResponse(error)) { - let title: string; - let message: string | undefined; - if (error.data.code === EcomApiErrorCodes.ProductNotFound) { - title = 'Product Not Found'; - message = "Unfortunately product you trying to open doesn't exist"; - } else { - title = 'Failed to load product details'; - message = error.data.message; - } + let title = 'Error'; + let message = getErrorMessage(error); - return ( - navigate(ROUTES.category.to())} - /> - ); + if (isRouteErrorResponse(error) && error.data.code === EcomApiErrorCodes.ProductNotFound) { + title = 'Product Not Found'; + message = "Unfortunately a product page you trying to open doesn't exist"; } - throw error; + return ( + navigate(ROUTES.category.to('all-products'))} + /> + ); } export const meta: MetaFunction = ({ data }) => { diff --git a/app/routes/thank-you/route.tsx b/app/routes/thank-you/route.tsx index 118d70b..eddcb6e 100644 --- a/app/routes/thank-you/route.tsx +++ b/app/routes/thank-you/route.tsx @@ -1,11 +1,12 @@ import { LinksFunction, LoaderFunctionArgs, MetaFunction } from '@remix-run/node'; -import { Link, useSearchParams } from '@remix-run/react'; +import { isRouteErrorResponse, Link, useRouteError, useSearchParams } from '@remix-run/react'; import { useEffect, useState } from 'react'; import { getEcomApi } from '~/api/ecom-api'; import { OrderDetails } from '~/api/types'; +import { ErrorComponent } from '~/components/error-component/error-component'; import { OrderSummary } from '~/components/order-summary/order-summary'; import { ROUTES } from '~/router/config'; -import { getUrlOriginWithPath } from '~/utils'; +import { getErrorMessage, getUrlOriginWithPath } from '~/utils'; import styles from './thank-you.module.scss'; export const loader = async ({ request }: LoaderFunctionArgs) => { @@ -61,6 +62,13 @@ export default function ThankYouPage() { ); } +export function ErrorBoundary() { + const error = useRouteError(); + const title = isRouteErrorResponse(error) ? 'Failed to load order details' : 'Error'; + const message = getErrorMessage(error); + return ; +} + export const meta: MetaFunction = ({ data }) => { const title = 'E-Commerce App - Thank You'; const description = 'Thank You for your purchase'; diff --git a/src/utils/common.ts b/src/utils/common.ts index 17e72d4..ae3cf9c 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -10,30 +10,39 @@ export function getUrlOriginWithPath(url: string) { * Retrieves the message from a thrown error. * - Handles Remix ErrorResponse (non-Error instance). * - Handles Wix eCom SDK errors (non-Error instance). - * - Converts plain objects to a JSON string as a measure - * to help identify the origin of such improper errors. - * - Falls back on converting the value to a string. + * - Handles plain objects structured like an Error. + * - Converts plain objects with unknown structure into + * a JSON string to help in debugging their source. + * - Falls back to converting the value to a string. */ -export function getErrorMessage(value: unknown): string { - if (value instanceof Error) { - return value.message; +export function getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; } - if (isRouteErrorResponse(value)) { - return value.data; + if (isEcomSDKError(error)) { + return error.message; } - if (isEcomSDKError(value)) { - return value.message; + // Remix ErrorResponse thrown from an action or loader: + // - throw new Response('oops'); + // - throw json('oops') + // - throw json({message: 'oops'}) + if (isRouteErrorResponse(error)) { + error = error.data; } - if (typeof value == 'object' && value !== null) { + if (typeof error == 'object' && error !== null) { + if ('message' in error && typeof error.message === 'string') { + return error.message; + } + try { - return JSON.stringify(value); + return JSON.stringify(error); } catch { - // ignore serialization failure + // Fall through. } } - return String(value); + return String(error); }