Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/cloud/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
119 changes: 3 additions & 116 deletions apps/cloud/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 (
<main className="flex min-h-screen items-center justify-center bg-background px-6 py-10">
<section className="w-full max-w-md text-center">
Expand All @@ -209,106 +188,14 @@ function ShellErrorFallback() {
<p className="mt-6 text-xs font-medium uppercase tracking-wide text-muted-foreground">
Get support
</p>
<div className="mt-3 flex flex-wrap items-center justify-center gap-2 text-sm">
<Dialog open={slackOpen} onOpenChange={setSlackOpen}>
<DialogTrigger asChild>
<Button type="button" variant="outline" size="sm" className="gap-2">
<SlackMark className="size-4" />
Slack
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Slack Connect</DialogTitle>
<DialogDescription>
Invite <span className="font-medium text-foreground">rhys@executor.sh</span> to
Slack Connect.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="outline">
Done
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
{supportLinks.map((link) => (
// oxlint-disable-next-line react/jsx-no-new-function-as-prop -- static support link component choice
<a
key={link.label}
href={link.href}
className="inline-flex h-9 items-center gap-2 rounded-md border border-border bg-background px-3 font-medium text-foreground transition-colors hover:bg-muted"
>
<link.icon className="size-4" />
{link.label}
</a>
))}
<div className="mt-3">
<SupportOptions />
</div>
</section>
</main>
);
}

function DiscordMark({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="currentColor" className={className} aria-hidden>
<path d="M20.32 4.37A19.8 19.8 0 0 0 15.36 2.8a13.8 13.8 0 0 0-.64 1.32 18.4 18.4 0 0 0-5.44 0 13.8 13.8 0 0 0-.64-1.32 19.7 19.7 0 0 0-4.97 1.57C.53 9.09-.32 13.69.1 18.22a19.9 19.9 0 0 0 6.08 3.03 14.7 14.7 0 0 0 1.3-2.09 12.8 12.8 0 0 1-2.04-.97l.5-.38a14.2 14.2 0 0 0 12.12 0l.5.38c-.65.38-1.33.7-2.04.97.37.74.8 1.44 1.3 2.09a19.9 19.9 0 0 0 6.08-3.03c.5-5.25-.84-9.8-3.58-13.85ZM8.02 15.43c-1.18 0-2.15-1.08-2.15-2.4 0-1.33.95-2.41 2.15-2.41 1.2 0 2.17 1.09 2.15 2.4 0 1.33-.95 2.41-2.15 2.41Zm7.96 0c-1.18 0-2.15-1.08-2.15-2.4 0-1.33.95-2.41 2.15-2.41 1.2 0 2.17 1.09 2.15 2.4 0 1.33-.95 2.41-2.15 2.41Z" />
</svg>
);
}

function GitHubMark({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="currentColor" className={className} aria-hidden>
<path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-2.15c-3.2.7-3.88-1.36-3.88-1.36-.52-1.33-1.28-1.68-1.28-1.68-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.03 1.77 2.71 1.26 3.37.96.1-.75.4-1.26.73-1.55-2.55-.29-5.24-1.28-5.24-5.68 0-1.25.45-2.28 1.19-3.08-.12-.29-.52-1.46.11-3.04 0 0 .98-.31 3.17 1.18a10.9 10.9 0 0 1 5.78 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.58.23 2.75.11 3.04.74.8 1.19 1.83 1.19 3.08 0 4.42-2.69 5.39-5.25 5.68.41.36.78 1.06.78 2.14v3.16c0 .31.21.67.79.56A11.5 11.5 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5Z" />
</svg>
);
}

function MailMark({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
className={className}
aria-hidden
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 7l9 6 9-6M5 19h14a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2Z"
/>
</svg>
);
}

function SlackMark({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<path
d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313z"
fill="#E01E5A"
/>
<path
d="M8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.527 2.527 0 0 1 2.521 2.521 2.527 2.527 0 0 1-2.521 2.521H2.522A2.527 2.527 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312z"
fill="#36C5F0"
/>
<path
d="M18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zm-1.272 0a2.528 2.528 0 0 1-2.521 2.521 2.527 2.527 0 0 1-2.521-2.521V2.522A2.527 2.527 0 0 1 15.163 0a2.528 2.528 0 0 1 2.521 2.522v6.312z"
fill="#2EB67D"
/>
<path
d="M15.163 18.956a2.528 2.528 0 0 1 2.521 2.522A2.528 2.528 0 0 1 15.163 24a2.527 2.527 0 0 1-2.521-2.522v-2.522h2.521zm0-1.272a2.527 2.527 0 0 1-2.521-2.521 2.527 2.527 0 0 1 2.521-2.521h6.315A2.527 2.527 0 0 1 24 15.163a2.528 2.528 0 0 1-2.522 2.521h-6.315z"
fill="#ECB22E"
/>
</svg>
);
}

