Skip to content

Commit 7ab4c6c

Browse files
committed
feat(cloud): add support
1 parent b3f1ecc commit 7ab4c6c

File tree

10 files changed

+178
-17
lines changed

10 files changed

+178
-17
lines changed

frontend/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@
4545
}
4646
</script>
4747
<script type="module" src="/src/main.tsx"></script>
48+
{{live_chat}}
4849
</body>
4950
</html>

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { faRailway, Icon } from "@rivet-gg/icons";
1+
import { faQuestionCircle, faRailway, Icon } from "@rivet-gg/icons";
22
import * as ConnectRailwayForm from "@/app/forms/connect-railway-form";
3-
import { Flex, Frame } from "@/components";
3+
import { HelpDropdown } from "@/app/help-dropdown";
4+
import { Button, Flex, Frame } from "@/components";
45

56
export default function CreateProjectFrameContent() {
67
return (
@@ -9,8 +10,15 @@ export default function CreateProjectFrameContent() {
910
defaultValues={{ name: "" }}
1011
>
1112
<Frame.Header>
12-
<Frame.Title>
13-
Add <Icon icon={faRailway} className="ml-0.5" /> Railway
13+
<Frame.Title className="justify-between flex items-center">
14+
<div>
15+
Add <Icon icon={faRailway} className="ml-0.5" /> Railway
16+
</div>
17+
<HelpDropdown>
18+
<Button variant="ghost" size="icon">
19+
<Icon icon={faQuestionCircle} />
20+
</Button>
21+
</HelpDropdown>
1422
</Frame.Title>
1523
</Frame.Header>
1624
<Frame.Content>

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { faVercel, Icon } from "@rivet-gg/icons";
1+
import { faQuestionCircle, faVercel, Icon } from "@rivet-gg/icons";
22
import { useMutation } from "@tanstack/react-query";
33
import * as ConnectVercelForm from "@/app/forms/connect-vercel-form";
4-
import { Flex, Frame } from "@/components";
4+
import { HelpDropdown } from "@/app/help-dropdown";
5+
import { Button, Flex, Frame } from "@/components";
56
import { useEngineCompatDataProvider } from "@/components/actors";
67

78
export default function CreateProjectFrameContent() {
@@ -26,9 +27,16 @@ export default function CreateProjectFrameContent() {
2627
defaultValues={{ name: "" }}
2728
>
2829
<Frame.Header>
29-
<Frame.Title>
30-
Add <Icon icon={faVercel} className="ml-0.5" />
31-
Vercel
30+
<Frame.Title className="justify-between flex items-center">
31+
<div>
32+
Add <Icon icon={faVercel} className="ml-0.5" />
33+
Vercel
34+
</div>
35+
<HelpDropdown>
36+
<Button variant="ghost" size="icon">
37+
<Icon icon={faQuestionCircle} />
38+
</Button>
39+
</HelpDropdown>
3240
</Frame.Title>
3341
</Frame.Header>
3442
<Frame.Content>

frontend/src/app/help-dropdown.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {
2+
faBooks,
3+
faComments,
4+
faDiscord,
5+
faGithub,
6+
Icon,
7+
} from "@rivet-gg/icons";
8+
import type { ReactNode } from "react";
9+
import {
10+
DropdownMenu,
11+
DropdownMenuContent,
12+
DropdownMenuItem,
13+
DropdownMenuTrigger,
14+
} from "@/components";
15+
16+
export const HelpDropdown = ({ children }: { children: ReactNode }) => {
17+
return (
18+
<DropdownMenu>
19+
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
20+
<DropdownMenuContent>
21+
<DropdownMenuItem
22+
indicator={<Icon icon={faGithub} />}
23+
onSelect={() => {
24+
window.open(
25+
"https://github.com/rivet-dev/engine/issues",
26+
"_blank",
27+
);
28+
}}
29+
>
30+
GitHub
31+
</DropdownMenuItem>
32+
<DropdownMenuItem
33+
indicator={<Icon icon={faDiscord} />}
34+
onSelect={() => {
35+
window.open("https://rivet.dev/discord", "_blank");
36+
}}
37+
>
38+
Discord
39+
</DropdownMenuItem>
40+
<DropdownMenuItem
41+
indicator={<Icon icon={faBooks} />}
42+
onSelect={() => {
43+
window.open("https://rivet.dev/docs", "_blank");
44+
}}
45+
>
46+
Documentation
47+
</DropdownMenuItem>
48+
{__APP_TYPE__ === "cloud" ? (
49+
<DropdownMenuItem
50+
indicator={<Icon icon={faComments} />}
51+
onSelect={() => {
52+
Plain.open();
53+
}}
54+
>
55+
Live Chat
56+
</DropdownMenuItem>
57+
) : null}
58+
</DropdownMenuContent>
59+
</DropdownMenu>
60+
);
61+
};

frontend/src/app/layout.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { ActorBuildsList } from "./actor-builds-list";
4848
import { Changelog } from "./changelog";
4949
import { ContextSwitcher } from "./context-switcher";
5050
import { useInspectorCredentials } from "./credentials-context";
51+
import { HelpDropdown } from "./help-dropdown";
5152
import { NamespaceSelect } from "./namespace-select";
5253
import { UserDropdown } from "./user-dropdown";
5354

@@ -207,6 +208,15 @@ const Sidebar = ({
207208
</a>
208209
</Button>
209210
</Changelog>
211+
<HelpDropdown>
212+
<Button
213+
className="text-muted-foreground justify-start py-1 h-auto aria-expanded:text-foreground aria-expanded:bg-accent"
214+
variant="ghost"
215+
size="xs"
216+
>
217+
Support
218+
</Button>
219+
</HelpDropdown>
210220
<DocsSheet
211221
path={"https://rivet.gg/docs"}
212222
title="Documentation"

frontend/src/routes/__root.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ export const Route = createRootRouteWithContext<RootRouteContext>()({
5252
email: context.clerk.user?.primaryEmailAddress
5353
?.emailAddress,
5454
});
55+
56+
Plain.setCustomerDetails({
57+
clerkId: context.clerk.user?.id,
58+
email: context.clerk.user?.primaryEmailAddress
59+
?.emailAddress,
60+
});
5561
return resolve(true);
5662
}
5763
});

frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { faRailway, faVercel, Icon } from "@rivet-gg/icons";
1+
import { faQuestionCircle, faRailway, faVercel, Icon } from "@rivet-gg/icons";
22
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
33
import {
44
createFileRoute,
55
notFound,
66
Link as RouterLink,
77
} from "@tanstack/react-router";
88
import { match } from "ts-pattern";
9+
import { HelpDropdown } from "@/app/help-dropdown";
910
import { RunnersTable } from "@/app/runners-table";
1011
import { Button, DocsSheet, H1, H3, Link, Skeleton } from "@/components";
1112
import {
@@ -34,6 +35,16 @@ function RouteComponent() {
3435
<div className="bg-card h-full border my-2 mr-2 rounded-lg">
3536
<div className="max-w-5xl mt-2 flex justify-between items-center px-6 py-4">
3637
<H1>Connect</H1>
38+
<div>
39+
<HelpDropdown>
40+
<Button
41+
variant="outline"
42+
startIcon={<Icon icon={faQuestionCircle} />}
43+
>
44+
Need help?
45+
</Button>
46+
</HelpDropdown>
47+
</div>
3748
</div>
3849
<p className="max-w-5xl mb-6 px-6 text-muted-foreground">
3950
Connect your RivetKit application to Rivet Cloud. Use your cloud
@@ -51,9 +62,9 @@ function RouteComponent() {
5162
<div className="p-4 px-6 max-w-5xl ">
5263
<Skeleton className="h-8 w-48 mb-4" />
5364
<div className="flex flex-wrap gap-2 my-4">
54-
<Skeleton className="h-10 w-32 rounded-md" />
55-
<Skeleton className="h-10 w-32 rounded-md" />
56-
<Skeleton className="h-10 w-32 rounded-md" />
65+
<Skeleton className="min-w-48 h-auto min-h-28 rounded-md" />
66+
<Skeleton className="min-w-48 h-auto min-h-28 rounded-md" />
67+
<Skeleton className="min-w-48 h-auto min-h-28 rounded-md" />
5768
</div>
5869
</div>
5970
</>
@@ -64,7 +75,7 @@ function RouteComponent() {
6475
<Button
6576
size="lg"
6677
variant="outline"
67-
className="min-w-32"
78+
className="min-w-48 h-auto min-h-28 text-xl"
6879
startIcon={<Icon icon={faRailway} />}
6980
asChild
7081
>
@@ -78,7 +89,7 @@ function RouteComponent() {
7889
<Button
7990
size="lg"
8091
variant="outline"
81-
className="min-w-32"
92+
className="min-w-48 h-auto min-h-28 text-xl"
8293
startIcon={<Icon icon={faVercel} />}
8394
asChild
8495
>

frontend/src/vite-env.d.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,29 @@
22

33
declare const __APP_BUILD_ID__: string;
44
declare const __APP_TYPE__: "engine" | "inspector" | "cloud";
5+
declare const Plain: {
6+
// This takes the same arguments as Plain.init. It will update the chat widget in-place with the new configuration.
7+
// Only top-level fields are updated, nested fields are not merged.
8+
update(params: any): void;
9+
10+
// This takes the same arguments as `customerDetails` in Plain.init.
11+
// This will update just the customer details in the chat widget. This may be useful if you have asynchronous authentication state
12+
setCustomerDetails(params: any): void;
13+
14+
// Opens and closes the widget if using the default, floating mode
15+
open(): void;
16+
close(): void;
17+
18+
// These are event listeners that will be fired when the chat widget is opened or closed respectively
19+
// These return a function that can be called to remove the listener
20+
onOpen(callback: () => void): () => void;
21+
onClose(callback: () => void): () => void;
22+
23+
// Returns whether or not the chat widget is initialized
24+
isInitialized(): boolean;
25+
26+
// This returns an array with debug logs that have been collected by the chat widget
27+
// This is useful if you are contacting Plain support with an issue regarding the chat widget
28+
// This will redact sensitive information such as customer details
29+
exportDebugLogs(): any[];
30+
};

frontend/vite.cloud.config.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { defineConfig, loadEnv, mergeConfig } from "vite";
22
import { cloudEnvSchema } from "./src/lib/env";
3-
import engineConfig from "./vite.engine.config";
3+
import engineConfig, { liveChatPlugin } from "./vite.engine.config";
44

55
// https://vitejs.dev/config/
66
export default defineConfig((config) => {
@@ -10,6 +10,25 @@ export default defineConfig((config) => {
1010
engineConfig(config),
1111
defineConfig({
1212
base: "/",
13+
plugins: [
14+
{
15+
...liveChatPlugin(`<script>
16+
(function(d, script) {
17+
script = d.createElement('script');
18+
script.async = false;
19+
script.onload = function(){
20+
Plain.init({
21+
appId: 'liveChatApp_01K5D3WHR3CGKA56RPRMBB7FX0',
22+
hideLauncher: true,
23+
});
24+
};
25+
script.src = 'https://chat.cdn-plain.com/index.js';
26+
d.getElementsByTagName('head')[0].appendChild(script);
27+
}(document));
28+
</script>`),
29+
enforce: "pre",
30+
},
31+
],
1332
define: {
1433
__APP_TYPE__: JSON.stringify("cloud"),
1534
},
@@ -20,5 +39,6 @@ export default defineConfig((config) => {
2039
port: 43710,
2140
},
2241
}),
42+
true,
2343
);
2444
});

frontend/vite.engine.config.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from "node:path";
33
import { sentryVitePlugin } from "@sentry/vite-plugin";
44
import { tanstackRouter } from "@tanstack/router-plugin/vite";
55
import react from "@vitejs/plugin-react";
6-
import { defineConfig, loadEnv } from "vite";
6+
import { defineConfig, loadEnv, type Plugin } from "vite";
77
import tsconfigPaths from "vite-tsconfig-paths";
88
import { commonEnvSchema } from "./src/lib/env";
99

@@ -21,6 +21,7 @@ export default defineConfig(({ mode }) => {
2121
tanstackRouter({ target: "react", autoCodeSplitting: true }),
2222
tsconfigPaths(),
2323
react(),
24+
liveChatPlugin(),
2425
env.SENTRY_AUTH_TOKEN
2526
? sentryVitePlugin({
2627
org: "rivet-gaming",
@@ -72,3 +73,12 @@ export default defineConfig(({ mode }) => {
7273
},
7374
};
7475
});
76+
77+
export function liveChatPlugin(source: string = ""): Plugin {
78+
return {
79+
name: "live-chat-plugin",
80+
transformIndexHtml(html) {
81+
return html.replace(/{{live_chat}}/, source);
82+
},
83+
};
84+
}

0 commit comments

Comments
 (0)