Skip to content

Commit 88201c4

Browse files
committed
Enhance error handling in root.tsx ErrorBoundary with user-facing error mapping and deduplication of error descriptions
1 parent cd40580 commit 88201c4

File tree

1 file changed

+54
-8
lines changed

1 file changed

+54
-8
lines changed

apps/cyberstorm-remix/app/root.tsx

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ import {
2727
NewBreadCrumbs,
2828
NewBreadCrumbsLink,
2929
} from "@thunderstore/cyberstorm";
30+
import {
31+
parseUserFacingErrorPayload,
32+
type UserFacingErrorPayload,
33+
} from "cyberstorm/utils/errors/userFacingErrorResponse";
3034
import { DapperTs } from "@thunderstore/dapper-ts";
3135
import { type CurrentUser } from "@thunderstore/dapper/types";
3236

@@ -35,6 +39,7 @@ import { memo, type ReactNode, Suspense, useEffect, useRef } from "react";
3539
import { useHydrated } from "remix-utils/use-hydrated";
3640
import Toast from "@thunderstore/cyberstorm/src/newComponents/Toast";
3741
import { Footer } from "./commonComponents/Footer/Footer";
42+
import { NimbusAwaitErrorElement } from "../cyberstorm/utils/errors/NimbusErrorBoundary";
3843
import { type RequestConfig } from "@thunderstore/thunderstore-api";
3944
import { NavigationWrapper } from "./commonComponents/Navigation/NavigationWrapper";
4045
import { NamespacedStorageManager } from "@thunderstore/ts-api-react";
@@ -617,18 +622,36 @@ export function ErrorBoundary() {
617622
console.log(error);
618623
}
619624
const isResponseError = isRouteErrorResponse(error);
625+
let payload: UserFacingErrorPayload | null = null;
626+
627+
if (isResponseError) {
628+
payload = parseUserFacingErrorPayload(error.data);
629+
}
630+
631+
const statusCode = payload?.status ?? (isResponseError ? error.status : 500);
632+
const headline =
633+
payload?.headline ??
634+
(isResponseError
635+
? error.statusText || `Error ${error.status}`
636+
: "Internal server error");
637+
638+
const fallbackDescription =
639+
isResponseError && typeof error.data === "string"
640+
? dedupeDescription(headline, error.data)
641+
: undefined;
642+
643+
const description = payload?.description ?? fallbackDescription;
644+
const showDefaultFlavor = !payload && !isResponseError;
620645
return (
621646
<div className="error">
622-
<div
623-
className="error__glitch"
624-
data-text={isResponseError ? error.status : 500}
625-
>
626-
<span>{isResponseError ? error.status : 500}</span>
647+
<div className="error__glitch" data-text={statusCode}>
648+
<span>{statusCode}</span>
627649
</div>
628650
<div className="error__description">
629-
{isResponseError ? error.data : "Internal server error"}
651+
<strong>{headline}</strong>
652+
{description ? <div>{description}</div> : null}
630653
</div>
631-
{!isResponseError && (
654+
{showDefaultFlavor && (
632655
<div className="error__flavor">
633656
Beep boop. Server something error happens.
634657
</div>
@@ -637,6 +660,26 @@ export function ErrorBoundary() {
637660
);
638661
}
639662

663+
function dedupeDescription(
664+
headline: string,
665+
description: string | undefined
666+
): string | undefined {
667+
if (!description) {
668+
return undefined;
669+
}
670+
671+
const trimmedDescription = description.trim();
672+
if (!trimmedDescription) {
673+
return undefined;
674+
}
675+
676+
if (trimmedDescription.toLowerCase() === headline.trim().toLowerCase()) {
677+
return undefined;
678+
}
679+
680+
return trimmedDescription;
681+
}
682+
640683
// Temporary solution for implementing ads
641684
// REMIX TODO: Move to dynamic html
642685
function AdsInit() {
@@ -735,7 +778,10 @@ function getCommunityBreadcrumb(
735778
</span>
736779
}
737780
>
738-
<Await resolve={communityPage.data.community}>
781+
<Await
782+
resolve={communityPage.data.community}
783+
errorElement={<NimbusAwaitErrorElement />}
784+
>
739785
{(resolvedValue) => {
740786
let label = undefined;
741787
let icon = undefined;

0 commit comments

Comments
 (0)