Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions apps/cyberstorm-remix/app/communities/Communities.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@
width: 100%;
}

.communities__error {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: flex-start;
padding: 3rem 0;
}

.communities__error-description {
max-width: 40rem;
color: var(--Color-text-muted, rgb(180 189 255 / 0.8));
}

.communities__community-skeleton {
.communities__community-skeleton-image {
display: flex;
Expand Down
106 changes: 68 additions & 38 deletions apps/cyberstorm-remix/app/communities/communities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
import { faFire, faGhost } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
getPublicEnvVariables,
getSessionTools,
} from "cyberstorm/security/publicEnvVariables";
NimbusAwaitErrorElement,
NimbusDefaultRouteErrorBoundary,
} from "cyberstorm/utils/errors/NimbusErrorBoundary";
import { handleLoaderError } from "cyberstorm/utils/errors/handleLoaderError";
import { getLoaderTools } from "cyberstorm/utils/getLoaderTools";
import { Suspense, memo, useEffect, useRef, useState } from "react";
import type { LoaderFunctionArgs, MetaFunction } from "react-router";
import {
Expand All @@ -28,10 +30,12 @@ import {
SkeletonBox,
} from "@thunderstore/cyberstorm";
import type { Communities } from "@thunderstore/dapper";
import { DapperTs } from "@thunderstore/dapper-ts";

import "./Communities.css";

/**
* Provides the HTML metadata for the communities listing route.
*/
export const meta: MetaFunction = () => {
return [
{ title: "Communities | Thunderstore" },
Expand Down Expand Up @@ -67,48 +71,63 @@ const selectOptions = [
},
];

export async function loader({ request }: LoaderFunctionArgs) {
interface CommunitiesQuery {
order: SortOptions;
search: string | undefined;
}

/**
* Extracts the current query parameters governing the communities list.
*/
function resolveCommunitiesQuery(request: Request): CommunitiesQuery {
const searchParams = new URL(request.url).searchParams;
const order = searchParams.get("order") ?? SortOptions.Popular;
const search = searchParams.get("search");
const page = undefined;
const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]);
const dapper = new DapperTs(() => {
return {
apiHost: publicEnvVariables.VITE_API_URL,
sessionId: undefined,
};
});
const orderParam = searchParams.get("order");
const orderValues = Object.values(SortOptions);
const order =
orderParam && orderValues.includes(orderParam as SortOptions)
? (orderParam as SortOptions)
: SortOptions.Popular;
const search = searchParams.get("search") ?? undefined;

return {
communities: await dapper.getCommunities(
page,
order === null ? undefined : order,
search === null ? undefined : search
),
order,
search,
};
}

export async function clientLoader({ request }: LoaderFunctionArgs) {
const tools = getSessionTools();
const dapper = new DapperTs(() => {
/**
* Fetches communities data on the server and surfaces mapped loader errors.
*/
export async function loader({ request }: LoaderFunctionArgs) {
const query = resolveCommunitiesQuery(request);
const page = undefined;
const { dapper } = getLoaderTools();
try {
return {
apiHost: tools?.getConfig().apiHost,
sessionId: tools?.getConfig().sessionId,
communities: await dapper.getCommunities(page, query.order, query.search),
};
});
const searchParams = new URL(request.url).searchParams;
const order = searchParams.get("order");
const search = searchParams.get("search");
} catch (error) {
handleLoaderError(error);
}
}

/**
* Fetches communities data on the client, returning a Suspense-ready promise wrapper.
*/
export function clientLoader({ request }: LoaderFunctionArgs) {
const { dapper } = getLoaderTools();
const query = resolveCommunitiesQuery(request);
const page = undefined;
return {
communities: dapper.getCommunities(
page,
order ?? SortOptions.Popular,
search ?? ""
),
communities: dapper
.getCommunities(page, query.order, query.search)
.catch((error) => handleLoaderError(error)),
};
}

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

/**
* Persists the selected sort order back into the URL search params.
*/
const changeOrder = (v: SortOptions) => {
if (v === SortOptions.Popular) {
searchParams.delete("order");
Expand Down Expand Up @@ -192,11 +214,9 @@ export default function CommunitiesPage() {
<Suspense fallback={<CommunitiesListSkeleton />}>
<Await
resolve={communities}
errorElement={<div>Error loading communities</div>}
errorElement={<NimbusAwaitErrorElement />}
>
{(resolvedValue) => (
<CommunitiesList communitiesData={resolvedValue} />
)}
{(result) => <CommunitiesList communitiesData={result} />}
</Await>
</Suspense>
</div>
Expand All @@ -205,6 +225,13 @@ export default function CommunitiesPage() {
);
}

export function ErrorBoundary() {
return <NimbusDefaultRouteErrorBoundary />;
}

/**
* Displays the resolved communities list or an empty state when no entries exist.
*/
const CommunitiesList = memo(function CommunitiesList(props: {
communitiesData: Communities;
}) {
Expand Down Expand Up @@ -238,6 +265,9 @@ const CommunitiesList = memo(function CommunitiesList(props: {
}
});

/**
* Shows a skeleton grid while the communities listing resolves.
*/
const CommunitiesListSkeleton = memo(function CommunitiesListSkeleton() {
return (
<div className="communities__communities-list">
Expand Down
Loading