diff --git a/frontend/package.json b/frontend/package.json index dc560af101..eead94e7ba 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -58,7 +58,7 @@ "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-visually-hidden": "^1.2.3", - "@rivet-gg/cloud": "https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780", + "@rivet-gg/cloud": "https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@bf2ebb2", "@rivet-gg/icons": "workspace:*", "@rivetkit/engine-api-full": "workspace:*", "@sentry/react": "^8.55.0", diff --git a/frontend/src/app/data-providers/engine-data-provider.tsx b/frontend/src/app/data-providers/engine-data-provider.tsx index b36246fd63..6252497441 100644 --- a/frontend/src/app/data-providers/engine-data-provider.tsx +++ b/frontend/src/app/data-providers/engine-data-provider.tsx @@ -78,6 +78,11 @@ export const createGlobalContext = (opts: { return lastPage.pagination.cursor; }, select: (data) => data.pages.flatMap((page) => page.namespaces), + retry: shouldRetryAllExpect403, + throwOnError: noThrow, + meta: { + mightRequireAuth, + }, }); }, createNamespaceMutationOptions(opts: { diff --git a/frontend/src/app/inspector-root.tsx b/frontend/src/app/inspector-root.tsx index b93db3b82a..41877ad226 100644 --- a/frontend/src/app/inspector-root.tsx +++ b/frontend/src/app/inspector-root.tsx @@ -1,5 +1,5 @@ import { - Route, + CatchBoundary, useNavigate, useRouteContext, useSearch, @@ -50,7 +50,14 @@ export function InspectorRoot() { - {!search.n ? : null} + + search.n?.join(",") ?? "no-build-name" + } + errorComponent={() => null} + > + {!search.n ? : null} + ); diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index a2a6ed20b0..e481c960cd 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -41,7 +41,10 @@ import { ScrollArea, Skeleton, } from "@/components"; -import { useInspectorDataProvider } from "@/components/actors"; +import { + useDataProviderCheck, + useInspectorDataProvider, +} from "@/components/actors"; import type { HeaderLinkProps } from "@/components/header/header-link"; import { ensureTrailingSlash } from "@/lib/utils"; import { ActorBuildsList } from "./actor-builds-list"; @@ -388,6 +391,7 @@ const Subnav = () => { } : { to: "/", fuzzy: true }, ); + const hasDataProvider = useDataProviderCheck(); if (nsMatch === false) { return null; @@ -405,12 +409,14 @@ const Subnav = () => { Connect ) : null} -
- - Instances - - -
+ {hasDataProvider ? ( +
+ + Instances + + +
+ ) : null} ); }; @@ -535,6 +541,8 @@ function CloudSidebarContent() { fuzzy: true, }); + const hasDataProvider = useDataProviderCheck(); + if (matchNamespace) { return (
@@ -546,12 +554,14 @@ function CloudSidebarContent() { > Connect -
- - Instances - - -
+ {hasDataProvider ? ( +
+ + Instances + + +
+ ) : null}
); } diff --git a/frontend/src/components/actors/data-provider.tsx b/frontend/src/components/actors/data-provider.tsx index 03eeb183f1..08d1c5257f 100644 --- a/frontend/src/components/actors/data-provider.tsx +++ b/frontend/src/components/actors/data-provider.tsx @@ -1,6 +1,8 @@ import { type RegisteredRouter, type RouteIds, + useMatches, + useMatchRoute, useRouteContext, } from "@tanstack/react-router"; import { match } from "ts-pattern"; @@ -34,6 +36,26 @@ export const useDataProvider = () => }) .exhaustive(); +export const useDataProviderCheck = () => { + const matchRoute = useMatchRoute(); + + return matchRoute({ + to: match(__APP_TYPE__) + .with("cloud", () => { + return "/orgs/$organization/projects/$project/ns/$namespace" as const; + }) + .with("engine", () => { + return "/ns/$namespace" as const; + }) + .with("inspector", () => { + return "/" as const; + }) + .otherwise(() => { + throw new Error("Not in a valid context"); + }), + }); +}; + export const useEngineDataProvider = () => { return useRouteContext({ from: "/_context/_engine", diff --git a/frontend/src/components/hooks/use-dialog.tsx b/frontend/src/components/hooks/use-dialog.tsx index 0801dbd6bc..cd2086f9fa 100644 --- a/frontend/src/components/hooks/use-dialog.tsx +++ b/frontend/src/components/hooks/use-dialog.tsx @@ -46,9 +46,24 @@ export const createDialogHook = < return ( - + {} + : props.dialogProps?.onOpenChange + } + > { if (opts.autoFocus === false) { return e.preventDefault(); diff --git a/frontend/src/components/modal-renderer.tsx b/frontend/src/components/modal-renderer.tsx index 658d439df8..cc830948fb 100644 --- a/frontend/src/components/modal-renderer.tsx +++ b/frontend/src/components/modal-renderer.tsx @@ -34,7 +34,7 @@ export function ModalRenderer() { function getDialogComponent(dialogKey: string) { const dialogs = useDialog; - const dialog = dialogs[dialogKey]; + const dialog = dialogs[dialogKey as keyof typeof dialogs]; if (!dialog || typeof dialog !== "function") { return null; diff --git a/frontend/src/queries/global.ts b/frontend/src/queries/global.ts index a4f137134c..1a28f02026 100644 --- a/frontend/src/queries/global.ts +++ b/frontend/src/queries/global.ts @@ -15,7 +15,7 @@ const queryCache = new QueryCache({ "statusCode" in error && error.statusCode === 403 ) { - modal.open("ProvideEngineCredentials"); + modal.open("ProvideEngineCredentials", { dismissible: false }); return; } }, diff --git a/frontend/src/routes/_context.tsx b/frontend/src/routes/_context.tsx index a788524853..5d9411cd75 100644 --- a/frontend/src/routes/_context.tsx +++ b/frontend/src/routes/_context.tsx @@ -43,7 +43,7 @@ export const Route = createFileRoute("/_context")({ return match(__APP_TYPE__) .with("engine", () => ({ dataProvider: createGlobalEngineContext({ - engineToken: + engineToken: () => ls.engineCredentials.get(getConfig().apiUrl) || "", }), __type: "engine" as const, diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/index.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/index.tsx index 6e6eeca120..d0c2f721af 100644 --- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/index.tsx +++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/index.tsx @@ -45,7 +45,12 @@ export function RouteComponent() { <> actorId ?? "no-actor-id"}> - {!n ? : null} + n?.join(",") ?? "no-build-name"} + errorComponent={() => null} + > + {!n ? : null} + ); diff --git a/frontend/src/routes/_context/_engine/ns.$namespace/index.tsx b/frontend/src/routes/_context/_engine/ns.$namespace/index.tsx index e1d1057bf3..b9ba95023d 100644 --- a/frontend/src/routes/_context/_engine/ns.$namespace/index.tsx +++ b/frontend/src/routes/_context/_engine/ns.$namespace/index.tsx @@ -1,4 +1,8 @@ -import { createFileRoute, useSearch } from "@tanstack/react-router"; +import { + CatchBoundary, + createFileRoute, + useSearch, +} from "@tanstack/react-router"; import { Actors } from "@/app/actors"; import { BuildPrefiller } from "@/app/build-prefiller"; @@ -12,7 +16,13 @@ export function RouteComponent() { return ( <> - {!n ? : null} + + n?.join(",") ?? "no-build-name"} + errorComponent={() => null} + > + {!n ? : null} + ); } diff --git a/frontend/src/routes/_context/index.tsx b/frontend/src/routes/_context/index.tsx index d7b21814be..0589915250 100644 --- a/frontend/src/routes/_context/index.tsx +++ b/frontend/src/routes/_context/index.tsx @@ -1,4 +1,4 @@ -import { createFileRoute, redirect } from "@tanstack/react-router"; +import { createFileRoute, isRedirect, redirect } from "@tanstack/react-router"; import { match } from "ts-pattern"; import CreateNamespacesFrameContent from "@/app/dialogs/create-namespace-frame"; import { InspectorRoot } from "@/app/inspector-root"; @@ -27,17 +27,27 @@ export const Route = createFileRoute("/_context/")({ }); }) .with({ __type: "engine" }, async (ctx) => { - const result = await ctx.queryClient.fetchInfiniteQuery( - ctx.dataProvider.namespacesQueryOptions(), - ); - const firstNamespace = result.pages[0]?.namespaces[0]; - if (!firstNamespace) { + try { + const result = await ctx.queryClient.fetchInfiniteQuery( + ctx.dataProvider.namespacesQueryOptions(), + ); + + const firstNamespace = result.pages[0]?.namespaces[0]; + if (!firstNamespace) { + return; + } + throw redirect({ + to: "/ns/$namespace", + params: { namespace: firstNamespace.name }, + }); + } catch (e) { + if (isRedirect(e)) { + throw e; + } + + // Ignore errors here, they will be handled in the UI return; } - throw redirect({ - to: "/ns/$namespace", - params: { namespace: firstNamespace.name }, - }); }) .with({ __type: "inspector" }, async (ctx) => { if (!search.t || !search.u) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ea28d1d66..6d8c28e60a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -177,8 +177,8 @@ importers: specifier: ^1.2.3 version: 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@rivet-gg/cloud': - specifier: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780 - version: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780 + specifier: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@bf2ebb2 + version: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@bf2ebb2 '@rivet-gg/icons': specifier: workspace:* version: link:packages/icons @@ -858,6 +858,9 @@ importers: '@rivet-gg/api': specifier: 0.0.1-rc8 version: 0.0.1-rc8 + '@rivet-gg/cloud': + specifier: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@bf2ebb2 + version: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@bf2ebb2 '@rivet-gg/components': specifier: workspace:* version: link:../frontend/packages/components @@ -3184,8 +3187,8 @@ packages: '@rivet-gg/api@0.0.1-rc8': resolution: {integrity: sha512-aGJVImxsmz8fLLzeZHUlFRJ/7Y/xBrke9bOvMpooVaJpHor/XmiP19QeEtB2hmQUOPlgS3dz5o8UtCZ5+LcQGg==} - '@rivet-gg/cloud@https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780': - resolution: {tarball: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780} + '@rivet-gg/cloud@https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@bf2ebb2': + resolution: {tarball: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@bf2ebb2} version: 0.0.0 '@rivetkit/engine-runner-protocol@https://pkg.pr.new/rivet-dev/engine/@rivetkit/engine-runner-protocol@b72b2324c50c5449ed1844a060928d80d1151839': @@ -10470,7 +10473,7 @@ snapshots: transitivePeerDependencies: - debug - '@rivet-gg/cloud@https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780': + '@rivet-gg/cloud@https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@bf2ebb2': dependencies: cross-fetch: 4.1.0 form-data: 4.0.4 @@ -12540,7 +12543,7 @@ snapshots: eslint: 8.26.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 2.7.1(eslint-plugin-import@2.32.0(eslint@8.26.0))(eslint@8.26.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.26.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.32.0(eslint@8.26.0))(eslint@8.26.0))(eslint@8.26.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.26.0) eslint-plugin-react: 7.37.5(eslint@8.26.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.26.0) @@ -12562,7 +12565,7 @@ snapshots: dependencies: debug: 4.4.1 eslint: 8.26.0 - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.26.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.32.0(eslint@8.26.0))(eslint@8.26.0))(eslint@8.26.0) glob: 7.2.3 is-glob: 4.0.3 resolve: 1.22.10 @@ -12581,7 +12584,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.26.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.32.0(eslint@8.26.0))(eslint@8.26.0))(eslint@8.26.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9