Skip to content

Commit 81fe141

Browse files
committed
Enhance error handling in Dependants loader functions with user-facing error mapping
1 parent 5ac0d36 commit 81fe141

File tree

1 file changed

+130
-111
lines changed

1 file changed

+130
-111
lines changed
Lines changed: 130 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,175 +1,194 @@
1-
import {
2-
getPublicEnvVariables,
3-
getSessionTools,
4-
} from "cyberstorm/security/publicEnvVariables";
5-
import { Suspense } from "react";
61
import { Await, useLoaderData, useOutletContext } from "react-router";
7-
import { PackageSearch } from "~/commonComponents/PackageSearch/PackageSearch";
8-
import { PageHeader } from "~/commonComponents/PageHeader/PageHeader";
9-
102
import {
3+
formatToDisplayName,
114
NewLink,
125
SkeletonBox,
13-
formatToDisplayName,
146
} from "@thunderstore/cyberstorm";
15-
import { DapperTs } from "@thunderstore/dapper-ts";
16-
7+
import "./Dependants.css";
8+
import { PackageSearch } from "~/commonComponents/PackageSearch/PackageSearch";
179
import { PackageOrderOptions } from "../../commonComponents/PackageSearch/components/PackageOrder";
1810
import { type OutletContextShape } from "../../root";
11+
import { PageHeader } from "~/commonComponents/PageHeader/PageHeader";
12+
import {
13+
NimbusAwaitErrorElement,
14+
NimbusDefaultRouteErrorBoundary,
15+
} from "cyberstorm/utils/errors/NimbusErrorBoundary";
1916
import type { Route } from "./+types/Dependants";
20-
import "./Dependants.css";
17+
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+
];
2130

2231
export async function loader({ params, request }: Route.LoaderArgs) {
2332
if (params.communityId && params.packageId && params.namespaceId) {
24-
const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]);
25-
const dapper = new DapperTs(() => {
26-
return {
27-
apiHost: publicEnvVariables.VITE_API_URL,
28-
sessionId: undefined,
29-
};
30-
});
33+
const { dapper } = getLoaderTools();
3134
const searchParams = new URL(request.url).searchParams;
3235
const ordering =
3336
searchParams.get("ordering") ?? PackageOrderOptions.Updated;
34-
const page = searchParams.get("page");
37+
const page = parseIntegerSearchParam(searchParams.get("page"));
3538
const search = searchParams.get("search");
3639
const includedCategories = searchParams.get("includedCategories");
3740
const excludedCategories = searchParams.get("excludedCategories");
3841
const section = searchParams.get("section");
3942
const nsfw = searchParams.get("nsfw");
4043
const deprecated = searchParams.get("deprecated");
41-
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+
]);
4270

43-
return {
44-
community: dapper.getCommunity(params.communityId),
45-
listing: dapper.getPackageListingDetails(
46-
params.communityId,
47-
params.namespaceId,
48-
params.packageId
49-
),
50-
filters: filters,
51-
listings: await dapper.getPackageListings(
52-
{
53-
kind: "package-dependants",
54-
communityId: params.communityId,
55-
namespaceId: params.namespaceId,
56-
packageName: params.packageId,
57-
},
58-
ordering ?? "",
59-
page === null ? undefined : Number(page),
60-
search ?? "",
61-
includedCategories?.split(",") ?? undefined,
62-
excludedCategories?.split(",") ?? undefined,
63-
section ? (section === "all" ? "" : section) : "",
64-
nsfw === "true" ? true : false,
65-
deprecated === "true" ? true : false
66-
),
67-
};
71+
return dataPromise;
72+
} catch (error) {
73+
handleLoaderError(error, {
74+
mappings: packageDependantsNotFoundMappings,
75+
});
76+
}
6877
}
69-
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+
});
7084
}
7185

