Skip to content

Commit 53f2970

Browse files
committed
Enhance error handling in Dependants loader functions with user-facing error mapping
1 parent cd40580 commit 53f2970

File tree

1 file changed

+124
-103
lines changed

1 file changed

+124
-103
lines changed

apps/cyberstorm-remix/app/p/dependants/Dependants.tsx

Lines changed: 124 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -6,168 +6,189 @@ import {
66
} from "@thunderstore/cyberstorm";
77
import "./Dependants.css";
88
import { PackageSearch } from "~/commonComponents/PackageSearch/PackageSearch";
9-
import { DapperTs } from "@thunderstore/dapper-ts";
109
import { PackageOrderOptions } from "../../commonComponents/PackageSearch/components/PackageOrder";
1110
import { type OutletContextShape } from "../../root";
1211
import { PageHeader } from "~/commonComponents/PageHeader/PageHeader";
1312
import {
14-
getPublicEnvVariables,
15-
getSessionTools,
16-
} from "cyberstorm/security/publicEnvVariables";
13+
NimbusAwaitErrorElement,
14+
NimbusDefaultRouteErrorBoundary,
15+
} from "cyberstorm/utils/errors/NimbusErrorBoundary";
1716
import type { Route } from "./+types/Dependants";
1817
import { Suspense } from "react";
18+
import { throwUserFacingPayloadResponse } from "cyberstorm/utils/errors/userFacingErrorResponse";
19+
import { handleLoaderError } from "cyberstorm/utils/errors/handleLoaderError";
20+
import { createNotFoundMapping } from "cyberstorm/utils/errors/loaderMappings";
21+
import { getLoaderTools } from "cyberstorm/utils/getLoaderTools";
22+
import { parseIntegerSearchParam } from "cyberstorm/utils/searchParamsUtils";
23+
24+
const packageDependantsNotFoundMappings = [
25+
createNotFoundMapping(
26+
"Package not found.",
27+
"We could not find the requested package."
28+
),
29+
];
1930

2031
export async function loader({ params, request }: Route.LoaderArgs) {
2132
if (params.communityId && params.packageId && params.namespaceId) {
22-
const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]);
23-
const dapper = new DapperTs(() => {
24-
return {
25-
apiHost: publicEnvVariables.VITE_API_URL,
26-
sessionId: undefined,
27-
};
28-
});
33+
const { dapper } = getLoaderTools();
2934
const searchParams = new URL(request.url).searchParams;
3035
const ordering =
3136
searchParams.get("ordering") ?? PackageOrderOptions.Updated;
32-
const page = searchParams.get("page");
37+
const page = parseIntegerSearchParam(searchParams.get("page"));
3338
const search = searchParams.get("search");
3439
const includedCategories = searchParams.get("includedCategories");
3540
const excludedCategories = searchParams.get("excludedCategories");
3641
const section = searchParams.get("section");
3742
const nsfw = searchParams.get("nsfw");
3843
const deprecated = searchParams.get("deprecated");
39-
const filters = await dapper.getCommunityFilters(params.communityId);
44+
try {
45+
const dataPromise = await Promise.all([
46+
dapper.getCommunityFilters(params.communityId),
47+
dapper.getCommunity(params.communityId),
48+
dapper.getPackageListingDetails(
49+
params.communityId,
50+
params.namespaceId,
51+
params.packageId
52+
),
53+
dapper.getPackageListings(
54+
{
55+
kind: "package-dependants",
56+
communityId: params.communityId,
57+
namespaceId: params.namespaceId,
58+
packageName: params.packageId,
59+
},
60+
ordering ?? "",
61+
page,
62+
search ?? "",
63+
includedCategories?.split(",") ?? undefined,
64+
excludedCategories?.split(",") ?? undefined,
65+
section ? (section === "all" ? "" : section) : "",
66+
nsfw === "true" ? true : false,
67+
deprecated === "true" ? true : false
68+
),
69+
]);
4070

41-
return {
42-
community: dapper.getCommunity(params.communityId),
43-
listing: dapper.getPackageListingDetails(
44-
params.communityId,
45-
params.namespaceId,
46-
params.packageId
47-
),
48-
filters: filters,
49-
listings: await dapper.getPackageListings(
50-
{
51-
kind: "package-dependants",
52-
communityId: params.communityId,
53-
namespaceId: params.namespaceId,
54-
packageName: params.packageId,
55-
},
56-
ordering ?? "",
57-
page === null ? undefined : Number(page),
58-
search ?? "",
59-
includedCategories?.split(",") ?? undefined,
60-
excludedCategories?.split(",") ?? undefined,
61-
section ? (section === "all" ? "" : section) : "",
62-
nsfw === "true" ? true : false,
63-
deprecated === "true" ? true : false
64-
),
65-
};
71+
return dataPromise;
72+
} catch (error) {
73+
handleLoaderError(error, {
74+
mappings: packageDependantsNotFoundMappings,
75+
});
76+
}
6677
}
67-
throw new Response("Community not found", { status: 404 });
78+
throwUserFacingPayloadResponse({
79+
headline: "Community not found.",
80+
description: "We could not find the requested community.",
81+
category: "not_found",
82+
status: 404,
83+
});
6884
}
6985

