Skip to content

Commit 6b80b50

Browse files
committed
feat(hub): functions
1 parent ed95552 commit 6b80b50

File tree

25 files changed

+2319
-219
lines changed

25 files changed

+2319
-219
lines changed

frontend/apps/hub/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@tanstack/react-query-devtools": "^5.58.0",
2929
"@tanstack/react-query-persist-client": "^5.56.2",
3030
"@tanstack/react-router": "^1.114.25",
31-
"@tanstack/react-table": "^8.20.6",
31+
"@tanstack/react-table": "^8.21.2",
3232
"@tanstack/router-devtools": "^1.114.25",
3333
"@tanstack/router-plugin": "^1.114.25",
3434
"@tanstack/zod-adapter": "^1.114.25",

frontend/apps/hub/src/app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ declare module "@tanstack/react-router" {
2727
router: typeof router;
2828
}
2929
interface StaticDataRouteOption {
30-
layout?: "full" | "compact" | "onboarding" | "actors";
30+
layout?: "full" | "compact" | "onboarding" | "actors" | "v2";
3131
}
3232
}
3333

frontend/apps/hub/src/components/header/header-sub-nav.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
import { useAuth } from "@/domains/auth/contexts/auth";
22
import { Skeleton, cn } from "@rivet-gg/components";
33
import { ErrorBoundary } from "@sentry/react";
4-
import { useMatchRoute } from "@tanstack/react-router";
4+
import {
5+
useMatches,
6+
useMatchRoute,
7+
useRouterState,
8+
} from "@tanstack/react-router";
59
import { Suspense, useContext } from "react";
610
import { MobileBreadcrumbsContext } from "../breadcrumbs/mobile-breadcrumbs";
711
import { HeaderEnvironmentLinks } from "./links/header-environment-links";
812
import { HeaderGroupLinks } from "./links/header-group-links";
913
import { HeaderProjectLinks } from "./links/header-project-links";
1014