function AuthGate() {
const auth = useAuth();

Expand Down
112 changes: 112 additions & 0 deletions apps/cloud/src/web/components/support-options.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex flex-wrap items-center justify-center gap-2 text-sm">
<Popover>
<PopoverTrigger asChild>
<Button type="button" variant="outline" size="sm" className="gap-2">
<SlackMark className="size-4" />
Slack
</Button>
</PopoverTrigger>
<PopoverContent className="w-72">
<PopoverHeader>
<PopoverTitle>Slack Connect</PopoverTitle>
<PopoverDescription>
Invite <span className="font-medium text-foreground">rhys@executor.sh</span> to Slack
Connect.
</PopoverDescription>
</PopoverHeader>
</PopoverContent>
</Popover>
{supportLinks.map((link) => (
// oxlint-disable-next-line react/jsx-no-new-function-as-prop -- static support link component choice
<a
key={link.label}
href={link.href}
className="inline-flex h-9 items-center gap-2 rounded-md border border-border bg-background px-3 font-medium text-foreground transition-colors hover:bg-muted"
>
<link.icon className="size-4" />
{link.label}
</a>
))}
</div>
);
}

function DiscordMark({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="currentColor" className={className} aria-hidden>
<path d="M20.32 4.37A19.8 19.8 0 0 0 15.36 2.8a13.8 13.8 0 0 0-.64 1.32 18.4 18.4 0 0 0-5.44 0 13.8 13.8 0 0 0-.64-1.32 19.7 19.7 0 0 0-4.97 1.57C.53 9.09-.32 13.69.1 18.22a19.9 19.9 0 0 0 6.08 3.03 14.7 14.7 0 0 0 1.3-2.09 12.8 12.8 0 0 1-2.04-.97l.5-.38a14.2 14.2 0 0 0 12.12 0l.5.38c-.65.38-1.33.7-2.04.97.37.74.8 1.44 1.3 2.09a19.9 19.9 0 0 0 6.08-3.03c.5-5.25-.84-9.8-3.58-13.85ZM8.02 15.43c-1.18 0-2.15-1.08-2.15-2.4 0-1.33.95-2.41 2.15-2.41 1.2 0 2.17 1.09 2.15 2.4 0 1.33-.95 2.41-2.15 2.41Zm7.96 0c-1.18 0-2.15-1.08-2.15-2.4 0-1.33.95-2.41 2.15-2.41 1.2 0 2.17 1.09 2.15 2.4 0 1.33-.95 2.41-2.15 2.41Z" />
</svg>
);
}

function GitHubMark({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="currentColor" className={className} aria-hidden>
<path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-2.15c-3.2.7-3.88-1.36-3.88-1.36-.52-1.33-1.28-1.68-1.28-1.68-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.03 1.77 2.71 1.26 3.37.96.1-.75.4-1.26.73-1.55-2.55-.29-5.24-1.28-5.24-5.68 0-1.25.45-2.28 1.19-3.08-.12-.29-.52-1.46.11-3.04 0 0 .98-.31 3.17 1.18a10.9 10.9 0 0 1 5.78 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.58.23 2.75.11 3.04.74.8 1.19 1.83 1.19 3.08 0 4.42-2.69 5.39-5.25 5.68.41.36.78 1.06.78 2.14v3.16c0 .31.21.67.79.56A11.5 11.5 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5Z" />
</svg>
);
}

function MailMark({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
className={className}
aria-hidden
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 7l9 6 9-6M5 19h14a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2Z"
/>
</svg>
);
}

function SlackMark({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<path
d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313z"
fill="#E01E5A"
/>
<path
d="M8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.527 2.527 0 0 1 2.521 2.521 2.527 2.527 0 0 1-2.521 2.521H2.522A2.527 2.527 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312z"
fill="#36C5F0"
/>
<path
d="M18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zm-1.272 0a2.528 2.528 0 0 1-2.521 2.521 2.527 2.527 0 0 1-2.521-2.521V2.522A2.527 2.527 0 0 1 15.163 0a2.528 2.528 0 0 1 2.521 2.522v6.312z"
fill="#2EB67D"
/>
<path
d="M15.163 18.956a2.528 2.528 0 0 1 2.521 2.522A2.528 2.528 0 0 1 15.163 24a2.527 2.527 0 0 1-2.521-2.522v-2.522h2.521zm0-1.272a2.527 2.527 0 0 1-2.521-2.521 2.527 2.527 0 0 1 2.521-2.521h6.315A2.527 2.527 0 0 1 24 15.163a2.528 2.528 0 0 1-2.522 2.521h-6.315z"
fill="#ECB22E"
/>
</svg>
);
}
76 changes: 65 additions & 11 deletions apps/cloud/src/web/shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
DialogHeader,
DialogTitle,
} from "@executor-js/react/components/dialog";
import { SupportOptions } from "./components/support-options";
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -37,6 +38,19 @@ import {
useCreateOrganizationForm,
} from "./components/create-organization-form";

