Skip to content

Commit c678dc3

Browse files
committed
Enhance error handling in route: /communities
1 parent de2572f commit c678dc3

File tree

2 files changed

+81
-38
lines changed

2 files changed

+81
-38
lines changed

apps/cyberstorm-remix/app/communities/Communities.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@
4242
width: 100%;
4343
}
4444

45+
.communities__error {
46+
display: flex;
47+
flex-direction: column;
48+
gap: 1rem;
49+
align-items: flex-start;
50+
padding: 3rem 0;
51+
}
52+
53+
.communities__error-description {
54+
max-width: 40rem;
55+
color: var(--Color-text-muted, rgb(180 189 255 / 0.8));
56+
}
57+
4558
.communities__community-skeleton {
4659
.communities__community-skeleton-image {
4760
display: flex;

apps/cyberstorm-remix/app/communities/communities.tsx

Lines changed: 68 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import {
66
import { faFire, faGhost } from "@fortawesome/free-solid-svg-icons";
77
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
88
import {
9-
getPublicEnvVariables,
10-
getSessionTools,
11-
} from "cyberstorm/security/publicEnvVariables";
9+
NimbusAwaitErrorElement,
10+
NimbusDefaultRouteErrorBoundary,
11+
} from "cyberstorm/utils/errors/NimbusErrorBoundary";
12+
import { handleLoaderError } from "cyberstorm/utils/errors/handleLoaderError";
13+
import { getLoaderTools } from "cyberstorm/utils/getLoaderTools";
1214
import { Suspense, memo, useEffect, useRef, useState } from "react";
1315
import type { LoaderFunctionArgs, MetaFunction } from "react-router";
1416
import {
@@ -28,10 +30,12 @@ import {
2830
SkeletonBox,
2931
} from "@thunderstore/cyberstorm";
3032
import type { Communities } from "@thunderstore/dapper";
31-
import { DapperTs } from "@thunderstore/dapper-ts";
3233

3334
import "./Communities.css";
3435

36+
/**
37+
* Provides the HTML metadata for the communities listing route.
38+
*/
3539
export const meta: MetaFunction = () => {
3640
return [
3741
{ title: "Communities | Thunderstore" },
@@ -67,48 +71,63 @@ const selectOptions = [
6771
},
6872
];
6973

70-
export async function loader({ request }: LoaderFunctionArgs) {
74+
interface CommunitiesQuery {
75+
order: SortOptions;
76+
search: string | undefined;
77+
}
78+
79+
/**
80+
* Extracts the current query parameters governing the communities list.
81+
*/
82+
function resolveCommunitiesQuery(request: Request): CommunitiesQuery {
7183
const searchParams = new URL(request.url).searchParams;
72-
const order = searchParams.get("order") ?? SortOptions.Popular;
73-
const search = searchParams.get("search");
74-
const page = undefined;
75-
const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]);
76-
const dapper = new DapperTs(() => {
77-
return {
78-
apiHost: publicEnvVariables.VITE_API_URL,
79-
sessionId: undefined,
80-
};
81-
});
84+
const orderParam = searchParams.get("order");
85+
const orderValues = Object.values(SortOptions);
86+
const order =
87+
orderParam && orderValues.includes(orderParam as SortOptions)
88+
? (orderParam as SortOptions)
89+
: SortOptions.Popular;
90+
const search = searchParams.get("search") ?? undefined;
91+
8292
return {
83-
communities: await dapper.getCommunities(
84-
page,
85-
order === null ? undefined : order,
86-
search === null ? undefined : search
87-
),
93+
order,
94+
search,
8895
};
8996
}
9097

91-
export async function clientLoader({ request }: LoaderFunctionArgs) {
92-
const tools = getSessionTools();
93-
const dapper = new DapperTs(() => {
98+
/**
99+
* Fetches communities data on the server and surfaces mapped loader errors.
100+
*/
101+
export async function loader({ request }: LoaderFunctionArgs) {
102+
const query = resolveCommunitiesQuery(request);
103+
const page = undefined;
104+
const { dapper } = getLoaderTools();
105+
try {
94106
return {
95-
apiHost: tools?.getConfig().apiHost,
96-
sessionId: tools?.getConfig().sessionId,
107+
communities: await dapper.getCommunities(page, query.order, query.search),
97108
};
98-
});
99-
const searchParams = new URL(request.url).searchParams;
100-
const order = searchParams.get("order");
101-
const search = searchParams.get("search");
109+
} catch (error) {
110+
handleLoaderError(error);
111+
}
112+
}
113+
114+
/**
115+
* Fetches communities data on the client, returning a Suspense-ready promise wrapper.
116+
*/
117+
export function clientLoader({ request }: LoaderFunctionArgs) {
118+
const { dapper } = getLoaderTools();
119+
const query = resolveCommunitiesQuery(request);
102120
const page = undefined;
103121
return {
104-
communities: dapper.getCommunities(
105-
page,
106-
order ?? SortOptions.Popular,
107-
search ?? ""
108-
),
122+
communities: dapper
123+
.getCommunities(page, query.order, query.search)
124+
.catch((error) => handleLoaderError(error)),
109125
};
110126
}
111127

128+
/**
129+
* Renders the communities listing experience with search, sorting, and Suspense fallback handling.
130+
*/
112131
export default function CommunitiesPage() {
113132
const { communities } = useLoaderData<typeof loader | typeof clientLoader>();
114133
const navigationType = useNavigationType();
@@ -117,6 +136,9 @@ export default function CommunitiesPage() {
117136
// TODO: Disabled until we can figure out how a proper way to display skeletons
118137
// const navigation = useNavigation();
119138

139+
/**
140+
* Persists the selected sort order back into the URL search params.
141+
*/
120142
const changeOrder = (v: SortOptions) => {
121143
if (v === SortOptions.Popular) {
122144
searchParams.delete("order");
@@ -192,11 +214,9 @@ export default function CommunitiesPage() {
192214
<Suspense fallback={<CommunitiesListSkeleton />}>
193215
<Await
194216
resolve={communities}
195-
errorElement={<div>Error loading communities</div>}
217+
errorElement={<NimbusAwaitErrorElement />}
196218
>
197-
{(resolvedValue) => (
198-
<CommunitiesList communitiesData={resolvedValue} />
199-
)}
219+
{(result) => <CommunitiesList communitiesData={result} />}
200220
</Await>
201221
</Suspense>
202222
</div>
@@ -205,6 +225,13 @@ export default function CommunitiesPage() {
205225
);
206226
}
207227

228+
export function ErrorBoundary() {
229+
return <NimbusDefaultRouteErrorBoundary />;
230+
}
231+
232+
/**
233+
* Displays the resolved communities list or an empty state when no entries exist.
234+
*/
208235
const CommunitiesList = memo(function CommunitiesList(props: {
209236
communitiesData: Communities;
210237
}) {
@@ -238,6 +265,9 @@ const CommunitiesList = memo(function CommunitiesList(props: {
238265
}
239266
});
240267

268+
/**
269+
* Shows a skeleton grid while the communities listing resolves.
270+
*/
241271
const CommunitiesListSkeleton = memo(function CommunitiesListSkeleton() {
242272
return (
243273
<div className="communities__communities-list">

0 commit comments

Comments
 (0)