diff --git a/frontend/src/app/serverless-connection-check.tsx b/frontend/src/app/serverless-connection-check.tsx
new file mode 100644
index 0000000000..f3b3a8f55f
--- /dev/null
+++ b/frontend/src/app/serverless-connection-check.tsx
@@ -0,0 +1,186 @@
+import {
+ faCheck,
+ faSpinnerThird,
+ faTriangleExclamation,
+ Icon,
+} from "@rivet-gg/icons";
+import type { Rivet } from "@rivetkit/engine-api-full";
+import { useQuery } from "@tanstack/react-query";
+import { AnimatePresence, motion } from "framer-motion";
+import { useEffect } from "react";
+import { useController, useWatch } from "react-hook-form";
+import { match, P } from "ts-pattern";
+import { useDebounceValue } from "usehooks-ts";
+import z from "zod";
+import { cn } from "@/components";
+import { useEngineCompatDataProvider } from "@/components/actors";
+
+export const endpointSchema = z
+ .string()
+ .nonempty("Endpoint is required")
+ .url("Please enter a valid URL")
+ .endsWith("/api/rivet", "Endpoint must end with /api/rivet");
+
+interface ServerlessConnectionCheckProps {
+ providerLabel: string;
+ /** How often to poll the runner health endpoint. */
+ pollIntervalMs?: number;
+}
+
+export function ServerlessConnectionCheck({
+ providerLabel,
+ pollIntervalMs = 3_000,
+}: ServerlessConnectionCheckProps) {
+ const dataProvider = useEngineCompatDataProvider();
+
+ const endpoint: string = useWatch({ name: "endpoint" });
+ const headers: [string, string][] = useWatch({ name: "headers" });
+
+ const enabled =
+ Boolean(endpoint) && endpointSchema.safeParse(endpoint).success;
+
+ const [debouncedEndpoint] = useDebounceValue(endpoint, 300);
+ const [debouncedHeaders] = useDebounceValue(headers, 300);
+
+ const { isSuccess, data, isError, isRefetchError, isLoadingError, error } =
+ useQuery({
+ ...dataProvider.runnerHealthCheckQueryOptions({
+ runnerUrl: debouncedEndpoint,
+ headers: Object.fromEntries(
+ (debouncedHeaders || [])
+ .filter(([k, v]) => k && v)
+ .map(([k, v]) => [k, v]),
+ ),
+ }),
+ enabled,
+ retry: 0,
+ refetchInterval: pollIntervalMs,
+ });
+
+ const {
+ field: { onChange },
+ } = useController({ name: "success" });
+
+ useEffect(() => {
+ onChange(isSuccess);
+ }, [isSuccess, onChange]);
+
+ return (
+
+ {enabled ? (
+
+ {isSuccess ? (
+ <>
+
+ {providerLabel} is running with RivetKit{" "}
+ {(data as any)?.version}
+ >
+ ) : isError || isRefetchError || isLoadingError ? (
+
+
+
+ Health check failed, verify the endpoint is
+ correct.
+
+ {isRivetHealthCheckFailureResponse(error) ? (
+
+ ) : null}
+
+ Endpoint{" "}
+
+ {endpoint}
+
+
+
+ ) : (
+
+
+
+ Waiting for Runner to connect...
+
+
+ )}
+
+ ) : null}
+
+ );
+}
+
+function isRivetHealthCheckFailureResponse(
+ error: any,
+): error is Rivet.RunnerConfigsServerlessHealthCheckResponseFailure["failure"] {
+ return error && "error" in error;
+}
+
+function HealthCheckFailure({
+ error,
+}: {
+ error: Rivet.RunnerConfigsServerlessHealthCheckResponseFailure["failure"];
+}) {
+ if (!("error" in error)) {
+ return null;
+ }
+ if (!error.error) {
+ return null;
+ }
+
+ return match(error.error)
+ .with({ nonSuccessStatus: P.any }, (e) => {
+ return (
+
+ Health check failed with status{" "}
+ {e.nonSuccessStatus.statusCode}
+
+ );
+ })
+ .with({ invalidRequest: P.any }, () => {
+ return
Health check failed because the request was invalid.
;
+ })
+ .with({ invalidResponseJson: P.any }, () => {
+ return (
+
+ Health check failed because the response was not valid JSON.
+
+ );
+ })
+ .with({ requestFailed: P.any }, () => {
+ return (
+
+ Health check failed because the request could not be
+ completed.
+
+ );
+ })
+ .with({ requestTimedOut: P.any }, () => {
+ return
Health check failed because the request timed out.
;
+ })
+ .with({ invalidResponseSchema: P.any }, () => {
+ return (
+
Health check failed because the response was not valid.
+ );
+ })
+ .exhaustive();
+}
diff --git a/frontend/src/app/use-dialog.tsx b/frontend/src/app/use-dialog.tsx
index 14f84484b7..9ecc317bce 100644
--- a/frontend/src/app/use-dialog.tsx
+++ b/frontend/src/app/use-dialog.tsx
@@ -23,6 +23,9 @@ export const useDialog = {
ConnectManual: createDialogHook(
() => import("@/app/dialogs/connect-manual-frame"),
),
+ ConnectCloudflare: createDialogHook(
+ () => import("@/app/dialogs/connect-cloudflare-frame"),
+ ),
ConnectAws: createDialogHook(
() => import("@/app/dialogs/connect-aws-frame"),
),
@@ -47,4 +50,7 @@ export const useDialog = {
CreateApiToken: createDialogHook(
() => import("@/app/dialogs/create-api-token-frame"),
),
+ StartWithTemplate: createDialogHook(
+ () => import("@/app/dialogs/start-with-template-frame"),
+ ),
};
diff --git a/frontend/src/components/cloud-organization-select.tsx b/frontend/src/components/cloud-organization-select.tsx
new file mode 100644
index 0000000000..24c7161f81
--- /dev/null
+++ b/frontend/src/components/cloud-organization-select.tsx
@@ -0,0 +1,100 @@
+import { useOrganizationList } from "@clerk/clerk-react";
+import { faPlus, Icon } from "@rivet-gg/icons";
+import { type ComponentProps, useCallback } from "react";
+import {
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+ Skeleton,
+} from "@/components";
+import { VisibilitySensor } from "@/components/visibility-sensor";
+
+interface CloudOrganizationSelectProps extends ComponentProps
{
+ showCreateOrganization?: boolean;
+ onCreateClick?: () => void;
+}
+
+export function CloudOrganizationSelect({
+ showCreateOrganization,
+ onCreateClick,
+ onValueChange,
+ ...props
+}: CloudOrganizationSelectProps) {
+ const {
+ userMemberships: { data = [], isLoading, hasNextPage, fetchNext },
+ } = useOrganizationList({
+ userMemberships: {
+ infinite: true,
+ },
+ });
+
+ const handleValueChange = useCallback(
+ (value: string) => {
+ if (value === "create") {
+ onCreateClick?.();
+ return;
+ }
+ onValueChange?.(value);
+ },
+ [onCreateClick, onValueChange],
+ );
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/cloud-project-select.tsx b/frontend/src/components/cloud-project-select.tsx
new file mode 100644
index 0000000000..16d8cfa8aa
--- /dev/null
+++ b/frontend/src/components/cloud-project-select.tsx
@@ -0,0 +1,92 @@
+import { faPlus, Icon } from "@rivet-gg/icons";
+import { useInfiniteQuery } from "@tanstack/react-query";
+import { type ComponentProps, useCallback } from "react";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+ Skeleton,
+} from "@/components";
+import { useCloudDataProvider } from "@/components/actors";
+import { VisibilitySensor } from "@/components/visibility-sensor";
+
+interface CloudProjectSelectProps extends ComponentProps {
+ organization: string;
+ showCreateProject?: boolean;
+ onCreateClick?: () => void;
+}
+
+export function CloudProjectSelect({
+ showCreateProject,
+ onCreateClick,
+ onValueChange,
+ organization,
+ ...props
+}: CloudProjectSelectProps) {
+ const dataProvider = useCloudDataProvider();
+ const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
+ useInfiniteQuery(
+ dataProvider.projectsQueryOptions({
+ organization,
+ }),
+ );
+
+ const handleValueChange = useCallback(
+ (value: string) => {
+ if (value === "create") {
+ onCreateClick?.();
+ return;
+ }
+ onValueChange?.(value);
+ },
+ [onCreateClick, onValueChange],
+ );
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts
index af2fd6dd30..7ade501343 100644
--- a/frontend/src/components/index.ts
+++ b/frontend/src/components/index.ts
@@ -4,6 +4,8 @@ export { toast } from "sonner";
export * from "./action-card";
export * from "./animated-currency";
export * from "./asset-image";
+export * from "./cloud-organization-select";
+export * from "./cloud-project-select";
export * from "./code";
export * from "./code-preview/code-preview";
export * from "./copy-area";
diff --git a/frontend/src/components/tailwind-base.ts b/frontend/src/components/tailwind-base.ts
index a11d21957c..ee158c781a 100644
--- a/frontend/src/components/tailwind-base.ts
+++ b/frontend/src/components/tailwind-base.ts
@@ -52,6 +52,16 @@ const config = {
"monospace",
],
},
+ backgroundImage: {
+ "card-fade":
+ "linear-gradient(to bottom, transparent 0%, transparent 30%, hsl(var(--card) / 30%) 60%, hsl(var(--card) / 100%) 100%)",
+ },
+ dropShadow: {
+ "dialog-close": [
+ "0 0 8px rgb(0 0 0 / 1)",
+ "0 4px 3px rgb(0 0 0 / 1)",
+ ],
+ },
data: {
active: 'status~="active"',
open: 'state*="open"',
diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx
index 476e4323c1..57d6da82e0 100644
--- a/frontend/src/components/ui/dialog.tsx
+++ b/frontend/src/components/ui/dialog.tsx
@@ -48,7 +48,10 @@ const DialogContent = React.forwardRef<
{children}
{hideClose ? null : (
-
+
Close
)}
diff --git a/frontend/src/lib/auth.ts b/frontend/src/lib/auth.ts
index 8da8f0304e..60f649baa2 100644
--- a/frontend/src/lib/auth.ts
+++ b/frontend/src/lib/auth.ts
@@ -7,11 +7,15 @@ export const clerk =
? new Clerk(cloudEnv().VITE_APP_CLERK_PUBLISHABLE_KEY)
: (null as unknown as Clerk);
-export const redirectToOrganization = async (clerk: Clerk) => {
+export const redirectToOrganization = async (
+ clerk: Clerk,
+ search: Record,
+) => {
if (clerk.user) {
if (clerk.organization) {
throw redirect({
- to: "/orgs/$organization",
+ to: search.from ?? "/orgs/$organization",
+ search: true,
params: {
organization: clerk.organization.id,
},
@@ -22,12 +26,14 @@ export const redirectToOrganization = async (clerk: Clerk) => {
if (orgs.length > 0) {
await clerk.setActive({ organization: orgs[0].organization.id });
throw redirect({
- to: "/orgs/$organization",
+ to: search.from ?? "/orgs/$organization",
+ search: true,
params: { organization: orgs[0].organization.id },
});
}
throw redirect({
to: "/onboarding/choose-organization",
+ search: true,
});
}
diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts
index 619fe8d6fb..ab97c0a52b 100644
--- a/frontend/src/routeTree.gen.ts
+++ b/frontend/src/routeTree.gen.ts
@@ -19,12 +19,15 @@ import { Route as OnboardingChooseOrganizationRouteImport } from './routes/onboa
import { Route as OnboardingAcceptInvitationRouteImport } from './routes/onboarding/accept-invitation'
import { Route as ContextEngineRouteImport } from './routes/_context/_engine'
import { Route as ContextCloudRouteImport } from './routes/_context/_cloud'
+import { Route as ContextCloudNewIndexRouteImport } from './routes/_context/_cloud/new/index'
import { Route as ContextEngineNsNamespaceRouteImport } from './routes/_context/_engine/ns.$namespace'
import { Route as ContextCloudOrgsOrganizationRouteImport } from './routes/_context/_cloud/orgs.$organization'
+import { Route as ContextCloudNewTemplateRouteImport } from './routes/_context/_cloud/new/$template'
import { Route as ContextEngineNsNamespaceIndexRouteImport } from './routes/_context/_engine/ns.$namespace/index'
import { Route as ContextCloudOrgsOrganizationIndexRouteImport } from './routes/_context/_cloud/orgs.$organization/index'
import { Route as ContextEngineNsNamespaceConnectRouteImport } from './routes/_context/_engine/ns.$namespace/connect'
import { Route as ContextCloudOrgsOrganizationProjectsIndexRouteImport } from './routes/_context/_cloud/orgs.$organization/projects.index'
+import { Route as ContextCloudOrgsOrganizationNewIndexRouteImport } from './routes/_context/_cloud/orgs.$organization/new/index'
import { Route as ContextCloudOrgsOrganizationProjectsProjectRouteImport } from './routes/_context/_cloud/orgs.$organization/projects.$project'
import { Route as ContextCloudOrgsOrganizationProjectsProjectIndexRouteImport } from './routes/_context/_cloud/orgs.$organization/projects.$project/index'
import { Route as ContextCloudOrgsOrganizationProjectsProjectNsNamespaceRouteImport } from './routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace'
@@ -81,6 +84,11 @@ const ContextCloudRoute = ContextCloudRouteImport.update({
id: '/_cloud',
getParentRoute: () => ContextRoute,
} as any)
+const ContextCloudNewIndexRoute = ContextCloudNewIndexRouteImport.update({
+ id: '/new/',
+ path: '/new/',
+ getParentRoute: () => ContextCloudRoute,
+} as any)
const ContextEngineNsNamespaceRoute =
ContextEngineNsNamespaceRouteImport.update({
id: '/ns/$namespace',
@@ -93,6 +101,11 @@ const ContextCloudOrgsOrganizationRoute =
path: '/orgs/$organization',
getParentRoute: () => ContextCloudRoute,
} as any)
+const ContextCloudNewTemplateRoute = ContextCloudNewTemplateRouteImport.update({
+ id: '/new/$template',
+ path: '/new/$template',
+ getParentRoute: () => ContextCloudRoute,
+} as any)
const ContextEngineNsNamespaceIndexRoute =
ContextEngineNsNamespaceIndexRouteImport.update({
id: '/',
@@ -117,6 +130,12 @@ const ContextCloudOrgsOrganizationProjectsIndexRoute =
path: '/projects/',
getParentRoute: () => ContextCloudOrgsOrganizationRoute,
} as any)
+const ContextCloudOrgsOrganizationNewIndexRoute =
+ ContextCloudOrgsOrganizationNewIndexRouteImport.update({
+ id: '/new/',
+ path: '/new/',
+ getParentRoute: () => ContextCloudOrgsOrganizationRoute,
+ } as any)
const ContextCloudOrgsOrganizationProjectsProjectRoute =
ContextCloudOrgsOrganizationProjectsProjectRouteImport.update({
id: '/projects/$project',
@@ -171,12 +190,15 @@ export interface FileRoutesByFullPath {
'/onboarding/accept-invitation': typeof OnboardingAcceptInvitationRoute
'/onboarding/choose-organization': typeof OnboardingChooseOrganizationRoute
'/': typeof ContextIndexRoute
+ '/new/$template': typeof ContextCloudNewTemplateRoute
'/orgs/$organization': typeof ContextCloudOrgsOrganizationRouteWithChildren
'/ns/$namespace': typeof ContextEngineNsNamespaceRouteWithChildren
+ '/new': typeof ContextCloudNewIndexRoute
'/ns/$namespace/connect': typeof ContextEngineNsNamespaceConnectRoute
'/orgs/$organization/': typeof ContextCloudOrgsOrganizationIndexRoute
'/ns/$namespace/': typeof ContextEngineNsNamespaceIndexRoute
'/orgs/$organization/projects/$project': typeof ContextCloudOrgsOrganizationProjectsProjectRouteWithChildren
+ '/orgs/$organization/new': typeof ContextCloudOrgsOrganizationNewIndexRoute
'/orgs/$organization/projects': typeof ContextCloudOrgsOrganizationProjectsIndexRoute
'/orgs/$organization/projects/$project/': typeof ContextCloudOrgsOrganizationProjectsProjectIndexRoute
'/orgs/$organization/projects/$project/ns/$namespace': typeof ContextCloudOrgsOrganizationProjectsProjectNsNamespaceRouteWithChildren
@@ -192,9 +214,12 @@ export interface FileRoutesByTo {
'/onboarding/accept-invitation': typeof OnboardingAcceptInvitationRoute
'/onboarding/choose-organization': typeof OnboardingChooseOrganizationRoute
'/': typeof ContextIndexRoute
+ '/new/$template': typeof ContextCloudNewTemplateRoute
+ '/new': typeof ContextCloudNewIndexRoute
'/ns/$namespace/connect': typeof ContextEngineNsNamespaceConnectRoute
'/orgs/$organization': typeof ContextCloudOrgsOrganizationIndexRoute
'/ns/$namespace': typeof ContextEngineNsNamespaceIndexRoute
+ '/orgs/$organization/new': typeof ContextCloudOrgsOrganizationNewIndexRoute
'/orgs/$organization/projects': typeof ContextCloudOrgsOrganizationProjectsIndexRoute
'/orgs/$organization/projects/$project': typeof ContextCloudOrgsOrganizationProjectsProjectIndexRoute
'/orgs/$organization/projects/$project/ns/$namespace/connect': typeof ContextCloudOrgsOrganizationProjectsProjectNsNamespaceConnectRoute
@@ -213,12 +238,15 @@ export interface FileRoutesById {
'/onboarding/accept-invitation': typeof OnboardingAcceptInvitationRoute
'/onboarding/choose-organization': typeof OnboardingChooseOrganizationRoute
'/_context/': typeof ContextIndexRoute
+ '/_context/_cloud/new/$template': typeof ContextCloudNewTemplateRoute
'/_context/_cloud/orgs/$organization': typeof ContextCloudOrgsOrganizationRouteWithChildren
'/_context/_engine/ns/$namespace': typeof ContextEngineNsNamespaceRouteWithChildren
+ '/_context/_cloud/new/': typeof ContextCloudNewIndexRoute
'/_context/_engine/ns/$namespace/connect': typeof ContextEngineNsNamespaceConnectRoute
'/_context/_cloud/orgs/$organization/': typeof ContextCloudOrgsOrganizationIndexRoute
'/_context/_engine/ns/$namespace/': typeof ContextEngineNsNamespaceIndexRoute
'/_context/_cloud/orgs/$organization/projects/$project': typeof ContextCloudOrgsOrganizationProjectsProjectRouteWithChildren
+ '/_context/_cloud/orgs/$organization/new/': typeof ContextCloudOrgsOrganizationNewIndexRoute
'/_context/_cloud/orgs/$organization/projects/': typeof ContextCloudOrgsOrganizationProjectsIndexRoute
'/_context/_cloud/orgs/$organization/projects/$project/': typeof ContextCloudOrgsOrganizationProjectsProjectIndexRoute
'/_context/_cloud/orgs/$organization/projects/$project/ns/$namespace': typeof ContextCloudOrgsOrganizationProjectsProjectNsNamespaceRouteWithChildren
@@ -236,12 +264,15 @@ export interface FileRouteTypes {
| '/onboarding/accept-invitation'
| '/onboarding/choose-organization'
| '/'
+ | '/new/$template'
| '/orgs/$organization'
| '/ns/$namespace'
+ | '/new'
| '/ns/$namespace/connect'
| '/orgs/$organization/'
| '/ns/$namespace/'
| '/orgs/$organization/projects/$project'
+ | '/orgs/$organization/new'
| '/orgs/$organization/projects'
| '/orgs/$organization/projects/$project/'
| '/orgs/$organization/projects/$project/ns/$namespace'
@@ -257,9 +288,12 @@ export interface FileRouteTypes {
| '/onboarding/accept-invitation'
| '/onboarding/choose-organization'
| '/'
+ | '/new/$template'
+ | '/new'
| '/ns/$namespace/connect'
| '/orgs/$organization'
| '/ns/$namespace'
+ | '/orgs/$organization/new'
| '/orgs/$organization/projects'
| '/orgs/$organization/projects/$project'
| '/orgs/$organization/projects/$project/ns/$namespace/connect'
@@ -277,12 +311,15 @@ export interface FileRouteTypes {
| '/onboarding/accept-invitation'
| '/onboarding/choose-organization'
| '/_context/'
+ | '/_context/_cloud/new/$template'
| '/_context/_cloud/orgs/$organization'
| '/_context/_engine/ns/$namespace'
+ | '/_context/_cloud/new/'
| '/_context/_engine/ns/$namespace/connect'
| '/_context/_cloud/orgs/$organization/'
| '/_context/_engine/ns/$namespace/'
| '/_context/_cloud/orgs/$organization/projects/$project'
+ | '/_context/_cloud/orgs/$organization/new/'
| '/_context/_cloud/orgs/$organization/projects/'
| '/_context/_cloud/orgs/$organization/projects/$project/'
| '/_context/_cloud/orgs/$organization/projects/$project/ns/$namespace'
@@ -371,6 +408,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ContextCloudRouteImport
parentRoute: typeof ContextRoute
}
+ '/_context/_cloud/new/': {
+ id: '/_context/_cloud/new/'
+ path: '/new'
+ fullPath: '/new'
+ preLoaderRoute: typeof ContextCloudNewIndexRouteImport
+ parentRoute: typeof ContextCloudRoute
+ }
'/_context/_engine/ns/$namespace': {
id: '/_context/_engine/ns/$namespace'
path: '/ns/$namespace'
@@ -385,6 +429,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ContextCloudOrgsOrganizationRouteImport
parentRoute: typeof ContextCloudRoute
}
+ '/_context/_cloud/new/$template': {
+ id: '/_context/_cloud/new/$template'
+ path: '/new/$template'
+ fullPath: '/new/$template'
+ preLoaderRoute: typeof ContextCloudNewTemplateRouteImport
+ parentRoute: typeof ContextCloudRoute
+ }
'/_context/_engine/ns/$namespace/': {
id: '/_context/_engine/ns/$namespace/'
path: '/'
@@ -413,6 +464,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ContextCloudOrgsOrganizationProjectsIndexRouteImport
parentRoute: typeof ContextCloudOrgsOrganizationRoute
}
+ '/_context/_cloud/orgs/$organization/new/': {
+ id: '/_context/_cloud/orgs/$organization/new/'
+ path: '/new'
+ fullPath: '/orgs/$organization/new'
+ preLoaderRoute: typeof ContextCloudOrgsOrganizationNewIndexRouteImport
+ parentRoute: typeof ContextCloudOrgsOrganizationRoute
+ }
'/_context/_cloud/orgs/$organization/projects/$project': {
id: '/_context/_cloud/orgs/$organization/projects/$project'
path: '/projects/$project'
@@ -500,6 +558,7 @@ const ContextCloudOrgsOrganizationProjectsProjectRouteWithChildren =
interface ContextCloudOrgsOrganizationRouteChildren {
ContextCloudOrgsOrganizationIndexRoute: typeof ContextCloudOrgsOrganizationIndexRoute
ContextCloudOrgsOrganizationProjectsProjectRoute: typeof ContextCloudOrgsOrganizationProjectsProjectRouteWithChildren
+ ContextCloudOrgsOrganizationNewIndexRoute: typeof ContextCloudOrgsOrganizationNewIndexRoute
ContextCloudOrgsOrganizationProjectsIndexRoute: typeof ContextCloudOrgsOrganizationProjectsIndexRoute
}
@@ -509,6 +568,8 @@ const ContextCloudOrgsOrganizationRouteChildren: ContextCloudOrgsOrganizationRou
ContextCloudOrgsOrganizationIndexRoute,
ContextCloudOrgsOrganizationProjectsProjectRoute:
ContextCloudOrgsOrganizationProjectsProjectRouteWithChildren,
+ ContextCloudOrgsOrganizationNewIndexRoute:
+ ContextCloudOrgsOrganizationNewIndexRoute,
ContextCloudOrgsOrganizationProjectsIndexRoute:
ContextCloudOrgsOrganizationProjectsIndexRoute,
}
@@ -519,12 +580,16 @@ const ContextCloudOrgsOrganizationRouteWithChildren =
)
interface ContextCloudRouteChildren {
+ ContextCloudNewTemplateRoute: typeof ContextCloudNewTemplateRoute
ContextCloudOrgsOrganizationRoute: typeof ContextCloudOrgsOrganizationRouteWithChildren
+ ContextCloudNewIndexRoute: typeof ContextCloudNewIndexRoute
}
const ContextCloudRouteChildren: ContextCloudRouteChildren = {
+ ContextCloudNewTemplateRoute: ContextCloudNewTemplateRoute,
ContextCloudOrgsOrganizationRoute:
ContextCloudOrgsOrganizationRouteWithChildren,
+ ContextCloudNewIndexRoute: ContextCloudNewIndexRoute,
}
const ContextCloudRouteWithChildren = ContextCloudRoute._addFileChildren(
diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx
index 4cc72fbbc9..b7a9b761c5 100644
--- a/frontend/src/routes/__root.tsx
+++ b/frontend/src/routes/__root.tsx
@@ -1,6 +1,5 @@
import type { Clerk } from "@clerk/clerk-js";
import { ClerkProvider } from "@clerk/clerk-react";
-import * as ClerkComponents from "@clerk/elements/common";
import { dark } from "@clerk/themes";
import type { QueryClient } from "@tanstack/react-query";
import {
diff --git a/frontend/src/routes/_context/_cloud.tsx b/frontend/src/routes/_context/_cloud.tsx
index d9779d4d27..ba83f8fe41 100644
--- a/frontend/src/routes/_context/_cloud.tsx
+++ b/frontend/src/routes/_context/_cloud.tsx
@@ -56,7 +56,7 @@ function CloudModals() {
// FIXME
onOpenChange: (value: any) => {
if (!value) {
- navigate({
+ return navigate({
to: ".",
search: (old) => ({
...old,
@@ -73,7 +73,7 @@ function CloudModals() {
// FIXME
onOpenChange: (value: any) => {
if (!value) {
- navigate({
+ return navigate({
to: ".",
search: (old) => ({
...old,
diff --git a/frontend/src/routes/_context/_cloud/new/$template.tsx b/frontend/src/routes/_context/_cloud/new/$template.tsx
new file mode 100644
index 0000000000..72ec5c75fd
--- /dev/null
+++ b/frontend/src/routes/_context/_cloud/new/$template.tsx
@@ -0,0 +1,22 @@
+import { createFileRoute, redirect } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/_context/_cloud/new/$template")({
+ component: RouteComponent,
+ beforeLoad: async ({ context, params }) => {
+ throw redirect({
+ to: "/orgs/$organization/new",
+ params: {
+ organization: context.clerk.organization?.id ?? "",
+ ...params,
+ },
+ search: {
+ template: params.template,
+ modal: "get-started",
+ },
+ });
+ },
+});
+
+function RouteComponent() {
+ return null;
+}
diff --git a/frontend/src/routes/_context/_cloud/new/index.tsx b/frontend/src/routes/_context/_cloud/new/index.tsx
new file mode 100644
index 0000000000..6e8d937615
--- /dev/null
+++ b/frontend/src/routes/_context/_cloud/new/index.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute, redirect } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/_context/_cloud/new/")({
+ component: RouteComponent,
+ beforeLoad: async ({ context, params }) => {
+ throw redirect({
+ to: "/orgs/$organization/new",
+ params: {
+ organization: context.clerk.organization?.id ?? "",
+ ...params,
+ },
+ })
+ },
+});
+
+function RouteComponent() {
+ return null;
+}
diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/index.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/index.tsx
index e53637fd21..0f4b835d25 100644
--- a/frontend/src/routes/_context/_cloud/orgs.$organization/index.tsx
+++ b/frontend/src/routes/_context/_cloud/orgs.$organization/index.tsx
@@ -3,7 +3,7 @@ import { match } from "ts-pattern";
import CreateProjectFrameContent from "@/app/dialogs/create-project-frame";
import { RouteError } from "@/app/route-error";
import { PendingRouteLayout, RouteLayout } from "@/app/route-layout";
-import { Card, H2, Skeleton } from "@/components";
+import { Card } from "@/components";
export const Route = createFileRoute("/_context/_cloud/orgs/$organization/")({
loader: async ({ context, params }) => {
@@ -22,7 +22,7 @@ export const Route = createFileRoute("/_context/_cloud/orgs/$organization/")({
throw redirect({
to: "/orgs/$organization/projects/$project",
replace: true,
-
+ search: true,
params: {
organization: params.organization,
project: firstProject.name,
diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/new/index.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/new/index.tsx
new file mode 100644
index 0000000000..ae8b20460e
--- /dev/null
+++ b/frontend/src/routes/_context/_cloud/orgs.$organization/new/index.tsx
@@ -0,0 +1,48 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { GettingStarted } from "@/app/getting-started";
+import { RouteLayout } from "@/app/route-layout";
+import { useDialog } from "@/app/use-dialog";
+
+export const Route = createFileRoute(
+ "/_context/_cloud/orgs/$organization/new/",
+)({
+ component: RouteComponent,
+});
+
+function RouteComponent() {
+ const navigate = Route.useNavigate();
+ const search = Route.useSearch();
+ const StartWithTemplateDialog = useDialog.StartWithTemplate.Dialog;
+ return (
+ <>
+
+ ({
+ to: "/orgs/$organization/new",
+ search: { template: slug, modal: "get-started" },
+ })}
+ />
+
+ {
+ if (!value) {
+ return navigate({
+ to: ".",
+ search: (old) => ({
+ ...old,
+ modal: undefined,
+ }),
+ });
+ }
+ },
+ }}
+ />
+ >
+ );
+}
diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/index.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/index.tsx
index 03cc5c06c4..b39087470f 100644
--- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/index.tsx
+++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/index.tsx
@@ -7,7 +7,7 @@ import { Card } from "@/components";
export const Route = createFileRoute(
"/_context/_cloud/orgs/$organization/projects/$project/",
)({
- beforeLoad: ({ context, params }) => {
+ beforeLoad: ({ context, params, search }) => {
return match(__APP_TYPE__)
.with("cloud", async () => {
if (!context.clerk?.organization) {
@@ -23,7 +23,7 @@ export const Route = createFileRoute(
throw redirect({
to: "/orgs/$organization/projects/$project/ns/$namespace",
replace: true,
-
+ search,
params: {
organization: params.organization,
project: params.project,
diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace.tsx
index 524d044634..539b429540 100644
--- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace.tsx
+++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace.tsx
@@ -1,7 +1,12 @@
-import { createFileRoute } from "@tanstack/react-router";
+import {
+ createFileRoute,
+ useNavigate,
+ useSearch,
+} from "@tanstack/react-router";
import { createNamespaceContext } from "@/app/data-providers/cloud-data-provider";
import { NotFoundCard } from "@/app/not-found-card";
import { PendingRouteLayout, RouteLayout } from "@/app/route-layout";
+import { useDialog } from "@/app/use-dialog";
export const Route = createFileRoute(
"/_context/_cloud/orgs/$organization/projects/$project/ns/$namespace",
@@ -36,5 +41,38 @@ export const Route = createFileRoute(
});
function RouteComponent() {
- return ;
+ return (
+ <>
+
+
+ >
+ );
+}
+
+function CloudNamespaceModals() {
+ const navigate = useNavigate();
+ const search = useSearch({ from: "/_context" });
+ const StartWithTemplateDialog = useDialog.StartWithTemplate.Dialog;
+
+ return (
+ {
+ if (!value) {
+ return navigate({
+ to: ".",
+ search: (old) => ({
+ ...old,
+ modal: undefined,
+ name: undefined,
+ }),
+ });
+ }
+ },
+ }}
+ />
+ );
}
diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx
index 1b6b5ca41b..3373d76432 100644
--- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx
+++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx
@@ -2,12 +2,9 @@ import {
faAws,
faGoogleCloud,
faHetznerH,
- faNextjs,
- faNodeJs,
faPlus,
faQuestionCircle,
faRailway,
- faReact,
faServer,
faVercel,
Icon,
@@ -17,21 +14,15 @@ import {
useSuspenseInfiniteQuery,
useSuspenseQuery,
} from "@tanstack/react-query";
-import {
- createFileRoute,
- Link,
- notFound,
- Link as RouterLink,
- useParams,
-} from "@tanstack/react-router";
+import { createFileRoute, notFound, useParams } from "@tanstack/react-router";
import { match } from "ts-pattern";
+import { GettingStarted } from "@/app/getting-started";
import { HelpDropdown } from "@/app/help-dropdown";
import { PublishableTokenCodeGroup } from "@/app/publishable-token-code-group";
import { RunnerConfigsTable } from "@/app/runner-config-table";
import { RunnersTable } from "@/app/runners-table";
import {
Button,
- DocsSheet,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
@@ -109,196 +100,7 @@ export function RouteComponent() {
}
if (!hasRunnerNames) {
- return (
-
-
-
-
-
Create New Project
-
-
- Start a new RivetKit project with Rivet Cloud. Use
- one of our templates to get started quickly.
-
-
-
-
-
1-Click Deploy From Template
-
- }
- asChild
- >
-
- Vercel
-
-
-
-
-
-
-
Quickstart Guides
-
-
- }
- >
- Node.js & Bun
-
-
-
- }
- >
- React
-
-
-
- }
- >
- Next.js
-
-
-
-
-
-
-
-
Connect Existing Project
-
- }
- >
- Need help?
-
-
-
-
- Connect your RivetKit application to Rivet Cloud.
- Use your cloud of choice to run Rivet Actors.
-
-
-
-
-
Add Provider
-
- }
- asChild
- >
-
- Vercel
-
-
- }
- asChild
- >
-
- Railway
-
-
- }
- asChild
- >
-
- AWS ECS
-
-
-
- }
- asChild
- >
-
- Google Cloud Run
-
-
- }
- asChild
- >
-
- Hetzner
-
-
- }
- asChild
- >
-
- Custom
-
-
-
-
-
-
-
- );
+ return ;
}
return (
@@ -586,19 +388,3 @@ function DataLoadingPlaceholder() {
);
}
-
-function OneClickDeployRailwayButton() {
- return (
-