Skip to content

Commit 30b6185

Browse files
committed
Enhance error handling in route: /communities
1 parent cd40580 commit 30b6185

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
@@ -23,13 +23,17 @@ import {
2323
useSearchParams,
2424
} from "react-router";
2525
import type { Communities } from "@thunderstore/dapper/types";
26-
import { DapperTs } from "@thunderstore/dapper-ts";
2726
import { PageHeader } from "~/commonComponents/PageHeader/PageHeader";
2827
import {
29-
getPublicEnvVariables,
30-
getSessionTools,
31-
} from "cyberstorm/security/publicEnvVariables";
28+
NimbusAwaitErrorElement,
29+
NimbusDefaultRouteErrorBoundary,
30+
} from "cyberstorm/utils/errors/NimbusErrorBoundary";
31+
import { handleLoaderError } from "cyberstorm/utils/errors/handleLoaderError";
32+
import { getLoaderTools } from "cyberstorm/utils/getLoaderTools";
3233

34+
/**
35+
* Provides the HTML metadata for the communities listing route.
36+
*/
3337
export const meta: MetaFunction = () => {
3438
return [
3539
{ title: "Communities | Thunderstore" },
@@ -65,48 +69,63 @@ const selectOptions = [
6569
},
6670
];
6771

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

89-
export async function clientLoader({ request }: LoaderFunctionArgs) {
90-
const tools = getSessionTools();
91-
const dapper = new DapperTs(() => {
96+
/**
97+
* Fetches communities data on the server and surfaces mapped loader errors.
98+
*/
99+
export async function loader({ request }: LoaderFunctionArgs) {
100+
const query = resolveCommunitiesQuery(request);
101+
const page = undefined;
102+
const { dapper } = getLoaderTools();
103+
try {
92104
return {
93-
apiHost: tools?.getConfig().apiHost,
94-
sessionId: tools?.getConfig().sessionId,
105+
communities: await dapper.getCommunities(page, query.order, query.search),
95106
};
96-
});
97-
const searchParams = new URL(request.url).searchParams;
98-
const order = searchParams.get("order");
99-
const search = searchParams.get("search");
107+
} catch (error) {
108+
handleLoaderError(error);
109+
}
110+
}
111+
112+
/**
113+
* Fetches communities data on the client, returning a Suspense-ready promise wrapper.
114+
*/
115+
export function clientLoader({ request }: LoaderFunctionArgs) {
116+
const { dapper } = getLoaderTools();
117+
const query = resolveCommunitiesQuery(request);
100118
const page = undefined;
101119
return {
102-
communities: dapper.getCommunities(
103-
page,
104-
order ?? SortOptions.Popular,
105-
search ?? ""
106-
),
120+
communities: dapper
121+
.getCommunities(page, query.order, query.search)
122+
.catch((error) => handleLoaderError(error)),
107123
};
108124
}
109125

126+
/**
127+
* Renders the communities listing experience with search, sorting, and Suspense fallback handling.
128+
*/
110129
export default function CommunitiesPage() {
111130
const { communities } = useLoaderData<typeof loader | typeof clientLoader>();
112131
const navigationType = useNavigationType();
@@ -115,6 +134,9 @@ export default function CommunitiesPage() {
115134
// TODO: Disabled until we can figure out how a proper way to display skeletons
116135
// const navigation = useNavigation();
117136

137+
/**
138+
* Persists the selected sort order back into the URL search params.
139+
*/
118140
const changeOrder = (v: SortOptions) => {
119141
if (v === SortOptions.Popular) {
120142
searchParams.delete("order");
@@ -190,11 +212,9 @@ export default function CommunitiesPage() {
190212
<Suspense fallback={<CommunitiesListSkeleton />}>
191213
<Await
192214
resolve={communities}
193-
errorElement={<div>Error loading communities</div>}
215+
errorElement={<NimbusAwaitErrorElement />}
194216
>
195-
{(resolvedValue) => (
196-
<CommunitiesList communitiesData={resolvedValue} />
197-
)}
217+
{(result) => <CommunitiesList communitiesData={result} />}
198218
</Await>
199219
</Suspense>
200220
</div>
@@ -203,6 +223,13 @@ export default function CommunitiesPage() {
203223
);
204224
}
205225

226+
export function ErrorBoundary() {
227+
return <NimbusDefaultRouteErrorBoundary />;
228+
}
229+
230+
/**
231+
* Displays the resolved communities list or an empty state when no entries exist.
232+
*/
206233
const CommunitiesList = memo(function CommunitiesList(props: {
207234
communitiesData: Communities;
208235
}) {
@@ -236,6 +263,9 @@ const CommunitiesList = memo(function CommunitiesList(props: {
236263
}
237264
});
238265

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

0 commit comments

Comments
 (0)