// ── Brand ────────────────────────────────────────────────────────────────

function Brand(props: { onNavigate?: () => void }) {
return (
<Link to="/" onClick={props.onNavigate} className="flex items-center gap-1.5">
<span className="font-display text-base tracking-tight text-foreground">executor</span>
<span className="rounded-sm bg-primary/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-primary">
Beta
</span>
</Link>
);
}

// ── NavItem ──────────────────────────────────────────────────────────────

function NavItem(props: { to: string; label: string; active: boolean; onNavigate?: () => void }) {
Expand Down Expand Up @@ -351,6 +365,50 @@ function UserFooter() {
);
}

// ── SupportButton ────────────────────────────────────────────────────────

function HelpIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 16 16" fill="none" className={className} aria-hidden>
<circle cx="8" cy="8" r="6.5" stroke="currentColor" strokeWidth="1.3" />
<path
d="M6.25 6.25c.25-1 1-1.5 1.85-1.5 1 0 1.9.7 1.9 1.7 0 .8-.5 1.2-1.1 1.6-.55.4-.9.7-.9 1.45M8 11.25v.05"
stroke="currentColor"
strokeWidth="1.3"
strokeLinecap="round"
/>
</svg>
);
}

function SupportButton() {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<Button
type="button"
variant="ghost"
onClick={() => setOpen(true)}
className="flex h-auto w-full items-center justify-start gap-2.5 rounded-md px-2.5 py-1.5 text-sm font-normal text-sidebar-foreground hover:bg-sidebar-active/60 hover:text-foreground"
>
<HelpIcon className="size-3.5 text-muted-foreground" />
Get support
</Button>
<DialogContent className="sm:max-w-[440px]">
<DialogHeader>
<DialogTitle className="font-display text-xl">Get support</DialogTitle>
<DialogDescription className="text-sm leading-relaxed">
Reach out through any of the channels below.
</DialogDescription>
</DialogHeader>
<div className="py-2">
<SupportOptions />
</div>
</DialogContent>
</Dialog>
);
}

// ── SidebarContent ───────────────────────────────────────────────────────

function SidebarContent(props: { pathname: string; onNavigate?: () => void; showBrand?: boolean }) {
Expand All @@ -365,9 +423,7 @@ function SidebarContent(props: { pathname: string; onNavigate?: () => void; show
<>
{props.showBrand !== false && (
<div className="flex h-12 shrink-0 items-center border-b border-sidebar-border px-4">
<Link to="/" className="flex items-center gap-1.5">
<span className="font-display text-base tracking-tight text-foreground">executor</span>
</Link>
<Brand onNavigate={props.onNavigate} />
</div>
)}

Expand Down Expand Up @@ -396,6 +452,10 @@ function SidebarContent(props: { pathname: string; onNavigate?: () => void; show
<SourceList pathname={props.pathname} onNavigate={props.onNavigate} />
</nav>

<div className="shrink-0 px-2 pb-2">
<SupportButton />
</div>

<UserFooter />
</>
);
Expand Down Expand Up @@ -443,11 +503,7 @@ export function Shell() {
/>
<div className="relative flex h-full w-[84vw] max-w-xs flex-col border-r border-sidebar-border bg-sidebar shadow-2xl">
<div className="flex h-12 shrink-0 items-center justify-between border-b border-sidebar-border px-4">
<Link to="/" className="flex items-center gap-1.5">
<span className="font-display text-base tracking-tight text-foreground">
executor
</span>
</Link>
<Brand onNavigate={() => setMobileSidebarOpen(false)} />
<Button
variant="ghost"
size="icon-sm"
Expand Down Expand Up @@ -496,9 +552,7 @@ export function Shell() {
/>
</svg>
</Button>
<Link to="/" className="flex items-center gap-1.5">
<span className="font-display text-base tracking-tight text-foreground">executor</span>
</Link>
<Brand />
<div className="w-8 shrink-0" />
</div>

Expand Down
Loading