Skip to content

Commit 7a3d2aa

Browse files
committed
fix: polish railway connect frame
1 parent 0ab7942 commit 7a3d2aa

28 files changed

+926
-212
lines changed

frontend/src/app/data-providers/engine-data-provider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ export const createNamespaceContext = ({
166166
regions: data.datacenters.map((dc) => ({
167167
id: dc.name,
168168
name: dc.name,
169+
url: dc.url,
169170
})),
170171
pagination: data.pagination,
171172
};
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
import { faRailway, faServer, Icon } from "@rivet-gg/icons";
2+
import { useQuery } from "@tanstack/react-query";
3+
import { useFormContext } from "react-hook-form";
4+
import * as ConnectRailwayForm from "@/app/forms/connect-railway-form";
5+
import {
6+
Button,
7+
CopyButton,
8+
type DialogContentProps,
9+
DiscreteInput,
10+
Frame,
11+
Label,
12+
Skeleton,
13+
} from "@/components";
14+
import { useEngineCompatDataProvider } from "@/components/actors";
15+
import { defineStepper } from "@/components/ui/stepper";
16+
import { engineEnv } from "@/lib/env";
17+
import { NeedHelp } from "../forms/connect-vercel-form";
18+
19+
const { Stepper } = defineStepper(
20+
{
21+
id: "initial",
22+
title: "Get Started",
23+
},
24+
{
25+
id: "step-1",
26+
title: "Deploy",
27+
},
28+
{
29+
id: "step-3",
30+
title: "Wait for the Runner to connect",
31+
},
32+
);
33+
34+
interface ConnectManualFrameContentProps extends DialogContentProps {}
35+
36+
export default function ConnectManualFrameContent({
37+
onClose,
38+
}: ConnectManualFrameContentProps) {
39+
return (
40+
<ConnectRailwayForm.Form
41+
onSubmit={async () => {}}
42+
mode="onChange"
43+
defaultValues={{
44+
runnerName: "default",
45+
datacenter: "auto",
46+
}}
47+
>
48+
<Frame.Header>
49+
<Frame.Title className="gap-2 flex items-center">
50+
<div>
51+
Add <Icon icon={faServer} className="ml-0.5" /> Manual
52+
Runner
53+
</div>
54+
</Frame.Title>
55+
</Frame.Header>
56+
<Frame.Content>
57+
<FormStepper onClose={onClose} />
58+
</Frame.Content>
59+
</ConnectRailwayForm.Form>
60+
);
61+
}
62+
63+
function FormStepper({ onClose }: { onClose?: () => void }) {
64+
return (
65+
<Stepper.Provider variant="vertical">
66+
{({ methods }) => (
67+
<>
68+
<Stepper.Navigation>
69+
{methods.all.map((step) => (
70+
<Stepper.Step
71+
key={step.id}
72+
className="min-w-0"
73+
of={step.id}
74+
onClick={() => methods.goTo(step.id)}
75+
>
76+
<Stepper.Title>{step.title}</Stepper.Title>
77+
{methods.when(step.id, (step) => {
78+
return (
79+
<Stepper.Panel className="space-y-4">
80+
{step.id === "initial" && (
81+
<>
82+
<p>
83+
Connect any
84+
RivetKit-compatible
85+
runner.
86+
</p>
87+
<ConnectRailwayForm.RunnerName />
88+
<ConnectRailwayForm.Datacenter />
89+
</>
90+
)}
91+
{step.id === "step-1" && (
92+
<EnvVariablesStep />
93+
)}
94+
{step.id === "step-3" && (
95+
<div>
96+
<ConnectRailwayForm.ConnectionCheck />
97+
</div>
98+
)}
99+
<Stepper.Controls>
100+
{step.id === "step-3" ? (
101+
<NeedHelp />
102+
) : null}
103+
<Button
104+
type="button"
105+
variant="secondary"
106+
onClick={methods.prev}
107+
disabled={methods.isFirst}
108+
>
109+
Previous
110+
</Button>
111+
<Button
112+
onClick={
113+
methods.isLast
114+
? onClose
115+
: methods.next
116+
}
117+
>
118+
{methods.isLast
119+
? "Done"
120+
: "Next"}
121+
</Button>
122+
</Stepper.Controls>
123+
</Stepper.Panel>
124+
);
125+
})}
126+
</Stepper.Step>
127+
))}
128+
</Stepper.Navigation>
129+
</>
130+
)}
131+
</Stepper.Provider>
132+
);
133+
}
134+
135+
function EnvVariablesStep() {
136+
return (
137+
<>
138+
<p>Set the following environment variables in your deployment.</p>
139+
<div>
140+
<div
141+
className="gap-1 items-center grid grid-cols-2"
142+
data-env-variables
143+
>
144+
<Label className="text-muted-foreground text-xs mb-1">
145+
Key
146+
</Label>
147+
<Label className="text-muted-foreground text-xs mb-1">
148+
Value
149+
</Label>
150+
<RivetEndpointEnv />
151+
<RivetTokenEnv />
152+
<RivetNamespaceEnv />
153+
<RivetRunnerEnv />
154+
</div>
155+
<div className="mt-2 flex justify-end">
156+
<CopyButton
157+
value={() => {
158+
const inputs =
159+
document.querySelectorAll<HTMLInputElement>(
160+
"[data-env-variables] input",
161+
);
162+
return Array.from(inputs)
163+
.reduce((acc, input, index) => {
164+
if (index % 2 === 0) {
165+
acc.push(
166+
`${input.value}=${inputs[index + 1]?.value}`,
167+
);
168+
}
169+
return acc;
170+
}, [] as string[])
171+
.join("\n");
172+
}}
173+
>
174+
<Button variant="ghost" size="sm">
175+
Copy all raw
176+
</Button>
177+
</CopyButton>
178+
</div>
179+
</div>
180+
</>
181+
);
182+
}
183+
184+
function RivetRunnerEnv() {
185+
const { watch } = useFormContext();
186+
187+
const runnerName = watch("runnerName");
188+
if (runnerName === "rivetkit") return null;
189+
190+
return (
191+
<>
192+
<DiscreteInput
193+
aria-label="environment variable key"
194+
value="RIVET_RUNNER"
195+
show
196+
/>
197+
<DiscreteInput
198+
aria-label="environment variable value"
199+
value={runnerName}
200+
show
201+
/>
202+
</>
203+
);
204+
}
205+
206+
function RivetTokenEnv() {
207+
const { data, isLoading } = useQuery(
208+
useEngineCompatDataProvider().engineAdminTokenQueryOptions(),
209+
);
210+
return (
211+
<>
212+
<DiscreteInput
213+
aria-label="environment variable key"
214+
value="RIVET_TOKEN"
215+
show
216+
/>
217+
{isLoading ? (
218+
<Skeleton className="w-full h-10" />
219+
) : (
220+
<DiscreteInput
221+
aria-label="environment variable value"
222+
value={data || ""}
223+
/>
224+
)}
225+
</>
226+
);
227+
}
228+
229+
function RivetEndpointEnv() {
230+
const url = useSelectedDatacenter();
231+
return (
232+
<>
233+
<DiscreteInput
234+
aria-label="environment variable key"
235+
value="RIVET_ENDPOINT"
236+
show
237+
/>
238+
<DiscreteInput
239+
aria-label="environment variable value"
240+
value={url}
241+
show
242+
/>
243+
</>
244+
);
245+
}
246+
247+
function RivetNamespaceEnv() {
248+
const dataProvider = useEngineCompatDataProvider();
249+
return (
250+
<>
251+
<DiscreteInput
252+
aria-label="environment variable key"
253+
value="RIVET_NAMESPACE"
254+
show
255+
/>
256+
<DiscreteInput
257+
aria-label="environment variable value"
258+
value={dataProvider.engineNamespace || ""}
259+
show
260+
/>
261+
</>
262+
);
263+
}
264+
265+
const useSelectedDatacenter = () => {
266+
const { watch } = useFormContext();
267+
const datacenter = watch("datacenter");
268+
269+
const { data } = useQuery(
270+
useEngineCompatDataProvider().regionQueryOptions(datacenter),
271+
);
272+
273+
return data?.url || engineEnv().VITE_APP_API_URL;
274+
};

0 commit comments

Comments
 (0)