7086
export async function clientLoader({
7187
request,
7288
params,
7389
}: Route.ClientLoaderArgs) {
7490
if (params.communityId && params.packageId && params.namespaceId) {
75-
const tools = getSessionTools();
76-
const dapper = new DapperTs(() => {
77-
return {
78-
apiHost: tools?.getConfig().apiHost,
79-
sessionId: tools?.getConfig().sessionId,
80-
};
81-
});
91+
const { dapper } = getLoaderTools();
8292
const searchParams = new URL(request.url).searchParams;
8393
const ordering =
8494
searchParams.get("ordering") ?? PackageOrderOptions.Updated;
85-
const page = searchParams.get("page");
95+
const page = parseIntegerSearchParam(searchParams.get("page"));
8696
const search = searchParams.get("search");
8797
const includedCategories = searchParams.get("includedCategories");
8898
const excludedCategories = searchParams.get("excludedCategories");
8999
const section = searchParams.get("section");
90100
const nsfw = searchParams.get("nsfw");
91101
const deprecated = searchParams.get("deprecated");
92-
const filters = dapper.getCommunityFilters(params.communityId);
93-
return {
94-
community: dapper.getCommunity(params.communityId),
95-
listing: dapper.getPackageListingDetails(
102+
103+
const dataPromise = Promise.all([
104+
dapper.getCommunityFilters(params.communityId),
105+
dapper.getCommunity(params.communityId),
106+
dapper.getPackageListingDetails(
96107
params.communityId,
97108
params.namespaceId,
98109
params.packageId
99110
),
100-
filters: filters,
101-
listings: dapper.getPackageListings(
111+
dapper.getPackageListings(
102112
{
103113
kind: "package-dependants",
104114
communityId: params.communityId,
105115
namespaceId: params.namespaceId,
106116
packageName: params.packageId,
107117
},
108118
ordering ?? "",
109-
page === null ? undefined : Number(page),
119+
page,
110120
search ?? "",
111121
includedCategories?.split(",") ?? undefined,
112122
excludedCategories?.split(",") ?? undefined,
113123
section ? (section === "all" ? "" : section) : "",
114-
nsfw === "true" ? true : false,
115-
deprecated === "true" ? true : false
124+
nsfw === "true",
125+
deprecated === "true"
116126
),
117-
};
127+
]);
128+
129+
return dataPromise;
118130
}
119-
throw new Response("Community not found", { status: 404 });
131+
throwUserFacingPayloadResponse({
132+
headline: "Community not found.",
133+
description: "We could not find the requested community.",
134+
category: "not_found",
135+
status: 404,
136+
});
120137
}
121138

122139
export default function Dependants() {
123-
const { filters, listing, listings } = useLoaderData<
124-
typeof loader | typeof clientLoader
125-
>();
140+
const data = useLoaderData<typeof loader | typeof clientLoader>();
126141

127142
const outletContext = useOutletContext() as OutletContextShape;
128143

129144
return (
130-
<>
131-
<section className="dependants">
132-
<Suspense fallback={<SkeletonBox />}>
133-
<Await resolve={listing}>
134-
{(resolvedValue) => (
135-
<PageHeader headingLevel="1" headingSize="3">
136-
Mods that depend on{" "}
137-
<NewLink
138-
primitiveType="cyberstormLink"
139-
linkId="Package"
140-
community={resolvedValue.community_identifier}
141-
namespace={resolvedValue.namespace}
142-
package={resolvedValue.name}
143-
csVariant="cyber"
144-
>
145-
{formatToDisplayName(resolvedValue.name)}
146-
</NewLink>
147-
{" by "}
148-
<NewLink
149-
primitiveType="cyberstormLink"
150-
linkId="Team"
151-
community={resolvedValue.community_identifier}
152-
team={resolvedValue.namespace}
153-
csVariant="cyber"
154-
>
155-
{resolvedValue.namespace}
156-
</NewLink>
157-
</PageHeader>
158-
)}
159-
</Await>
160-
</Suspense>
161-
<>
162-
<PackageSearch
163-
listings={listings}
164-
filters={filters}
165-
config={outletContext.requestConfig}
166-
currentUser={outletContext.currentUser}
167-
dapper={outletContext.dapper}
168-
/>
169-
</>
170-
</section>
171-
</>
145+
<section className="dependants">
146+
<Suspense fallback={<SkeletonBox />}>
147+
<Await resolve={data} errorElement={<NimbusAwaitErrorElement />}>
148+
{(resolvedData) => {
149+
const [communityFilters, community, listingDetail, listings] =
150+
resolvedData;
151+
return (
152+
<>
153+
<PageHeader headingLevel="1" headingSize="3">
154+
Mods that depend on{" "}
155+
<NewLink
156+
primitiveType="cyberstormLink"
157+
linkId="Package"
158+
community={community.identifier}
159+
namespace={listingDetail.namespace}
160+
package={listingDetail.name}
161+
csVariant="cyber"
162+
>
163+
{formatToDisplayName(listingDetail.name)}
164+
</NewLink>
165+
{" by "}
166+
<NewLink
167+
primitiveType="cyberstormLink"
168+
linkId="Team"
169+
community={listingDetail.community_identifier}
170+
team={listingDetail.namespace}
171+
csVariant="cyber"
172+
>
173+
{listingDetail.namespace}
174+
</NewLink>
175+
</PageHeader>
176+
<PackageSearch
177+
listings={listings}
178+
filters={communityFilters}
179+
config={outletContext.requestConfig}
180+
currentUser={outletContext.currentUser}
181+
dapper={outletContext.dapper}
182+
/>
183+
</>
184+
);
185+
}}
186+
</Await>
187+
</Suspense>
188+
</section>
172189
);
173190
}
191+
192+
export function ErrorBoundary() {
193+
return <NimbusDefaultRouteErrorBoundary />;
194+
}

0 commit comments

Comments
 (0)