1115
function Content() {
16+
const allMatches = useMatches();
17+
18+
const v2Envs = allMatches.find((match) => match.id.includes("/_v2/"));
19+
1220
const matchRoute = useMatchRoute();
1321

1422
const { profile } = useAuth();
1523

16-
if (!profile?.identity.isRegistered) {
24+
if (!profile?.identity.isRegistered || v2Envs) {
1725
return null;
1826
}
1927

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import * as EditRouteForm from "@/domains/project/forms/route-edit-form";
2+
import type { DialogContentProps } from "@/hooks/use-dialog";
3+
import {
4+
Button,
5+
DialogFooter,
6+
DialogHeader,
7+
DialogTitle,
8+
Label,
9+
} from "@rivet-gg/components";
10+
import { useState } from "react";
11+
import { useCreateRouteMutation } from "../../queries";
12+
13+
interface ContentProps extends DialogContentProps {
14+
projectNameId: string;
15+
environmentNameId: string;
16+
}
17+
18+
export default function EditRouteDialogContent(props: ContentProps) {
19+
return <Content {...props} />;
20+
}
21+
22+
function Content({ projectNameId, environmentNameId, onClose }: ContentProps) {
23+
const { mutateAsync } = useCreateRouteMutation();
24+
25+
const [tagKeys, setTagKeys] = useState<{ label: string; value: string }[]>(
26+
() => [],
27+
);
28+
29+
const [tagValues, setTagValues] = useState<
30+
{ label: string; value: string }[]
31+
>(() => []);
32+
33+
return (
34+
<EditRouteForm.Form
35+
defaultValues={{
36+
tags: [],
37+
hostname: "",
38+
path: "",
39+
}}
40+
onSubmit={async (values) => {
41+
const selector = Object.fromEntries(
42+
values.tags.map(({ key, value }) => [key, value]),
43+
);
44+
45+
await mutateAsync({
46+
projectNameId,
47+
environmentNameId,
48+
selector,
49+
hostname: values.hostname,
50+
pathPrefix: values.path,
51+
});
52+
onClose?.();
53+
}}
54+
>
55+
<DialogHeader>
56+
<DialogTitle>Add Route</DialogTitle>
57+
</DialogHeader>
58+
59+
<div className="flex gap-2">
60+
<EditRouteForm.Hostname />
61+
<EditRouteForm.Path />
62+
</div>
63+
64+
<Label>Selectors</Label>
65+
66+
<EditRouteForm.Tags
67+
keys={tagKeys}
68+
values={tagValues}
69+
onCreateKeyOption={(option) =>
70+
setTagKeys((opts) => [
71+
...opts,
72+
{ label: option, value: option },
73+
])
74+
}
75+
onCreateValueOption={(option) =>
76+
setTagValues((opts) => [
77+
...opts,
78+
{ label: option, value: option },
79+
])
80+
}
81+
/>
82+
83+
<DialogFooter>
84+
<EditRouteForm.Submit>Save</EditRouteForm.Submit>
85+
<Button type="button" variant="secondary" onClick={onClose}>
86+
Close
87+
</Button>
88+
</DialogFooter>
89+
</EditRouteForm.Form>
90+
);
91+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import * as EditRouteForm from "@/domains/project/forms/route-edit-form";
2+
import type { DialogContentProps } from "@/hooks/use-dialog";
3+
import {
4+
Button,
5+
DialogFooter,
6+
DialogHeader,
7+
DialogTitle,
8+
Label,
9+
} from "@rivet-gg/components";
10+
import { useSuspenseQuery } from "@tanstack/react-query";
11+
import { useState } from "react";
12+
import { routeQueryOptions, usePatchRouteMutation } from "../../queries";
13+
14+
interface ContentProps extends DialogContentProps {
15+
projectNameId: string;
16+
environmentNameId: string;
17+
routeId: string;
18+
}
19+
20+
export default function EditRouteDialogContent(props: ContentProps) {
21+
if (!props.routeId) {
22+
return null;
23+
}
24+
25+
return <Content {...props} />;
26+
}
27+
28+
function Content({
29+
routeId,
30+
projectNameId,
31+
environmentNameId,
32+
onClose,
33+
}: ContentProps) {
34+
const { data } = useSuspenseQuery(
35+
routeQueryOptions({
36+
routeId,
37+
projectNameId,
38+
environmentNameId,
39+
}),
40+
);
41+
const { mutateAsync } = usePatchRouteMutation();
42+
43+
const [tagKeys, setTagKeys] = useState<{ label: string; value: string }[]>(
44+
() => [],
45+
);
46+
47+
const [tagValues, setTagValues] = useState<
48+
{ label: string; value: string }[]
49+
>(() => []);
50+
51+
return (
52+
<EditRouteForm.Form
53+
defaultValues={{
54+
tags: Object.entries(data.selector).map(([key, value]) => ({
55+
key,
56+
value,
57+
})),
58+
hostname: data.hostname,
59+
path: data.pathPrefix,
60+
}}
61+
onSubmit={async (values) => {
62+
const selector = Object.fromEntries(
63+
values.tags.map(({ key, value }) => [key, value]),
64+
);
65+
66+
await mutateAsync({
67+
projectNameId,
68+
environmentNameId,
69+
routeId,
70+
selector,
71+
hostname: values.hostname,
72+
pathPrefix: values.path,
73+
});
74+
onClose?.();
75+
}}
76+
>
77+
<DialogHeader>
78+
<DialogTitle>Edit Route</DialogTitle>
79+
</DialogHeader>
80+
81+
<div className="flex gap-2">
82+
<EditRouteForm.Hostname />
83+
<EditRouteForm.Path />
84+
</div>
85+
86+
<Label>Selectors</Label>
87+
<EditRouteForm.Tags
88+
keys={tagKeys}
89+
values={tagValues}
90+
onCreateKeyOption={(option) =>
91+
setTagKeys((opts) => [
92+
...opts,
93+
{ label: option, value: option },
94+
])
95+
}
96+
onCreateValueOption={(option) =>
97+
setTagValues((opts) => [
98+
...opts,
99+
{ label: option, value: option },
100+
])
101+
}
102+
/>
103+
104+
<DialogFooter>
105+
<EditRouteForm.Submit>Save</EditRouteForm.Submit>
106+
<Button type="button" variant="secondary" onClick={onClose}>
107+
Close
108+
</Button>
109+
</DialogFooter>
110+
</EditRouteForm.Form>
111+
);
112+
}

0 commit comments

Comments
 (0)