From 51b097bb9241645e1df9f73268755f4be8d1b64a Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Fri, 8 May 2026 17:03:54 -0700 Subject: [PATCH] feat(cloud): beta badge, Get support sidebar modal, dev proxy auto-restart - Add Beta badge next to the executor brand in sidebar and mobile top bar - Add Get support entry above the user footer that opens a modal with the same support options (Slack, Discord, GitHub, Email) as the error fallback - Extract SupportOptions into a shared component; switch Slack to a Popover so it nests cleanly under the support modal - dev:proxy: auto-stop and restart when the running proxy has a different config, so vite no longer silently exits in multiplex/shared-port mode --- apps/cloud/package.json | 2 +- apps/cloud/src/routes/__root.tsx | 119 +----------------- .../src/web/components/support-options.tsx | 112 +++++++++++++++++ apps/cloud/src/web/shell.tsx | 76 +++++++++-- 4 files changed, 181 insertions(+), 128 deletions(-) create mode 100644 apps/cloud/src/web/components/support-options.tsx diff --git a/apps/cloud/package.json b/apps/cloud/package.json index 7828b342b..94e805db8 100644 --- a/apps/cloud/package.json +++ b/apps/cloud/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "bun run dev:proxy && concurrently -n db,vite -c blue,green \"bun run dev:db\" \"bun run dev:vite\"", - "dev:proxy": "portless proxy start --multiplex --shared-port --port 5394 || true", + "dev:proxy": "portless proxy start --multiplex --shared-port --port 5394 || (portless proxy stop -p 5394 && portless proxy start --multiplex --shared-port --port 5394)", "dev:db": "bun run scripts/dev-db.ts", "dev:vite": "EXECUTOR_DIRECT_DATABASE_URL=true CLOUDFLARE_INCLUDE_PROCESS_ENV=true op run --env-file=.env.op -- portless --name executor-cloud vite dev", "db:schema": "node --import jiti/register ../../packages/core/cli/src/index.ts generate --config ./executor.config.ts --output ./src/services/executor-schema.ts", diff --git a/apps/cloud/src/routes/__root.tsx b/apps/cloud/src/routes/__root.tsx index c7aa72caa..334fbe9ef 100644 --- a/apps/cloud/src/routes/__root.tsx +++ b/apps/cloud/src/routes/__root.tsx @@ -6,22 +6,12 @@ import posthog from "posthog-js"; import { PostHogProvider } from "posthog-js/react"; import type { FrontendErrorReporter } from "@executor-js/react/api/error-reporting"; import { ExecutorProvider } from "@executor-js/react/api/provider"; -import { Button } from "@executor-js/react/components/button"; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@executor-js/react/components/dialog"; import { Skeleton } from "@executor-js/react/components/skeleton"; import { Toaster } from "@executor-js/react/components/sonner"; import { ExecutorPluginsProvider } from "@executor-js/sdk/client"; import { plugins as clientPlugins } from "virtual:executor/plugins-client"; import { AuthProvider, useAuth } from "../web/auth"; +import { SupportOptions } from "../web/components/support-options"; import { LoginPage } from "../web/pages/login"; import { OnboardingPage } from "../web/pages/onboarding"; import { Shell } from "../web/shell"; @@ -185,17 +175,6 @@ function ShellSkeleton() { } function ShellErrorFallback() { - const [slackOpen, setSlackOpen] = React.useState(false); - const supportLinks = [ - { label: "Discord", href: "https://discord.gg/eF29HBHwM6", icon: DiscordMark }, - { - label: "GitHub Issues", - href: "https://github.com/RhysSullivan/executor/issues", - icon: GitHubMark, - }, - { label: "Email", href: "mailto:rhys@executor.sh?subject=Executor%20support", icon: MailMark }, - ] as const; - return (
@@ -209,106 +188,14 @@ function ShellErrorFallback() {

Get support

-
- - - - - - - Slack Connect - - Invite rhys@executor.sh to - Slack Connect. - - - - - - - - - - {supportLinks.map((link) => ( - // oxlint-disable-next-line react/jsx-no-new-function-as-prop -- static support link component choice - - - {link.label} - - ))} +
+
); } -function DiscordMark({ className }: { className?: string }) { - return ( - - - - ); -} - -function GitHubMark({ className }: { className?: string }) { - return ( - - - - ); -} - -function MailMark({ className }: { className?: string }) { - return ( - - - - ); -} - -function SlackMark({ className }: { className?: string }) { - return ( - - - - - - - ); -} - function AuthGate() { const auth = useAuth(); diff --git a/apps/cloud/src/web/components/support-options.tsx b/apps/cloud/src/web/components/support-options.tsx new file mode 100644 index 000000000..68bce2b43 --- /dev/null +++ b/apps/cloud/src/web/components/support-options.tsx @@ -0,0 +1,112 @@ +import { Button } from "@executor-js/react/components/button"; +import { + Popover, + PopoverContent, + PopoverDescription, + PopoverHeader, + PopoverTitle, + PopoverTrigger, +} from "@executor-js/react/components/popover"; + +const supportLinks = [ + { label: "Discord", href: "https://discord.gg/eF29HBHwM6", icon: DiscordMark }, + { + label: "GitHub Issues", + href: "https://github.com/RhysSullivan/executor/issues", + icon: GitHubMark, + }, + { label: "Email", href: "mailto:rhys@executor.sh?subject=Executor%20support", icon: MailMark }, +] as const; + +export function SupportOptions() { + return ( +
+ + + + + + + Slack Connect + + Invite rhys@executor.sh to Slack + Connect. + + + + + {supportLinks.map((link) => ( + // oxlint-disable-next-line react/jsx-no-new-function-as-prop -- static support link component choice + + + {link.label} + + ))} +
+ ); +} + +function DiscordMark({ className }: { className?: string }) { + return ( + + + + ); +} + +function GitHubMark({ className }: { className?: string }) { + return ( + + + + ); +} + +function MailMark({ className }: { className?: string }) { + return ( + + + + ); +} + +function SlackMark({ className }: { className?: string }) { + return ( + + + + + + + ); +} diff --git a/apps/cloud/src/web/shell.tsx b/apps/cloud/src/web/shell.tsx index fe2c17e35..23d016953 100644 --- a/apps/cloud/src/web/shell.tsx +++ b/apps/cloud/src/web/shell.tsx @@ -16,6 +16,7 @@ import { DialogHeader, DialogTitle, } from "@executor-js/react/components/dialog"; +import { SupportOptions } from "./components/support-options"; import { DropdownMenu, DropdownMenuContent, @@ -37,6 +38,19 @@ import { useCreateOrganizationForm, } from "./components/create-organization-form"; +// ── Brand ──────────────────────────────────────────────────────────────── + +function Brand(props: { onNavigate?: () => void }) { + return ( + + executor + + Beta + + + ); +} + // ── NavItem ────────────────────────────────────────────────────────────── function NavItem(props: { to: string; label: string; active: boolean; onNavigate?: () => void }) { @@ -351,6 +365,50 @@ function UserFooter() { ); } +// ── SupportButton ──────────────────────────────────────────────────────── + +function HelpIcon({ className }: { className?: string }) { + return ( + + + + + ); +} + +function SupportButton() { + const [open, setOpen] = useState(false); + return ( + + + + + Get support + + Reach out through any of the channels below. + + +
+ +
+
+
+ ); +} + // ── SidebarContent ─────────────────────────────────────────────────────── function SidebarContent(props: { pathname: string; onNavigate?: () => void; showBrand?: boolean }) { @@ -365,9 +423,7 @@ function SidebarContent(props: { pathname: string; onNavigate?: () => void; show <> {props.showBrand !== false && (
- - executor - +
)} @@ -396,6 +452,10 @@ function SidebarContent(props: { pathname: string; onNavigate?: () => void; show +
+ +
+ ); @@ -443,11 +503,7 @@ export function Shell() { />
- - - executor - - + setMobileSidebarOpen(false)} /> - - executor - +