Skip to content

Commit

Permalink
feat: modules tab
Browse files Browse the repository at this point in the history
  • Loading branch information
jog1t committed Sep 21, 2024
1 parent 35779e5 commit cbc99dd
Show file tree
Hide file tree
Showing 16 changed files with 796 additions and 91 deletions.
12 changes: 4 additions & 8 deletions apps/hub/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { Suspense } from "react";
import { ThirdPartyProviders } from "./components/third-party-providers";
import { AuthProvider, useAuth } from "./domains/auth/contexts/auth";
import { routeMasks } from "./lib/route-masks";
import { queryClient, queryClientPersister } from "./queries/global";
import { routeTree } from "./routeTree.gen";
Expand Down Expand Up @@ -42,8 +41,7 @@ export const router = createRouter({
});

function InnerApp() {
const auth = useAuth();
return <RouterProvider router={router} context={{ auth }} />;
return <RouterProvider router={router} />;
}

export function App() {
Expand All @@ -55,11 +53,9 @@ export function App() {
<ConfigProvider value={getConfig()}>
<ThirdPartyProviders>
<Suspense fallback={<FullscreenLoading />}>
<AuthProvider>
<TooltipProvider>
<InnerApp />
</TooltipProvider>
</AuthProvider>
<TooltipProvider>
<InnerApp />
</TooltipProvider>
</Suspense>

<Toaster />
Expand Down
42 changes: 42 additions & 0 deletions apps/hub/src/components/featured-modules-grid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Grid, H2 } from "@rivet-gg/components";
import { motion } from "framer-motion";
import type { ReactNode } from "react";
import OpenGBMeta from "../../vendor/opengb-modules-meta.json";
import { ModuleCard } from "./module-card";

const FEATURED_MODULES = ["lobbies", "friends", "analytics"];

interface FeaturesModulesGridProps {
footer?: ReactNode;
}

export function FeaturesModulesGrid({ footer }: FeaturesModulesGridProps) {
const modules = OpenGBMeta.categories
.flatMap((category) => category.modules)
.filter((module) => FEATURED_MODULES.includes(module.id));

return (
<motion.section
className="mt-12"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<H2 className="my-4 text-base">Extend your game with modules</H2>

<Grid
columns={{ initial: "1", sm: "3" }}
gap="4"
items="start"
className="my-4"
>
{modules.map((module) => (
<ModuleCard key={module.id} layoutAnimation={false} {...module} />
))}
</Grid>
{footer}
</motion.section>
);
}

export default FeaturesModulesGrid;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
faHome,
faKey,
faPuzzle,
faPuzzlePiece,
faServer,
} from "@rivet-gg/icons";
import { useSuspenseQueries } from "@tanstack/react-query";
Expand Down Expand Up @@ -64,6 +65,14 @@ export function HeaderNamespaceLinks({
Backend
</Link>
</HeaderLink>
<HeaderLink icon={faPuzzlePiece}>
<Link
to="/games/$gameId/environments/$namespaceId/modules"
params={{ gameId, namespaceId }}
>
Modules
</Link>
</HeaderLink>
</>
{legacyLobbiesEnabled ? (
<>
Expand Down
97 changes: 97 additions & 0 deletions apps/hub/src/components/module-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {
type IconPack,
type IconProp,
library,
} from "@fortawesome/fontawesome-svg-core";
import { fab } from "@fortawesome/free-brands-svg-icons";
import { fas } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Card,
CardDescription,
CardHeader,
CardTitle,
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@rivet-gg/components";
import { motion } from "framer-motion";

const fasFab: IconPack = Object.fromEntries(
Object.entries(fab).map(([iconName, icon]) => [
iconName,
{ ...icon, prefix: "fas" },
]),
);

library.add(fasFab, fas);

const animationProps = {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
};

interface ModuleCardProps {
id: string;
name: string;
description: string;
status: string;
icon: string;
layoutAnimation?: boolean;
}

export function ModuleCard({
id,
name,
icon,
description,
layoutAnimation = true,
}: ModuleCardProps) {
return (
<Sheet>
<SheetTrigger asChild>
<Card
className="text-left hover:border-primary transition-colors"
asChild
>
<motion.button
type="button"
className="h-full flex"
{...(layoutAnimation
? { ...animationProps, layout: "position", layoutId: id }
: {})}
>
<CardHeader className="max-w-full">
<CardTitle>
<FontAwesomeIcon
className="text-primary mb-3 block"
icon={icon as IconProp}
/>
{name}
</CardTitle>
<CardDescription className="break-words">
{description}
</CardDescription>
</CardHeader>
</motion.button>
</Card>
</SheetTrigger>
<SheetContent className="sm:max-w-[500px]">
<SheetHeader>
<SheetTitle>{name} in OpenGB Docs</SheetTitle>
<SheetDescription className="-mx-6">
<iframe
className="w-full h-screen"
src={`https://opengb.dev/modules/${id}/overview`}
title={name}
/>
</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
);
}
14 changes: 14 additions & 0 deletions apps/hub/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ export function isRivetError(
);
}

const rivetLikeObject = z.object({
body: z.object({
message: z.string(),
code: z.string().optional(),
}),
statusCode: z.number().optional(),
});

export function isLikeRivetError(
error: unknown,
): error is z.infer<typeof rivetLikeObject> {
return rivetLikeObject.safeParse(error).success;
}

export function hasMethod<TName extends string>(
obj: unknown,
methodName: TName,
Expand Down
41 changes: 24 additions & 17 deletions apps/hub/src/queries/global.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isRivetError } from "@/lib/utils";
import { isLikeRivetError, isRivetError } from "@/lib/utils";
import { type Rivet, RivetClient } from "@rivet-gg/api";
import { RivetClient as RivetEeClient } from "@rivet-gg/api-ee";
import { fetcher } from "@rivet-gg/api/core";
import { type APIResponse, type Fetcher, fetcher } from "@rivet-gg/api/core";
import { getConfig, toast } from "@rivet-gg/components";
import { broadcastQueryClient } from "@tanstack/query-broadcast-client-experimental";
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
Expand Down Expand Up @@ -48,7 +48,26 @@ export async function getToken() {

const clientOptions: RivetClient.Options = {
environment: getConfig().apiUrl,
fetcher: (args) => fetcher({ ...args, withCredentials: true }),
fetcher: async <R = unknown>(
args: Fetcher.Args,
): Promise<APIResponse<R, Fetcher.Error>> => {
const response = await fetcher<R>({ ...args, withCredentials: true });

if (!response.ok) {
if (
isLikeRivetError(response.error) &&
(response.error.body.code === "CLAIMS_ENTITLEMENT_EXPIRED" ||
response.error.body.code === "TOKEN_REVOKED")
) {
await queryClient.invalidateQueries({
...identityTokenQueryOptions(),
refetchType: "all",
});
}
}

return response;
},
token: getToken,
};

Expand All @@ -59,19 +78,7 @@ export const rivetClientTokeneless = new RivetClient({
export const rivetClient = new RivetClient(clientOptions);
export const rivetEeClient = new RivetEeClient(clientOptions);

const queryCache = new QueryCache({
async onError(error) {
if (isRivetError(error)) {
if (
error.body.code === "CLAIMS_ENTITLEMENT_EXPIRED" ||
error.body.code === "TOKEN_REVOKED"
) {
queryClient.invalidateQueries(identityTokenQueryOptions());
await getToken();
}
}
},
});
const queryCache = new QueryCache();

const mutationCache = new MutationCache({
onError(error, variables, context, mutation) {
Expand All @@ -90,7 +97,7 @@ export const queryClient = new QueryClient({
queries: {
staleTime: 5 * 1000,
gcTime: 1000 * 60 * 60 * 24,
retry: 0,
retry: 1,
},
},
queryCache,
Expand Down
35 changes: 35 additions & 0 deletions apps/hub/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

// This file is auto-generated by TanStack Router

import { createFileRoute } from '@tanstack/react-router'

// Import Routes

import { Route as rootRoute } from './routes/__root'
Expand Down Expand Up @@ -53,6 +55,13 @@ import { Route as AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdLobbiesLo
import { Route as AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdBackendVariablesImport } from './routes/_authenticated/_layout/games/$gameId_/environments/$namespaceId/backend/variables'
import { Route as AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdBackendLogsImport } from './routes/_authenticated/_layout/games/$gameId_/environments/$namespaceId/backend/logs'

// Create Virtual Routes

const AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdModulesLazyImport =
createFileRoute(
'/_authenticated/_layout/games/$gameId/environments/$namespaceId/modules',
)()

// Create/Update Routes

const AuthenticatedRoute = AuthenticatedImport.update({
Expand Down Expand Up @@ -202,6 +211,19 @@ const AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdIndexRoute =
AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdRoute,
} as any)

const AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdModulesLazyRoute =
AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdModulesLazyImport.update(
{
path: '/modules',
getParentRoute: () =>
AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdRoute,
} as any,
).lazy(() =>
import(
'./routes/_authenticated/_layout/games/$gameId_/environments/$namespaceId/modules.lazy'
).then((d) => d.Route),
)

const AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdVersionsRoute =
AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdVersionsImport.update({
path: '/versions',
Expand Down Expand Up @@ -558,6 +580,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdVersionsImport
parentRoute: typeof AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdImport
}
'/_authenticated/_layout/games/$gameId/environments/$namespaceId/modules': {
id: '/_authenticated/_layout/games/$gameId/environments/$namespaceId/modules'
path: '/modules'
fullPath: '/games/$gameId/environments/$namespaceId/modules'
preLoaderRoute: typeof AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdModulesLazyImport
parentRoute: typeof AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdImport
}
'/_authenticated/_layout/games/$gameId/environments/$namespaceId/': {
id: '/_authenticated/_layout/games/$gameId/environments/$namespaceId/'
path: '/'
Expand Down Expand Up @@ -686,6 +715,7 @@ export const routeTree = rootRoute.addChildren({
),
AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdTokensRoute,
AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdVersionsRoute,
AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdModulesLazyRoute,
AuthenticatedLayoutGamesGameIdEnvironmentsNamespaceIdIndexRoute,
}),
}),
Expand Down Expand Up @@ -839,6 +869,7 @@ export const routeTree = rootRoute.addChildren({
"/_authenticated/_layout/games/$gameId/environments/$namespaceId/servers",
"/_authenticated/_layout/games/$gameId/environments/$namespaceId/tokens",
"/_authenticated/_layout/games/$gameId/environments/$namespaceId/versions",
"/_authenticated/_layout/games/$gameId/environments/$namespaceId/modules",
"/_authenticated/_layout/games/$gameId/environments/$namespaceId/"
]
},
Expand Down Expand Up @@ -896,6 +927,10 @@ export const routeTree = rootRoute.addChildren({
"filePath": "_authenticated/_layout/games/$gameId_/environments/$namespaceId/versions.tsx",
"parent": "/_authenticated/_layout/games/$gameId/environments/$namespaceId"
},
"/_authenticated/_layout/games/$gameId/environments/$namespaceId/modules": {
"filePath": "_authenticated/_layout/games/$gameId_/environments/$namespaceId/modules.lazy.tsx",
"parent": "/_authenticated/_layout/games/$gameId/environments/$namespaceId"
},
"/_authenticated/_layout/games/$gameId/environments/$namespaceId/": {
"filePath": "_authenticated/_layout/games/$gameId_/environments/$namespaceId/index.tsx",
"parent": "/_authenticated/_layout/games/$gameId/environments/$namespaceId"
Expand Down
10 changes: 6 additions & 4 deletions apps/hub/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ErrorComponent } from "@/components/error-component";
import { NotFoundComponent } from "@/components/not-found-component";
import type { AuthContext } from "@/domains/auth/contexts/auth";
import { type AuthContext, AuthProvider } from "@/domains/auth/contexts/auth";
import { useDialog } from "@/hooks/use-dialog";
import * as PageLayout from "@/layouts/page";
import * as Layout from "@/layouts/root";
Expand Down Expand Up @@ -97,9 +97,11 @@ function Root() {
function RootRoute() {
return (
<>
{/* <Suspense fallback={<FullscreenLoading />}> */}
<Root />
{/* </Suspense> */}
<AuthProvider>
{/* <Suspense fallback={<FullscreenLoading />}> */}
<Root />
{/* </Suspense> */}
</AuthProvider>

{import.meta.env.DEV ? <TanStackRouterDevtools /> : null}
</>
Expand Down
Loading

0 comments on commit cbc99dd

Please sign in to comment.