diff --git a/frontend/src/app/data-providers/engine-data-provider.tsx b/frontend/src/app/data-providers/engine-data-provider.tsx
index d66df703b8..f9592faa19 100644
--- a/frontend/src/app/data-providers/engine-data-provider.tsx
+++ b/frontend/src/app/data-providers/engine-data-provider.tsx
@@ -15,7 +15,6 @@ import {
type DefaultDataProvider,
RECORDS_PER_PAGE,
} from "./default-data-provider";
-import { getConfig } from "@/components/lib/config";
const mightRequireAuth = __APP_TYPE__ === "engine";
@@ -44,7 +43,7 @@ export function createClient(
export const createGlobalContext = (opts: {
engineToken: (() => string) | string;
}) => {
- const client = createClient(getConfig().apiUrl, {
+ const client = createClient(engineEnv().VITE_APP_API_URL, {
token: opts.engineToken,
});
return {
diff --git a/frontend/src/app/dialogs/connect-vercel-frame.tsx b/frontend/src/app/dialogs/connect-vercel-frame.tsx
index 74c9e8b5af..2623b0a39f 100644
--- a/frontend/src/app/dialogs/connect-vercel-frame.tsx
+++ b/frontend/src/app/dialogs/connect-vercel-frame.tsx
@@ -41,9 +41,8 @@ export default function CreateProjectFrameContent() {
-
+
-
diff --git a/frontend/src/app/forms/connect-vercel-form.tsx b/frontend/src/app/forms/connect-vercel-form.tsx
index ef52b1eab9..ae18f4444a 100644
--- a/frontend/src/app/forms/connect-vercel-form.tsx
+++ b/frontend/src/app/forms/connect-vercel-form.tsx
@@ -1,36 +1,22 @@
-import { faCopy, Icon } from "@rivet-gg/icons";
-import { useQuery } from "@tanstack/react-query";
-import { useParams } from "@tanstack/react-router";
import { type UseFormReturn, useFormContext } from "react-hook-form";
import z from "zod";
import {
- Button,
- CodePreview,
- CopyButton,
createSchemaForm,
FormControl,
+ FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
Input,
- Label,
- ScrollArea,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components";
-import { useCloudDataProvider } from "@/components/actors";
export const formSchema = z.object({
- name: z
- .string()
- .max(16)
- .refine((value) => value.trim() !== "" && value.trim() === value, {
- message: "Name cannot be empty or contain whitespaces",
- }),
plan: z.string(),
endpoint: z.string().url(),
});
@@ -44,29 +30,6 @@ export type SubmitHandler = (
const { Form, Submit, SetValue } = createSchemaForm(formSchema);
export { Form, Submit, SetValue };
-export const Name = ({ className }: { className?: string }) => {
- const { control } = useFormContext();
- return (
- (
-
- Name
-
-
-
-
-
- )}
- />
- );
-};
-
export const Plan = ({ className }: { className?: string }) => {
const { control } = useFormContext();
return (
@@ -75,17 +38,14 @@ export const Plan = ({ className }: { className?: string }) => {
name="plan"
render={({ field }) => (
- Plan
+ Vercel Plan
+
)}
@@ -127,60 +88,3 @@ export const Endpoint = ({ className }: { className?: string }) => {
/>
);
};
-
-const code = ({
- token,
- name,
-}: {
- token: string;
- name: string;
-}) => `import { registry } from "./registry";
-
-registry.runServer({
- token: "${token}",
- name: "${name}",
-});`;
-
-export function Preview() {
- const params = useParams({
- from: "/_context/_cloud/orgs/$organization/projects/$project/ns/$namespace",
- });
- const { data } = useQuery(
- useCloudDataProvider().currentOrgProjectNamespaceQueryOptions(params),
- );
-
- const { watch } = useFormContext();
- const name = watch("name");
- return (
-
-
-
-
- ",
- name,
- })}
- />
-
-
- ",
- name,
- })}
- >
-
-
-
-
- );
-}
diff --git a/frontend/src/components/actors/guard-connectable-inspector.tsx b/frontend/src/components/actors/guard-connectable-inspector.tsx
index 4f1100da5a..37ee097dd5 100644
--- a/frontend/src/components/actors/guard-connectable-inspector.tsx
+++ b/frontend/src/components/actors/guard-connectable-inspector.tsx
@@ -1,7 +1,13 @@
import { faPowerOff, faSpinnerThird, Icon } from "@rivet-gg/icons";
import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query";
import { useMatch } from "@tanstack/react-router";
-import { createContext, type ReactNode, useContext, useMemo } from "react";
+import {
+ createContext,
+ type ReactNode,
+ useContext,
+ useMemo,
+ useState,
+} from "react";
import { useInspectorCredentials } from "@/app/credentials-context";
import { createInspectorActorContext } from "@/queries/actor-inspector";
import { DiscreteCopyButton } from "../copy-area";
@@ -33,6 +39,12 @@ export function GuardConnectableInspector({
} = useQuery({
...useDataProvider().actorQueryOptions(actorId),
refetchInterval: 1000,
+ select: (data) => ({
+ destroyedAt: data.destroyedAt,
+ sleepingAt: data.sleepingAt,
+ pendingAllocationAt: data.pendingAllocationAt,
+ startedAt: data.startedAt,
+ }),
});
if (destroyedAt) {
@@ -126,7 +138,7 @@ function ActorInspectorProvider({ children }: { children: ReactNode }) {
}
function useActorRunner({ actorId }: { actorId: ActorId }) {
- const { data: actor } = useSuspenseQuery(
+ const { data: actor, isLoading } = useSuspenseQuery(
useDataProvider().actorQueryOptions(actorId),
);
@@ -142,19 +154,29 @@ function useActorRunner({ actorId }: { actorId: ActorId }) {
throw new Error("Actor is missing required fields");
}
- const { data: runner } = useQuery({
+ const {
+ data: runner,
+ isLoading: isLoadingRunner,
+ isSuccess,
+ } = useQuery({
...useEngineCompatDataProvider().runnerByNameQueryOptions({
runnerName: actor.runner,
namespace: match.params.namespace,
}),
+ retryDelay: 10_000,
refetchInterval: 1000,
});
- return { actor, runner };
+ return {
+ actor,
+ runner,
+ isLoading: isLoading || isLoadingRunner,
+ isSuccess,
+ };
}
function useActorEngineContext({ actorId }: { actorId: ActorId }) {
- const { actor, runner } = useActorRunner({ actorId });
+ const { actor, runner, isLoading } = useActorRunner({ actorId });
const actorContext = useMemo(() => {
return createInspectorActorContext({
@@ -164,7 +186,7 @@ function useActorEngineContext({ actorId }: { actorId: ActorId }) {
});
}, [runner?.metadata?.inspectorToken]);
- return { actorContext, actor, runner };
+ return { actorContext, actor, runner, isLoading };
}
function ActorEngineProvider({
@@ -174,7 +196,9 @@ function ActorEngineProvider({
actorId: ActorId;
children: ReactNode;
}) {
- const { actorContext, actor, runner } = useActorEngineContext({ actorId });
+ const { actorContext, actor, runner } = useActorEngineContext({
+ actorId,
+ });
if (!runner || !actor.runner) {
return (
@@ -207,6 +231,7 @@ function NoRunnerInfo({ runner }: { runner: string }) {
{runner}
+ Will retry automatically in the background.
);
}
@@ -232,9 +257,11 @@ function WakeUpActorButton({ actorId }: { actorId: ActorId }) {
}
function AutoWakeUpActor({ actorId }: { actorId: ActorId }) {
- const { runner, actor, actorContext } = useActorEngineContext({ actorId });
+ const { runner, actor, actorContext } = useActorEngineContext({
+ actorId,
+ });
- const { isPending } = useQuery(
+ useQuery(
actorContext.actorAutoWakeUpQueryOptions(actorId, {
enabled: !!runner,
}),
@@ -242,12 +269,12 @@ function AutoWakeUpActor({ actorId }: { actorId: ActorId }) {
if (!runner) return ;
- return isPending ? (
+ return (
Waiting for Actor to wake...
- ) : null;
+ );
}
diff --git a/frontend/src/components/lib/config.ts b/frontend/src/components/lib/config.ts
index 2131a0d0fc..b4702f10dc 100644
--- a/frontend/src/components/lib/config.ts
+++ b/frontend/src/components/lib/config.ts
@@ -23,16 +23,16 @@ export const ConfigContext = createContext({
export const useConfig = () => useContext(ConfigContext);
export const ConfigProvider = ConfigContext.Provider;
-const getApiEndpoint = (apiEndpoint: string) => {
- if (apiEndpoint === "__AUTO__") {
- if (location.hostname.startsWith("hub.")) {
+export const getApiEndpoint = (apiEndpoint: string) => {
+ if (typeof window !== "undefined" && apiEndpoint === "__AUTO__") {
+ if (window.location.hostname.startsWith("hub.")) {
// Connect to the corresponding API endpoint
- return `https://${location.hostname.replace("hub.", "api.")}`;
+ return `https://${window.location.hostname.replace("hub.", "api.")}`;
}
// Default to staging servers for all other endpoints
return "https://api.staging2.gameinc.io";
- } else if (apiEndpoint === "__SAME__") {
- return location.origin;
+ } else if (typeof window !== "undefined" && apiEndpoint === "__SAME__") {
+ return window.location.origin;
}
return apiEndpoint;
};
diff --git a/frontend/src/lib/env.ts b/frontend/src/lib/env.ts
index b4c798b7e5..e7721c12fd 100644
--- a/frontend/src/lib/env.ts
+++ b/frontend/src/lib/env.ts
@@ -1,7 +1,10 @@
import z from "zod";
+import { getApiEndpoint } from "../components/lib/config";
export const commonEnvSchema = z.object({
- VITE_APP_API_URL: z.string(),
+ VITE_APP_API_URL: z.string().transform((url) => {
+ return getApiEndpoint(url);
+ }),
VITE_APP_ASSETS_URL: z.string().url(),
VITE_APP_POSTHOG_API_KEY: z.string().optional(),
VITE_APP_POSTHOG_API_HOST: z.string().url().optional(),
@@ -19,7 +22,9 @@ export const engineEnv = () => commonEnvSchema.parse(import.meta.env);
export const cloudEnvSchema = commonEnvSchema.merge(
z.object({
- VITE_APP_API_URL: z.string().url(),
+ VITE_APP_API_URL: z.string().transform((url) => {
+ return getApiEndpoint(url);
+ }),
VITE_APP_CLOUD_ENGINE_URL: z.string().url(),
VITE_APP_CLOUD_API_URL: z.string().url(),
VITE_APP_CLERK_PUBLISHABLE_KEY: z.string(),
diff --git a/frontend/src/routes/_context/_engine.tsx b/frontend/src/routes/_context/_engine.tsx
index 5bdf34c7b5..62c57c79c3 100644
--- a/frontend/src/routes/_context/_engine.tsx
+++ b/frontend/src/routes/_context/_engine.tsx
@@ -34,23 +34,62 @@ function EngineModals() {
const CreateNamespaceDialog = useDialog.CreateNamespace.Dialog;
+ const ConnectVercelDialog = useDialog.ConnectVercel.Dialog;
+ const ConnectRailwayDialog = useDialog.ConnectRailway.Dialog;
+
return (
- {
- if (!value) {
- navigate({
- to: ".",
- search: (old) => ({
- ...old,
- modal: undefined,
- }),
- });
- }
- },
- }}
- />
+ <>
+ {
+ if (!value) {
+ navigate({
+ to: ".",
+ search: (old) => ({
+ ...old,
+ modal: undefined,
+ }),
+ });
+ }
+ },
+ }}
+ />
+ {
+ if (!value) {
+ navigate({
+ to: ".",
+ search: (old) => ({
+ ...old,
+ modal: undefined,
+ }),
+ });
+ }
+ },
+ }}
+ />
+ {
+ if (!value) {
+ navigate({
+ to: ".",
+ search: (old) => ({
+ ...old,
+ modal: undefined,
+ }),
+ });
+ }
+ },
+ }}
+ />
+ >
);
}