Skip to content

Commit 31276a0

Browse files
committed
refactor: onboarding (#3194)
1 parent aec7521 commit 31276a0

File tree

12 files changed

+546
-66
lines changed

12 files changed

+546
-66
lines changed

frontend/src/app/dialogs/connect-aws-frame.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import * as ConnectVercelForm from "@/app/forms/connect-vercel-form";
1919
import { cn, type DialogContentProps, Frame } from "@/components";
2020
import { type Region, useEngineCompatDataProvider } from "@/components/actors";
2121
import { defineStepper } from "@/components/ui/stepper";
22+
import { queryClient } from "@/queries/global";
2223
import { StepperForm } from "../forms/stepper-form";
2324
import { EnvVariablesStep } from "./connect-railway-frame";
2425

frontend/src/app/dialogs/connect-railway-frame.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import {
2525
import { useEngineCompatDataProvider } from "@/components/actors";
2626
import { defineStepper } from "@/components/ui/stepper";
2727
import { engineEnv } from "@/lib/env";
28+
import { queryClient } from "@/queries/global";
29+
import { useRailwayTemplateLink } from "@/utils/use-railway-template-link";
2830
import { StepperForm } from "../forms/stepper-form";
2931

3032
const stepper = defineStepper(
@@ -120,15 +122,16 @@ function FormStepper({
120122
origin: { x: 1 },
121123
});
122124

123-
await queryClient.invalidateQueries(provider.runnerConfigsQueryOptions());
125+
await queryClient.invalidateQueries(
126+
provider.runnerConfigsQueryOptions(),
127+
);
124128
onClose?.();
125129
},
126130
});
127131
return (
128132
<StepperForm
129133
{...stepper}
130134
onSubmit={async ({ values }) => {
131-
console.log(values);
132135
await mutateAsync({
133136
name: values.runnerName,
134137
config: {
@@ -328,18 +331,16 @@ function RivetNamespaceEnv() {
328331
}
329332

330333
function DeployToRailwayButton() {
331-
const dataProvider = useEngineCompatDataProvider();
332-
const url = useSelectedDatacenter();
333-
const { data } = useQuery(dataProvider.engineAdminTokenQueryOptions());
334334
const runnerName = useWatch({ name: "runnerName" });
335335

336+
const url = useRailwayTemplateLink({
337+
runnerName: runnerName || "default",
338+
datacenter: useWatch({ name: "datacenter" }) || "auto",
339+
});
340+
336341
return (
337342
<a
338-
href={`https://railway.com/new/template/rivet-cloud-starter?referralCode=RC7bza&utm_medium=integration&utm_source=template&utm_campaign=generic&RIVET_TOKEN=${data || ""}&RIVET_ENDPOINT=${
339-
url || ""
340-
}&RIVET_NAMESPACE=${
341-
dataProvider.engineNamespace || ""
342-
}&RIVET_RUNNER=${runnerName || ""}`}
343+
href={url}
343344
target="_blank"
344345
rel="noreferrer"
345346
className="inline-block h-10"
@@ -352,13 +353,3 @@ function DeployToRailwayButton() {
352353
</a>
353354
);
354355
}
355-
356-
const useSelectedDatacenter = () => {
357-
const datacenter = useWatch({ name: "datacenter" });
358-
359-
const { data } = useQuery(
360-
useEngineCompatDataProvider().regionQueryOptions(datacenter || "auto"),
361-
);
362-
363-
return data?.url || engineEnv().VITE_APP_API_URL;
364-
};

frontend/src/app/dialogs/edit-runner-config.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ export default function EditRunnerConfigFrameContent({
3535
},
3636
});
3737

38-
const config = data.find(([id]) => id === name)?.[1].datacenters?.[dc]
39-
.serverless;
38+
const datacenters = data.find(([id]) => id === name)?.[1].datacenters;
39+
const config = datacenters?.[dc].serverless;
4040

4141
if (!config) {
4242
return (
@@ -52,6 +52,7 @@ export default function EditRunnerConfigFrameContent({
5252
await mutateAsync({
5353
name,
5454
config: {
55+
...datacenters,
5556
[dc]: {
5657
serverless: {
5758
...values,

frontend/src/app/forms/edit-runner-config-form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
} from "@/components";
2121

2222
export const formSchema = z.object({
23-
url: z.string().url().endsWith("/api/rivet"),
23+
url: z.string().url(),
2424
maxRunners: z.coerce.number().positive(),
2525
minRunners: z.coerce.number().min(0),
2626
requestLifespan: z.coerce.number().positive(),

frontend/src/app/runner-config-table.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ function Row({
141141
<WithTooltip
142142
content={config.serverless?.url || "-"}
143143
trigger={
144-
<DiscreteCopyButton value={name}>
144+
<DiscreteCopyButton value={config.serverless?.url}>
145145
<span>
146146
{config.serverless?.url &&
147147
config.serverless.url.length > 32

frontend/src/app/runners-table.tsx

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { faHourglassClock, Icon } from "@rivet-gg/icons";
1+
import { faHourglassClock, faPlus, Icon } from "@rivet-gg/icons";
22
import type { Rivet } from "@rivetkit/engine-api-full";
3+
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
4+
import { Link } from "@tanstack/react-router";
35
import { formatRelative } from "date-fns";
46
import {
57
Button,
@@ -15,6 +17,7 @@ import {
1517
Text,
1618
WithTooltip,
1719
} from "@/components";
20+
import { useEngineCompatDataProvider } from "@/components/actors";
1821

1922
interface RunnersTableProps {
2023
isLoading?: boolean;
@@ -46,13 +49,7 @@ export function RunnersTable({
4649
</TableHeader>
4750
<TableBody>
4851
{!isLoading && !isError && runners?.length === 0 ? (
49-
<TableRow>
50-
<TableCell colSpan={7}>
51-
<Text className="text-center">
52-
There's no runners matching criteria.
53-
</Text>
54-
</TableCell>
55-
</TableRow>
52+
<EmptyState />
5653
) : null}
5754
{isError ? (
5855
<TableRow>
@@ -197,3 +194,63 @@ function RunnerStatusBadge(runner: Rivet.Runner) {
197194
/>
198195
);
199196
}
197+
198+
function EmptyState() {
199+
const { data: serverlessConfig } = useInfiniteQuery({
200+
...useEngineCompatDataProvider().runnerConfigsQueryOptions(),
201+
select(data) {
202+
for (const page of data.pages) {
203+
for (const rc of Object.values(page.runnerConfigs)) {
204+
for (const [dc, config] of Object.entries(rc.datacenters)) {
205+
if (config.serverless) {
206+
return config;
207+
}
208+
}
209+
}
210+
}
211+
return null;
212+
},
213+
});
214+
215+
const { data: actorNames } = useInfiniteQuery({
216+
...useEngineCompatDataProvider().buildsQueryOptions(),
217+
select(data) {
218+
return data.pages[0].builds.length > 0;
219+
},
220+
});
221+
222+
return (
223+
<TableRow>
224+
<TableCell colSpan={7}>
225+
{serverlessConfig ? (
226+
<>
227+
<Text className="text-center">
228+
Runners will be created when an actor is created.
229+
</Text>
230+
{actorNames ? (
231+
<div className="text-center mt-2">
232+
<Button
233+
asChild
234+
size="sm"
235+
startIcon={<Icon icon={faPlus} />}
236+
>
237+
<Link
238+
to="."
239+
search={{ modal: "create-actor" }}
240+
>
241+
Create Actor
242+
</Link>
243+
</Button>
244+
</div>
245+
) : null}
246+
</>
247+
) : (
248+
<Text className="text-center">
249+
There are no runners connected. You will not be able to
250+
run actors until a runner appears here.
251+
</Text>
252+
)}
253+
</TableCell>
254+
</TableRow>
255+
);
256+
}

frontend/src/components/actors/dialogs/create-actor-dialog.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ export default function CreateActorDialog({ onClose }: ContentProps) {
4747
}}
4848
defaultValues={{
4949
name,
50+
key: "example-key",
5051
crashPolicy: CrashPolicy.Destroy,
51-
datacenter: "auto",
5252
}}
5353
>
5454
<DialogHeader>
@@ -62,7 +62,11 @@ export default function CreateActorDialog({ onClose }: ContentProps) {
6262
<ActorCreateForm.Keys />
6363
<ActorCreateForm.ActorPreview />
6464
{["engine", "cloud"].includes(__APP_TYPE__) ? (
65-
<ActorCreateForm.PrefillRunnerName />
65+
<>
66+
<ActorCreateForm.PrefillActorName />
67+
<ActorCreateForm.PrefillRunnerName />
68+
<ActorCreateForm.PrefillDatacenter />
69+
</>
6670
) : null}
6771

6872
<Accordion type="single" collapsible>

frontend/src/components/actors/form/actor-create-form.tsx

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { useInfiniteQuery } from "@tanstack/react-query";
1+
import {
2+
useInfiniteQuery,
3+
useSuspenseInfiniteQuery,
4+
} from "@tanstack/react-query";
25
import { useEffect, useRef } from "react";
36
import { type UseFormReturn, useFormContext } from "react-hook-form";
47
import z from "zod";
@@ -212,11 +215,34 @@ export const ActorPreview = () => {
212215
);
213216
};
214217

218+
export const PrefillActorName = () => {
219+
const prefilled = useRef(false);
220+
const { watch } = useFormContext<FormValues>();
221+
222+
const { data: name, isSuccess } = useSuspenseInfiniteQuery({
223+
...useEngineCompatDataProvider().buildsQueryOptions(),
224+
select: (data) => data.pages[0].builds[0].name,
225+
});
226+
227+
const watchedValue = watch("name");
228+
229+
const { setValue } = useFormContext<FormValues>();
230+
231+
useEffect(() => {
232+
if (name && isSuccess && !watchedValue && !prefilled.current) {
233+
setValue("name", name);
234+
prefilled.current = true;
235+
}
236+
}, [name, setValue, isSuccess, watchedValue]);
237+
238+
return null;
239+
};
240+
215241
export const PrefillRunnerName = () => {
216242
const prefilled = useRef(false);
217243
const { watch } = useFormContext<FormValues>();
218244

219-
const { data = [], isSuccess } = useInfiniteQuery(
245+
const { data = [], isSuccess } = useSuspenseInfiniteQuery(
220246
useEngineCompatDataProvider().runnerNamesQueryOptions(),
221247
);
222248

@@ -239,6 +265,33 @@ export const PrefillRunnerName = () => {
239265
return null;
240266
};
241267

268+
export const PrefillDatacenter = () => {
269+
const prefilled = useRef(false);
270+
const { watch } = useFormContext<FormValues>();
271+
272+
const { data: datacenter, isSuccess } = useSuspenseInfiniteQuery({
273+
...useEngineCompatDataProvider().runnerConfigsQueryOptions(),
274+
select: (data) => {
275+
return Object.keys(
276+
Object.values(data.pages[0].runnerConfigs)[0].datacenters,
277+
)[0];
278+
},
279+
});
280+
281+
const watchedValue = watch("datacenter");
282+
283+
const { setValue } = useFormContext<FormValues>();
284+
285+
useEffect(() => {
286+
if (datacenter && isSuccess && !watchedValue && !prefilled.current) {
287+
setValue("datacenter", datacenter);
288+
prefilled.current = true;
289+
}
290+
}, [datacenter, setValue, isSuccess, watchedValue]);
291+
292+
return null;
293+
};
294+
242295
export const Datacenter = () => {
243296
const { control } = useFormContext<FormValues>();
244297

@@ -251,6 +304,7 @@ export const Datacenter = () => {
251304
<FormLabel>Datacenter</FormLabel>
252305
<FormControl>
253306
<RegionSelect
307+
showAuto={false}
254308
value={field.value}
255309
onValueChange={field.onChange}
256310
/>

frontend/src/components/code-preview/code-preview.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,15 @@ export function CodePreview({ className, code, language }: CodePreviewProps) {
5757
);
5858

5959
if (isLoading) {
60-
return <Skeleton className="w-full h-5" />;
60+
return (
61+
<div className="px-2 flex flex-col gap-0.5">
62+
<Skeleton className="w-full h-5" />
63+
<Skeleton className="w-full h-5" />
64+
<Skeleton className="w-full h-5" />
65+
<Skeleton className="w-full h-5" />
66+
<Skeleton className="w-full h-5" />
67+
</div>
68+
);
6169
}
6270

6371
return (

frontend/src/components/code.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { faCopy, faFile, Icon } from "@rivet-gg/icons";
2-
import { Children, cloneElement, type ReactElement } from "react";
2+
import {
3+
Children,
4+
cloneElement,
5+
type ReactElement,
6+
type ReactNode,
7+
} from "react";
38
import { CopyButton } from "./copy-area";
49
import { cn } from "./lib/utils";
510
import { Badge } from "./ui/badge";
@@ -96,6 +101,7 @@ interface CodeFrameProps {
96101
language: keyof typeof languageNames;
97102
isInGroup?: boolean;
98103
code?: () => string | string;
104+
footer?: ReactNode;
99105
children?: ReactElement;
100106
}
101107
export const CodeFrame = ({
@@ -104,6 +110,7 @@ export const CodeFrame = ({
104110
language,
105111
code,
106112
title,
113+
footer,
107114
isInGroup,
108115
}: CodeFrameProps) => {
109116
return (
@@ -118,6 +125,7 @@ export const CodeFrame = ({
118125

119126
<div className="text-foreground flex items-center justify-between gap-2 border-t p-2 text-xs">
120127
<div className="text-muted-foreground flex items-center gap-1">
128+
{footer}
121129
{file ? (
122130
<>
123131
<Icon icon={faFile} className="block" />

0 commit comments

Comments
 (0)