Skip to content

Commit 995f034

Browse files
committed
Enhance error handling in route: /communities
1 parent 5ac0d36 commit 995f034

File tree

2 files changed

+76
-34
lines changed

2 files changed

+76
-34
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: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ import type { Communities } from "@thunderstore/dapper/types";
3232

3333
import "./Communities.css";
3434

35+
/**
36+
* Provides the HTML metadata for the communities listing route.
37+
*/
3538
export const meta: MetaFunction = () => {
3639
return [
3740
{ title: "Communities | Thunderstore" },
@@ -67,48 +70,63 @@ const selectOptions = [
6770
},
6871
];
6972

70-
export async function loader({ request }: LoaderFunctionArgs) {
73+
interface CommunitiesQuery {
74+
order: SortOptions;
75+
search: string | undefined;
76+
}
77+
78+
/**
79+
* Extracts the current query parameters governing the communities list.
80+
*/
81+
function resolveCommunitiesQuery(request: Request): CommunitiesQuery {
7182
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-
});
83+
const orderParam = searchParams.get("order");
84+
const orderValues = Object.values(SortOptions);
85+
const order =
86+
orderParam && orderValues.includes(orderParam as SortOptions)
87+
? (orderParam as SortOptions)
88+
: SortOptions.Popular;
89+
const search = searchParams.get("search") ?? undefined;
90+
8291
return {
83-
communities: await dapper.getCommunities(
84-
page,
85-
order === null ? undefined : order,
86-
search === null ? undefined : search
87-
),
92+
order,
93+
search,
8894
};
8995
}
9096

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

127+
/**
128+
* Renders the communities listing experience with search, sorting, and Suspense fallback handling.
129+
*/
112130
export default function CommunitiesPage() {
113131
const { communities } = useLoaderData<typeof loader | typeof clientLoader>();
114132
const navigationType = useNavigationType();
@@ -117,6 +135,9 @@ export default function CommunitiesPage() {
117135
// TODO: Disabled until we can figure out how a proper way to display skeletons
118136
// const navigation = useNavigation();
119137

138+
/**
139+
* Persists the selected sort order back into the URL search params.
140+
*/
120141
const changeOrder = (v: SortOptions) => {
121142
if (v === SortOptions.Popular) {
122143
searchParams.delete("order");
@@ -192,11 +213,9 @@ export default function CommunitiesPage() {
192213
<Suspense fallback={<CommunitiesListSkeleton />}>
193214
<Await
194215
resolve={communities}
195-
errorElement={<div>Error loading communities</div>}
216+
errorElement={<NimbusAwaitErrorElement />}
196217
>
197-
{(resolvedValue) => (
198-
<CommunitiesList communitiesData={resolvedValue} />
199-
)}
218+
{(result) => <CommunitiesList communitiesData={result} />}
200219
</Await>
201220
</Suspense>
202221
</div>
@@ -205,6 +224,13 @@ export default function CommunitiesPage() {
205224
);
206225
}
207226

227+
export function ErrorBoundary() {
228+
return <NimbusDefaultRouteErrorBoundary />;
229+
}
230+
231+
/**
232+
* Displays the resolved communities list or an empty state when no entries exist.
233+
*/
208234
const CommunitiesList = memo(function CommunitiesList(props: {
209235
communitiesData: Communities;
210236
}) {
@@ -238,6 +264,9 @@ const CommunitiesList = memo(function CommunitiesList(props: {
238264
}
239265
});
240266

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

0 commit comments

Comments
 (0)