7286
export async function clientLoader({
7387
request,
7488
params,
7589
}: Route.ClientLoaderArgs) {
7690
if (params.communityId && params.packageId && params.namespaceId) {
77-
const tools = getSessionTools();
78-
const dapper = new DapperTs(() => {
79-
return {
80-
apiHost: tools?.getConfig().apiHost,
81-
sessionId: tools?.getConfig().sessionId,
82-
};
83-
});
91+
const { dapper } = getLoaderTools();
8492
const searchParams = new URL(request.url).searchParams;
8593
const ordering =
8694
searchParams.get("ordering") ?? PackageOrderOptions.Updated;
87-
const page = searchParams.get("page");
95+
const page = parseIntegerSearchParam(searchParams.get("page"));
8896
const search = searchParams.get("search");
8997
const includedCategories = searchParams.get("includedCategories");
9098
const excludedCategories = searchParams.get("excludedCategories");
9199
const section = searchParams.get("section");
92100
const nsfw = searchParams.get("nsfw");
93101
const deprecated = searchParams.get("deprecated");
94-
const filters = dapper.getCommunityFilters(params.communityId);
95-
return {
96-
community: dapper.getCommunity(params.communityId),
97-
listing: dapper.getPackageListingDetails(
102+
103+
const dataPromise = Promise.all([
104+
dapper.getCommunityFilters(params.communityId),
105+
dapper.getCommunity(params.communityId),
106+
dapper.getPackageListingDetails(
98107
params.communityId,
99108
params.namespaceId,
100109
params.packageId
101110
),
102-
filters: filters,
103-
listings: dapper.getPackageListings(
111+
dapper.getPackageListings(
104112
{
105113
kind: "package-dependants",
106114
communityId: params.communityId,
107115
namespaceId: params.namespaceId,
108116
packageName: params.packageId,
109117
},
110118
ordering ?? "",
111-
page === null ? undefined : Number(page),
119+
page,
112120
search ?? "",
113121
includedCategories?.split(",") ?? undefined,
114122
excludedCategories?.split(",") ?? undefined,
115123
section ? (section === "all" ? "" : section) : "",
116-
nsfw === "true" ? true : false,
117-
deprecated === "true" ? true : false
124+
nsfw === "true",
125+
deprecated === "true"
118126
),
119-
};
127+
]);
128+
129+
return dataPromise;
120130
}
121-
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+
});
122137
}
123138

124139
export default function Dependants() {
125-
const { filters, listing, listings } = useLoaderData<
126-
typeof loader | typeof clientLoader
127-
>();
140+
const data = useLoaderData<typeof loader | typeof clientLoader>();
128141

129142
const outletContext = useOutletContext() as OutletContextShape;
130143

131144
return (
132-
<>
133-
<section className="dependants">
134-
<Suspense fallback={<SkeletonBox />}>
135-
<Await resolve={listing}>
136-
{(resolvedValue) => (
137-
<PageHeader headingLevel="1" headingSize="3">
138-
Mods that depend on{" "}
139-
<NewLink
140-
primitiveType="cyberstormLink"
141-
linkId="Package"
142-
community={resolvedValue.community_identifier}
143-
namespace={resolvedValue.namespace}
144-
package={resolvedValue.name}
145-
csVariant="cyber"
146-
>
147-
{formatToDisplayName(resolvedValue.name)}
148-
</NewLink>
149-
{" by "}
150-
<NewLink
151-
primitiveType="cyberstormLink"
152-
linkId="Team"
153-
community={resolvedValue.community_identifier}
154-
team={resolvedValue.namespace}
155-
csVariant="cyber"
156-
>
157-
{resolvedValue.namespace}
158-
</NewLink>
159-
</PageHeader>
160-
)}
161-
</Await>
162-
</Suspense>
163-
<>
164-
<PackageSearch
165-
listings={listings}
166-
filters={filters}
167-
config={outletContext.requestConfig}
168-
currentUser={outletContext.currentUser}
169-
dapper={outletContext.dapper}
170-
/>
171-
</>
172-
</section>
173-
</>
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>
174189
);
175190
}
191+
192+
export function ErrorBoundary() {
193+
return <NimbusDefaultRouteErrorBoundary />;
194+
}

0 commit comments

Comments
 (0)