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, + }), + }); + } + }, + }} + /> + ); }