@agent-native/dispatch 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/components/app-keys-popover.d.ts.map +1 -1
  2. package/dist/components/app-keys-popover.js +2 -1
  3. package/dist/components/app-keys-popover.js.map +1 -1
  4. package/dist/components/create-app-popover.js +1 -1
  5. package/dist/components/create-app-popover.js.map +1 -1
  6. package/dist/components/layout/Layout.js +3 -3
  7. package/dist/components/layout/Layout.js.map +1 -1
  8. package/dist/routes/index.d.ts.map +1 -1
  9. package/dist/routes/index.js +5 -0
  10. package/dist/routes/index.js.map +1 -1
  11. package/dist/routes/pages/$appId.d.ts +8 -0
  12. package/dist/routes/pages/$appId.d.ts.map +1 -0
  13. package/dist/routes/pages/$appId.js +91 -0
  14. package/dist/routes/pages/$appId.js.map +1 -0
  15. package/dist/routes/pages/approval.d.ts.map +1 -1
  16. package/dist/routes/pages/approval.js +2 -1
  17. package/dist/routes/pages/approval.js.map +1 -1
  18. package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
  19. package/dist/routes/pages/apps.$appId.js +2 -1
  20. package/dist/routes/pages/apps.$appId.js.map +1 -1
  21. package/dist/routes/pages/integrations.d.ts +1 -1
  22. package/dist/routes/pages/integrations.d.ts.map +1 -1
  23. package/dist/routes/pages/integrations.js +131 -30
  24. package/dist/routes/pages/integrations.js.map +1 -1
  25. package/dist/routes/pages/overview.d.ts.map +1 -1
  26. package/dist/routes/pages/overview.js +10 -1
  27. package/dist/routes/pages/overview.js.map +1 -1
  28. package/dist/routes/pages/vault.d.ts.map +1 -1
  29. package/dist/routes/pages/vault.js +4 -3
  30. package/dist/routes/pages/vault.js.map +1 -1
  31. package/dist/routes/pages/workspace.d.ts.map +1 -1
  32. package/dist/routes/pages/workspace.js +5 -3
  33. package/dist/routes/pages/workspace.js.map +1 -1
  34. package/dist/server/plugins/integrations.js +1 -1
  35. package/dist/server/plugins/integrations.js.map +1 -1
  36. package/package.json +2 -2
  37. package/src/components/app-keys-popover.tsx +15 -1
  38. package/src/components/create-app-popover.tsx +1 -1
  39. package/src/components/layout/Layout.tsx +3 -3
  40. package/src/routes/index.ts +5 -0
  41. package/src/routes/pages/$appId.tsx +178 -0
  42. package/src/routes/pages/approval.tsx +33 -3
  43. package/src/routes/pages/apps.$appId.tsx +6 -1
  44. package/src/routes/pages/integrations.tsx +348 -215
  45. package/src/routes/pages/overview.tsx +58 -26
  46. package/src/routes/pages/vault.tsx +25 -12
  47. package/src/routes/pages/workspace.tsx +21 -3
  48. package/src/server/plugins/integrations.ts +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"$appId.js","sourceRoot":"","sources":["../../../src/routes/pages/$appId.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo } from \"react\";\nimport {\n Link,\n Navigate,\n redirect,\n useParams,\n type LoaderFunctionArgs,\n} from \"react-router\";\nimport { useActionQuery, appPath } from \"@agent-native/core/client\";\nimport { loadWorkspaceAppsManifest } from \"@agent-native/core/server/agent-discovery\";\nimport {\n IconArrowLeft,\n IconArrowUpRight,\n IconClockHour4,\n} from \"@tabler/icons-react\";\nimport { DispatchShell } from \"@/components/dispatch-shell\";\nimport { Spinner } from \"@/components/ui/spinner\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n workspaceAppHref,\n type WorkspaceAppSummary,\n} from \"@/lib/workspace-apps\";\n\nexport function meta() {\n return [{ title: \"Workspace app - Dispatch\" }];\n}\n\n/**\n * Catch-all for `/dispatch/<segment>` paths that don't match an explicit\n * Dispatch route. When `<segment>` is the id of a workspace app sibling\n * (e.g. `/dispatch/todo` after Builder.io routes a \"navigate to /todo\"\n * call through Dispatch's mount point), bounce to the absolute `/<appId>`\n * so the user lands on the actual app instead of a 404 inside Dispatch.\n *\n * Server-side redirect: we resolve the workspace app manifest via the\n * shared `loadWorkspaceAppsManifest()` helper, which checks the\n * `AGENT_NATIVE_WORKSPACE_APPS_JSON` env var, then the\n * `.agent-native/workspace-apps.json` file written by `workspace-deploy.ts`,\n * then a live filesystem scan of `apps/` for local dev. We then throw\n * `redirect(\"/<appId>\")`. React Router 7 does not prepend the basename to\n * absolute paths returned from a loader, so the redirect escapes Dispatch's\n * `/dispatch` mount cleanly.\n *\n * Why a catch-all instead of fixing the agent prompt: Builder.io currently\n * resolves \"navigate to /todo\" relative to Dispatch's mount, sending the\n * user to /dispatch/todo. The same wrong path then gets captured as the\n * OAuth callbackURL, so Google sign-in completes back at /dispatch/todo\n * and looks broken. This route fixes both the post-creation navigation\n * and the OAuth round-trip from a single place.\n *\n * `appId === \"dispatch\"` short-circuit: when the segment matches Dispatch\n * itself (e.g. `/dispatch/dispatch`), we go straight to the overview rather\n * than chaining through `/dispatch` (which polled `useActionQuery` re-fired\n * `window.location.assign` against and looped forever in production).\n */\nfunction dispatchSelfRedirect(appId: string | undefined): string | null {\n if (appId === \"dispatch\") return appPath(\"/overview\");\n return null;\n}\n\nexport function loader({ params }: LoaderFunctionArgs) {\n const appId = params.appId;\n if (!appId) return null;\n const selfTarget = dispatchSelfRedirect(appId);\n if (selfTarget) throw redirect(selfTarget);\n const apps = loadWorkspaceAppsManifest();\n if (!apps) return null;\n const app = apps.find((entry) => entry?.id === appId);\n const target =\n app?.path && app.path.startsWith(\"/\") ? app.path : app ? `/${appId}` : null;\n if (target) throw redirect(target);\n return null;\n}\n\nexport function clientLoader({ params }: LoaderFunctionArgs) {\n const selfTarget = dispatchSelfRedirect(params.appId);\n if (selfTarget) throw redirect(selfTarget);\n return null;\n}\n\nexport default function WorkspaceAppCatchAllRoute() {\n const { appId } = useParams();\n const { data: apps = [], isLoading } = useActionQuery(\n \"list-workspace-apps\",\n { includeAgentCards: false },\n { refetchInterval: 2_000 },\n );\n const app = useMemo(\n () =>\n (apps as WorkspaceAppSummary[]).find((item) => item.id === appId) ?? null,\n [appId, apps],\n );\n const href = app ? workspaceAppHref(app) : null;\n const isSelfReference = appId === \"dispatch\";\n\n useEffect(() => {\n if (isSelfReference) return;\n if (!app || app.status === \"pending\" || !href) return;\n window.location.assign(href);\n }, [app, href, isSelfReference]);\n\n if (isSelfReference) {\n return <Navigate to={appPath(\"/overview\")} replace />;\n }\n\n if ((isLoading && !app) || (app && app.status !== \"pending\" && href)) {\n return (\n <div className=\"flex h-screen w-full items-center justify-center\">\n <Spinner className=\"size-8\" />\n </div>\n );\n }\n\n return (\n <DispatchShell\n title={app?.name || \"Page not found\"}\n description=\"This route is not in the workspace app list yet.\"\n >\n <div className=\"max-w-2xl rounded-lg border bg-card p-5\">\n <Button asChild size=\"sm\" variant=\"ghost\" className=\"-ml-2 mb-4\">\n <Link to={appPath(\"/overview\")}>\n <IconArrowLeft size={15} className=\"mr-1.5\" />\n Overview\n </Link>\n </Button>\n\n {app?.status === \"pending\" ? (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {app.name}\n </h2>\n <Badge\n variant=\"outline\"\n className=\"gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\"\n >\n <IconClockHour4 size={12} />\n Building\n </Badge>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n This app is being created. It will be available at{\" \"}\n <span className=\"font-mono text-foreground\">{app.path}</span>{\" \"}\n after its branch is merged and the workspace deploy finishes.\n </p>\n {app.branchName ? (\n <p className=\"text-xs text-muted-foreground\">\n Branch: {app.branchName}\n </p>\n ) : null}\n {app.builderUrl ? (\n <Button asChild>\n <a href={app.builderUrl} target=\"_blank\" rel=\"noreferrer\">\n Open Builder branch\n <IconArrowUpRight size={15} className=\"ml-1.5\" />\n </a>\n </Button>\n ) : null}\n </div>\n ) : (\n <div className=\"space-y-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n Page not found\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n <span className=\"font-mono text-foreground\">/{appId}</span> isn't\n a Dispatch tab or a workspace app in this workspace.\n </p>\n <Button asChild>\n <Link to={appPath(\"/apps\")}>Browse apps</Link>\n </Button>\n </div>\n )}\n </div>\n </DispatchShell>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/approval.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,IAAI;;IAEnB;AAoCD,MAAM,CAAC,OAAO,UAAU,oBAAoB,4CAwL3C"}
1
+ {"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/approval.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,IAAI;;IAEnB;AAoCD,MAAM,CAAC,OAAO,UAAU,oBAAoB,4CAqN3C"}
@@ -4,6 +4,7 @@ import { useActionMutation, useActionQuery, isInAgentEmbed, postNavigate, appPat
4
4
  import { toast } from "sonner";
5
5
  import { Button } from "../../components/ui/button.js";
6
6
  import { Badge } from "../../components/ui/badge.js";
7
+ import { Skeleton } from "../../components/ui/skeleton.js";
7
8
  import { IconCheck, IconX, IconArrowUpRight, IconShieldCheck, IconClock, IconAlertCircle, } from "@tabler/icons-react";
8
9
  export function meta() {
9
10
  return [{ title: "Approval — Dispatch" }];
@@ -33,7 +34,7 @@ export default function ApprovalPreviewRoute() {
33
34
  return (_jsx("div", { className: "flex min-h-screen items-center justify-center bg-background p-6", children: _jsxs("div", { className: "w-full max-w-md rounded-2xl border bg-card p-6 text-center", children: [_jsx(IconAlertCircle, { size: 32, className: "mx-auto mb-3 text-muted-foreground" }), _jsx("p", { className: "text-sm font-medium text-foreground", children: "No approval id provided" }), _jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: ["Add ", _jsx("code", { className: "rounded bg-muted px-1", children: "?id=<id>" }), " to the URL."] })] }) }));
34
35
  }
35
36
  if (isLoading) {
36
- return (_jsx("div", { className: "flex min-h-screen items-center justify-center bg-background p-6", children: _jsx("div", { className: "w-full max-w-md rounded-2xl border bg-card p-6 text-center", children: _jsx("p", { className: "text-sm text-muted-foreground", children: "Loading..." }) }) }));
37
+ return (_jsx("div", { className: "flex min-h-screen items-start justify-center bg-background p-6", children: _jsx("div", { className: "w-full max-w-md space-y-4", children: _jsxs("div", { className: "rounded-2xl border bg-card p-5", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx(Skeleton, { className: "h-9 w-9 shrink-0 rounded-xl" }), _jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Skeleton, { className: "h-4 w-40" }), _jsx(Skeleton, { className: "h-5 w-20 rounded-full" })] }), _jsx(Skeleton, { className: "h-3 w-32" })] })] }), _jsxs("div", { className: "mt-4 space-y-2 rounded-xl border bg-muted/30 px-4 py-3", children: [_jsxs("div", { className: "flex justify-between gap-4", children: [_jsx(Skeleton, { className: "h-3 w-20" }), _jsx(Skeleton, { className: "h-3 w-24" })] }), _jsxs("div", { className: "flex justify-between gap-4", children: [_jsx(Skeleton, { className: "h-3 w-20" }), _jsx(Skeleton, { className: "h-3 w-28" })] }), _jsxs("div", { className: "flex justify-between gap-4", children: [_jsx(Skeleton, { className: "h-3 w-16" }), _jsx(Skeleton, { className: "h-3 w-32" })] })] }), _jsxs("div", { className: "mt-4 flex gap-2", children: [_jsx(Skeleton, { className: "h-8 flex-1 rounded-md" }), _jsx(Skeleton, { className: "h-8 flex-1 rounded-md" })] })] }) }) }));
37
38
  }
38
39
  if (!approval) {
39
40
  return (_jsx("div", { className: "flex min-h-screen items-center justify-center bg-background p-6", children: _jsxs("div", { className: "w-full max-w-md rounded-2xl border bg-card p-6 text-center", children: [_jsx(IconAlertCircle, { size: 32, className: "mx-auto mb-3 text-muted-foreground" }), _jsx("p", { className: "text-sm font-medium text-foreground", children: "Approval not found" }), _jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: ["The approval with id", " ", _jsx("code", { className: "rounded bg-muted px-1", children: id }), " does not exist."] }), inEmbed && (_jsxs(Button, { size: "sm", variant: "outline", className: "mt-4 gap-1.5", onClick: () => postNavigate(appPath("/approvals")), children: [_jsx(IconArrowUpRight, { size: 14 }), "View all approvals"] }))] }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"approval.js","sourceRoot":"","sources":["../../../src/routes/pages/approval.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useSearchParams } from \"react-router\";\nimport {\n useActionMutation,\n useActionQuery,\n isInAgentEmbed,\n postNavigate,\n appPath,\n} from \"@agent-native/core/client\";\nimport { toast } from \"sonner\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n IconCheck,\n IconX,\n IconArrowUpRight,\n IconShieldCheck,\n IconClock,\n IconAlertCircle,\n} from \"@tabler/icons-react\";\n\nexport function meta() {\n return [{ title: \"Approval — Dispatch\" }];\n}\n\nfunction StatusBadge({ status }: { status: string }) {\n if (status === \"pending\") {\n return (\n <Badge\n variant=\"outline\"\n className=\"gap-1.5 border-amber-500/40 bg-amber-500/10 text-amber-600 dark:text-amber-400\"\n >\n <IconClock size={11} />\n Pending\n </Badge>\n );\n }\n if (status === \"approved\") {\n return (\n <Badge\n variant=\"outline\"\n className=\"gap-1.5 border-emerald-500/40 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400\"\n >\n <IconCheck size={11} />\n Approved\n </Badge>\n );\n }\n return (\n <Badge\n variant=\"outline\"\n className=\"gap-1.5 border-red-500/40 bg-red-500/10 text-red-600 dark:text-red-400\"\n >\n <IconX size={11} />\n Rejected\n </Badge>\n );\n}\n\nexport default function ApprovalPreviewRoute() {\n const [searchParams] = useSearchParams();\n const id = searchParams.get(\"id\") ?? \"\";\n\n const { data: approvals, isLoading } = useActionQuery(\n \"list-dispatch-approvals\",\n {},\n );\n\n const approve = useActionMutation(\"approve-dispatch-change\", {\n onSuccess: () => toast.success(\"Change approved\"),\n });\n const reject = useActionMutation(\"reject-dispatch-change\", {\n onSuccess: () => toast.success(\"Change rejected\"),\n });\n\n const inEmbed = isInAgentEmbed();\n\n const approval = approvals?.find((item) => item.id === id) ?? null;\n\n if (!id) {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-background p-6\">\n <div className=\"w-full max-w-md rounded-2xl border bg-card p-6 text-center\">\n <IconAlertCircle\n size={32}\n className=\"mx-auto mb-3 text-muted-foreground\"\n />\n <p className=\"text-sm font-medium text-foreground\">\n No approval id provided\n </p>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n Add <code className=\"rounded bg-muted px-1\">?id=&lt;id&gt;</code> to\n the URL.\n </p>\n </div>\n </div>\n );\n }\n\n if (isLoading) {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-background p-6\">\n <div className=\"w-full max-w-md rounded-2xl border bg-card p-6 text-center\">\n <p className=\"text-sm text-muted-foreground\">Loading...</p>\n </div>\n </div>\n );\n }\n\n if (!approval) {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-background p-6\">\n <div className=\"w-full max-w-md rounded-2xl border bg-card p-6 text-center\">\n <IconAlertCircle\n size={32}\n className=\"mx-auto mb-3 text-muted-foreground\"\n />\n <p className=\"text-sm font-medium text-foreground\">\n Approval not found\n </p>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n The approval with id{\" \"}\n <code className=\"rounded bg-muted px-1\">{id}</code> does not exist.\n </p>\n {inEmbed && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"mt-4 gap-1.5\"\n onClick={() => postNavigate(appPath(\"/approvals\"))}\n >\n <IconArrowUpRight size={14} />\n View all approvals\n </Button>\n )}\n </div>\n </div>\n );\n }\n\n const isPending = approval.status === \"pending\";\n\n return (\n <div className=\"flex min-h-screen items-start justify-center bg-background p-6\">\n <div className=\"w-full max-w-md space-y-4\">\n <div className=\"rounded-2xl border bg-card p-5\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-xl border bg-muted text-foreground\">\n <IconShieldCheck size={17} />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">\n {approval.summary}\n </span>\n <StatusBadge status={approval.status} />\n </div>\n <div className=\"mt-1 text-xs text-muted-foreground\">\n Requested by{\" \"}\n <span className=\"font-medium text-foreground\">\n {approval.requestedBy}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"mt-4 space-y-2 rounded-xl border bg-muted/30 px-4 py-3 text-xs\">\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">Change type</span>\n <span className=\"font-mono font-medium text-foreground\">\n {approval.changeType}\n </span>\n </div>\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">Target type</span>\n <span className=\"font-mono font-medium text-foreground\">\n {approval.targetType}\n </span>\n </div>\n {approval.targetId && (\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">Target id</span>\n <span className=\"truncate font-mono font-medium text-foreground\">\n {approval.targetId}\n </span>\n </div>\n )}\n {approval.reviewedBy && (\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">Reviewed by</span>\n <span className=\"font-medium text-foreground\">\n {approval.reviewedBy}\n </span>\n </div>\n )}\n </div>\n\n {isPending && (\n <div className=\"mt-4 flex gap-2\">\n <Button\n size=\"sm\"\n className=\"flex-1\"\n disabled={approve.isPending || reject.isPending}\n onClick={() => approve.mutate({ id: approval.id })}\n >\n <IconCheck size={14} className=\"mr-1.5\" />\n Approve\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"flex-1\"\n disabled={approve.isPending || reject.isPending}\n onClick={() =>\n reject.mutate({\n id: approval.id,\n reason: \"Rejected in dispatch UI\",\n })\n }\n >\n <IconX size={14} className=\"mr-1.5\" />\n Reject\n </Button>\n </div>\n )}\n </div>\n\n {inEmbed && (\n <div className=\"flex justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n className=\"gap-1.5 text-muted-foreground\"\n onClick={() => postNavigate(appPath(\"/approvals\"))}\n >\n <IconArrowUpRight size={14} />\n Open in app\n </Button>\n </div>\n )}\n </div>\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"approval.js","sourceRoot":"","sources":["../../../src/routes/pages/approval.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useSearchParams } from \"react-router\";\nimport {\n useActionMutation,\n useActionQuery,\n isInAgentEmbed,\n postNavigate,\n appPath,\n} from \"@agent-native/core/client\";\nimport { toast } from \"sonner\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n IconCheck,\n IconX,\n IconArrowUpRight,\n IconShieldCheck,\n IconClock,\n IconAlertCircle,\n} from \"@tabler/icons-react\";\n\nexport function meta() {\n return [{ title: \"Approval — Dispatch\" }];\n}\n\nfunction StatusBadge({ status }: { status: string }) {\n if (status === \"pending\") {\n return (\n <Badge\n variant=\"outline\"\n className=\"gap-1.5 border-amber-500/40 bg-amber-500/10 text-amber-600 dark:text-amber-400\"\n >\n <IconClock size={11} />\n Pending\n </Badge>\n );\n }\n if (status === \"approved\") {\n return (\n <Badge\n variant=\"outline\"\n className=\"gap-1.5 border-emerald-500/40 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400\"\n >\n <IconCheck size={11} />\n Approved\n </Badge>\n );\n }\n return (\n <Badge\n variant=\"outline\"\n className=\"gap-1.5 border-red-500/40 bg-red-500/10 text-red-600 dark:text-red-400\"\n >\n <IconX size={11} />\n Rejected\n </Badge>\n );\n}\n\nexport default function ApprovalPreviewRoute() {\n const [searchParams] = useSearchParams();\n const id = searchParams.get(\"id\") ?? \"\";\n\n const { data: approvals, isLoading } = useActionQuery(\n \"list-dispatch-approvals\",\n {},\n );\n\n const approve = useActionMutation(\"approve-dispatch-change\", {\n onSuccess: () => toast.success(\"Change approved\"),\n });\n const reject = useActionMutation(\"reject-dispatch-change\", {\n onSuccess: () => toast.success(\"Change rejected\"),\n });\n\n const inEmbed = isInAgentEmbed();\n\n const approval = approvals?.find((item) => item.id === id) ?? null;\n\n if (!id) {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-background p-6\">\n <div className=\"w-full max-w-md rounded-2xl border bg-card p-6 text-center\">\n <IconAlertCircle\n size={32}\n className=\"mx-auto mb-3 text-muted-foreground\"\n />\n <p className=\"text-sm font-medium text-foreground\">\n No approval id provided\n </p>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n Add <code className=\"rounded bg-muted px-1\">?id=&lt;id&gt;</code> to\n the URL.\n </p>\n </div>\n </div>\n );\n }\n\n if (isLoading) {\n return (\n <div className=\"flex min-h-screen items-start justify-center bg-background p-6\">\n <div className=\"w-full max-w-md space-y-4\">\n <div className=\"rounded-2xl border bg-card p-5\">\n <div className=\"flex items-start gap-3\">\n <Skeleton className=\"h-9 w-9 shrink-0 rounded-xl\" />\n <div className=\"min-w-0 flex-1 space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Skeleton className=\"h-4 w-40\" />\n <Skeleton className=\"h-5 w-20 rounded-full\" />\n </div>\n <Skeleton className=\"h-3 w-32\" />\n </div>\n </div>\n <div className=\"mt-4 space-y-2 rounded-xl border bg-muted/30 px-4 py-3\">\n <div className=\"flex justify-between gap-4\">\n <Skeleton className=\"h-3 w-20\" />\n <Skeleton className=\"h-3 w-24\" />\n </div>\n <div className=\"flex justify-between gap-4\">\n <Skeleton className=\"h-3 w-20\" />\n <Skeleton className=\"h-3 w-28\" />\n </div>\n <div className=\"flex justify-between gap-4\">\n <Skeleton className=\"h-3 w-16\" />\n <Skeleton className=\"h-3 w-32\" />\n </div>\n </div>\n <div className=\"mt-4 flex gap-2\">\n <Skeleton className=\"h-8 flex-1 rounded-md\" />\n <Skeleton className=\"h-8 flex-1 rounded-md\" />\n </div>\n </div>\n </div>\n </div>\n );\n }\n\n if (!approval) {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-background p-6\">\n <div className=\"w-full max-w-md rounded-2xl border bg-card p-6 text-center\">\n <IconAlertCircle\n size={32}\n className=\"mx-auto mb-3 text-muted-foreground\"\n />\n <p className=\"text-sm font-medium text-foreground\">\n Approval not found\n </p>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n The approval with id{\" \"}\n <code className=\"rounded bg-muted px-1\">{id}</code> does not exist.\n </p>\n {inEmbed && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"mt-4 gap-1.5\"\n onClick={() => postNavigate(appPath(\"/approvals\"))}\n >\n <IconArrowUpRight size={14} />\n View all approvals\n </Button>\n )}\n </div>\n </div>\n );\n }\n\n const isPending = approval.status === \"pending\";\n\n return (\n <div className=\"flex min-h-screen items-start justify-center bg-background p-6\">\n <div className=\"w-full max-w-md space-y-4\">\n <div className=\"rounded-2xl border bg-card p-5\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-xl border bg-muted text-foreground\">\n <IconShieldCheck size={17} />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">\n {approval.summary}\n </span>\n <StatusBadge status={approval.status} />\n </div>\n <div className=\"mt-1 text-xs text-muted-foreground\">\n Requested by{\" \"}\n <span className=\"font-medium text-foreground\">\n {approval.requestedBy}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"mt-4 space-y-2 rounded-xl border bg-muted/30 px-4 py-3 text-xs\">\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">Change type</span>\n <span className=\"font-mono font-medium text-foreground\">\n {approval.changeType}\n </span>\n </div>\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">Target type</span>\n <span className=\"font-mono font-medium text-foreground\">\n {approval.targetType}\n </span>\n </div>\n {approval.targetId && (\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">Target id</span>\n <span className=\"truncate font-mono font-medium text-foreground\">\n {approval.targetId}\n </span>\n </div>\n )}\n {approval.reviewedBy && (\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">Reviewed by</span>\n <span className=\"font-medium text-foreground\">\n {approval.reviewedBy}\n </span>\n </div>\n )}\n </div>\n\n {isPending && (\n <div className=\"mt-4 flex gap-2\">\n <Button\n size=\"sm\"\n className=\"flex-1\"\n disabled={approve.isPending || reject.isPending}\n onClick={() => approve.mutate({ id: approval.id })}\n >\n <IconCheck size={14} className=\"mr-1.5\" />\n Approve\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"flex-1\"\n disabled={approve.isPending || reject.isPending}\n onClick={() =>\n reject.mutate({\n id: approval.id,\n reason: \"Rejected in dispatch UI\",\n })\n }\n >\n <IconX size={14} className=\"mr-1.5\" />\n Reject\n </Button>\n </div>\n )}\n </div>\n\n {inEmbed && (\n <div className=\"flex justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n className=\"gap-1.5 text-muted-foreground\"\n onClick={() => postNavigate(appPath(\"/approvals\"))}\n >\n <IconArrowUpRight size={14} />\n Open in app\n </Button>\n </div>\n )}\n </div>\n </div>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"apps.$appId.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/apps.$appId.tsx"],"names":[],"mappings":"AAgBA,wBAAgB,IAAI;;IAEnB;AAED,MAAM,CAAC,OAAO,UAAU,iBAAiB,4CAoGxC"}
1
+ {"version":3,"file":"apps.$appId.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/apps.$appId.tsx"],"names":[],"mappings":"AAiBA,wBAAgB,IAAI;;IAEnB;AAED,MAAM,CAAC,OAAO,UAAU,iBAAiB,4CAwGxC"}
@@ -6,6 +6,7 @@ import { IconArrowLeft, IconArrowUpRight, IconClockHour4, } from "@tabler/icons-
6
6
  import { DispatchShell } from "../../components/dispatch-shell.js";
7
7
  import { Badge } from "../../components/ui/badge.js";
8
8
  import { Button } from "../../components/ui/button.js";
9
+ import { Skeleton } from "../../components/ui/skeleton.js";
9
10
  import { workspaceAppHref, } from "../../lib/workspace-apps.js";
10
11
  export function meta() {
11
12
  return [{ title: "Workspace app - Dispatch" }];
@@ -22,6 +23,6 @@ export default function WorkspaceAppRoute() {
22
23
  return;
23
24
  window.location.assign(href);
24
25
  }, [app, href]);
25
- return (_jsx(DispatchShell, { title: app?.name || "Workspace App", description: "Open a deployed app or check the status of an app being created.", children: _jsxs("div", { className: "max-w-2xl rounded-lg border bg-card p-5", children: [_jsx(Button, { asChild: true, size: "sm", variant: "ghost", className: "-ml-2 mb-4", children: _jsxs(Link, { to: "/apps", children: [_jsx(IconArrowLeft, { size: 15, className: "mr-1.5" }), "Apps"] }) }), isLoading && !app ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Loading app status..." })) : !app ? (_jsxs("div", { className: "space-y-3", children: [_jsx("h2", { className: "text-base font-semibold text-foreground", children: "App not found" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "This route is not in the workspace app list yet." })] })) : app.status === "pending" ? (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("h2", { className: "text-base font-semibold text-foreground", children: app.name }), _jsxs(Badge, { variant: "outline", className: "gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300", children: [_jsx(IconClockHour4, { size: 12 }), "Building"] })] }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["This app is being created. It will be available at", " ", _jsx("span", { className: "font-mono text-foreground", children: app.path }), " ", "after its branch is merged and the workspace deploy finishes."] }), app.branchName ? (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Branch: ", app.branchName] })) : null, app.builderUrl ? (_jsx(Button, { asChild: true, children: _jsxs("a", { href: app.builderUrl, target: "_blank", rel: "noreferrer", children: ["Open Builder branch", _jsx(IconArrowUpRight, { size: 15, className: "ml-1.5" })] }) })) : null] })) : (_jsxs("div", { className: "space-y-3", children: [_jsxs("h2", { className: "text-base font-semibold text-foreground", children: ["Opening ", app.name] }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Redirecting to", " ", _jsx("span", { className: "font-mono text-foreground", children: app.path }), "."] }), href ? (_jsx(Button, { asChild: true, children: _jsxs("a", { href: href, children: ["Open app", _jsx(IconArrowUpRight, { size: 15, className: "ml-1.5" })] }) })) : null] }))] }) }));
26
+ return (_jsx(DispatchShell, { title: app?.name || "Workspace App", description: "Open a deployed app or check the status of an app being created.", children: _jsxs("div", { className: "max-w-2xl rounded-lg border bg-card p-5", children: [_jsx(Button, { asChild: true, size: "sm", variant: "ghost", className: "-ml-2 mb-4", children: _jsxs(Link, { to: "/apps", children: [_jsx(IconArrowLeft, { size: 15, className: "mr-1.5" }), "Apps"] }) }), isLoading && !app ? (_jsxs("div", { className: "space-y-3", children: [_jsx(Skeleton, { className: "h-5 w-48" }), _jsx(Skeleton, { className: "h-4 w-full" }), _jsx(Skeleton, { className: "h-4 w-2/3" })] })) : !app ? (_jsxs("div", { className: "space-y-3", children: [_jsx("h2", { className: "text-base font-semibold text-foreground", children: "App not found" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "This route is not in the workspace app list yet." })] })) : app.status === "pending" ? (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("h2", { className: "text-base font-semibold text-foreground", children: app.name }), _jsxs(Badge, { variant: "outline", className: "gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300", children: [_jsx(IconClockHour4, { size: 12 }), "Building"] })] }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["This app is being created. It will be available at", " ", _jsx("span", { className: "font-mono text-foreground", children: app.path }), " ", "after its branch is merged and the workspace deploy finishes."] }), app.branchName ? (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Branch: ", app.branchName] })) : null, app.builderUrl ? (_jsx(Button, { asChild: true, children: _jsxs("a", { href: app.builderUrl, target: "_blank", rel: "noreferrer", children: ["Open Builder branch", _jsx(IconArrowUpRight, { size: 15, className: "ml-1.5" })] }) })) : null] })) : (_jsxs("div", { className: "space-y-3", children: [_jsxs("h2", { className: "text-base font-semibold text-foreground", children: ["Opening ", app.name] }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Redirecting to", " ", _jsx("span", { className: "font-mono text-foreground", children: app.path }), "."] }), href ? (_jsx(Button, { asChild: true, children: _jsxs("a", { href: href, children: ["Open app", _jsx(IconArrowUpRight, { size: 15, className: "ml-1.5" })] }) })) : null] }))] }) }));
26
27
  }
27
28
  //# sourceMappingURL=apps.$appId.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"apps.$appId.js","sourceRoot":"","sources":["../../../src/routes/pages/apps.$appId.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo } from \"react\";\nimport { Link, useParams } from \"react-router\";\nimport { useActionQuery } from \"@agent-native/core/client\";\nimport {\n IconArrowLeft,\n IconArrowUpRight,\n IconClockHour4,\n} from \"@tabler/icons-react\";\nimport { DispatchShell } from \"@/components/dispatch-shell\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n workspaceAppHref,\n type WorkspaceAppSummary,\n} from \"@/lib/workspace-apps\";\n\nexport function meta() {\n return [{ title: \"Workspace app - Dispatch\" }];\n}\n\nexport default function WorkspaceAppRoute() {\n const { appId } = useParams();\n const { data: apps = [], isLoading } = useActionQuery(\n \"list-workspace-apps\",\n { includeAgentCards: false },\n {\n refetchInterval: 2_000,\n },\n );\n const app = useMemo(\n () =>\n (apps as WorkspaceAppSummary[]).find((item) => item.id === appId) ?? null,\n [appId, apps],\n );\n const href = app ? workspaceAppHref(app) : null;\n\n useEffect(() => {\n if (!app || app.status === \"pending\" || !href) return;\n window.location.assign(href);\n }, [app, href]);\n\n return (\n <DispatchShell\n title={app?.name || \"Workspace App\"}\n description=\"Open a deployed app or check the status of an app being created.\"\n >\n <div className=\"max-w-2xl rounded-lg border bg-card p-5\">\n <Button asChild size=\"sm\" variant=\"ghost\" className=\"-ml-2 mb-4\">\n <Link to=\"/apps\">\n <IconArrowLeft size={15} className=\"mr-1.5\" />\n Apps\n </Link>\n </Button>\n\n {isLoading && !app ? (\n <p className=\"text-sm text-muted-foreground\">Loading app status...</p>\n ) : !app ? (\n <div className=\"space-y-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n App not found\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n This route is not in the workspace app list yet.\n </p>\n </div>\n ) : app.status === \"pending\" ? (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {app.name}\n </h2>\n <Badge\n variant=\"outline\"\n className=\"gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\"\n >\n <IconClockHour4 size={12} />\n Building\n </Badge>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n This app is being created. It will be available at{\" \"}\n <span className=\"font-mono text-foreground\">{app.path}</span>{\" \"}\n after its branch is merged and the workspace deploy finishes.\n </p>\n {app.branchName ? (\n <p className=\"text-xs text-muted-foreground\">\n Branch: {app.branchName}\n </p>\n ) : null}\n {app.builderUrl ? (\n <Button asChild>\n <a href={app.builderUrl} target=\"_blank\" rel=\"noreferrer\">\n Open Builder branch\n <IconArrowUpRight size={15} className=\"ml-1.5\" />\n </a>\n </Button>\n ) : null}\n </div>\n ) : (\n <div className=\"space-y-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n Opening {app.name}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n Redirecting to{\" \"}\n <span className=\"font-mono text-foreground\">{app.path}</span>.\n </p>\n {href ? (\n <Button asChild>\n <a href={href}>\n Open app\n <IconArrowUpRight size={15} className=\"ml-1.5\" />\n </a>\n </Button>\n ) : null}\n </div>\n )}\n </div>\n </DispatchShell>\n );\n}\n"]}
1
+ {"version":3,"file":"apps.$appId.js","sourceRoot":"","sources":["../../../src/routes/pages/apps.$appId.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo } from \"react\";\nimport { Link, useParams } from \"react-router\";\nimport { useActionQuery } from \"@agent-native/core/client\";\nimport {\n IconArrowLeft,\n IconArrowUpRight,\n IconClockHour4,\n} from \"@tabler/icons-react\";\nimport { DispatchShell } from \"@/components/dispatch-shell\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n workspaceAppHref,\n type WorkspaceAppSummary,\n} from \"@/lib/workspace-apps\";\n\nexport function meta() {\n return [{ title: \"Workspace app - Dispatch\" }];\n}\n\nexport default function WorkspaceAppRoute() {\n const { appId } = useParams();\n const { data: apps = [], isLoading } = useActionQuery(\n \"list-workspace-apps\",\n { includeAgentCards: false },\n {\n refetchInterval: 2_000,\n },\n );\n const app = useMemo(\n () =>\n (apps as WorkspaceAppSummary[]).find((item) => item.id === appId) ?? null,\n [appId, apps],\n );\n const href = app ? workspaceAppHref(app) : null;\n\n useEffect(() => {\n if (!app || app.status === \"pending\" || !href) return;\n window.location.assign(href);\n }, [app, href]);\n\n return (\n <DispatchShell\n title={app?.name || \"Workspace App\"}\n description=\"Open a deployed app or check the status of an app being created.\"\n >\n <div className=\"max-w-2xl rounded-lg border bg-card p-5\">\n <Button asChild size=\"sm\" variant=\"ghost\" className=\"-ml-2 mb-4\">\n <Link to=\"/apps\">\n <IconArrowLeft size={15} className=\"mr-1.5\" />\n Apps\n </Link>\n </Button>\n\n {isLoading && !app ? (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-5 w-48\" />\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-2/3\" />\n </div>\n ) : !app ? (\n <div className=\"space-y-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n App not found\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n This route is not in the workspace app list yet.\n </p>\n </div>\n ) : app.status === \"pending\" ? (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {app.name}\n </h2>\n <Badge\n variant=\"outline\"\n className=\"gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\"\n >\n <IconClockHour4 size={12} />\n Building\n </Badge>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n This app is being created. It will be available at{\" \"}\n <span className=\"font-mono text-foreground\">{app.path}</span>{\" \"}\n after its branch is merged and the workspace deploy finishes.\n </p>\n {app.branchName ? (\n <p className=\"text-xs text-muted-foreground\">\n Branch: {app.branchName}\n </p>\n ) : null}\n {app.builderUrl ? (\n <Button asChild>\n <a href={app.builderUrl} target=\"_blank\" rel=\"noreferrer\">\n Open Builder branch\n <IconArrowUpRight size={15} className=\"ml-1.5\" />\n </a>\n </Button>\n ) : null}\n </div>\n ) : (\n <div className=\"space-y-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n Opening {app.name}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n Redirecting to{\" \"}\n <span className=\"font-mono text-foreground\">{app.path}</span>.\n </p>\n {href ? (\n <Button asChild>\n <a href={href}>\n Open app\n <IconArrowUpRight size={15} className=\"ml-1.5\" />\n </a>\n </Button>\n ) : null}\n </div>\n )}\n </div>\n </DispatchShell>\n );\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  export declare function meta(): {
2
2
  title: string;
3
3
  }[];
4
- export default function IntegrationsRoute(): import("react/jsx-runtime").JSX.Element;
4
+ export default function ConnectionsRoute(): import("react/jsx-runtime").JSX.Element;
5
5
  //# sourceMappingURL=integrations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/integrations.tsx"],"names":[],"mappings":"AAgBA,wBAAgB,IAAI;;IAEnB;AA2JD,MAAM,CAAC,OAAO,UAAU,iBAAiB,4CAwGxC"}
1
+ {"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/integrations.tsx"],"names":[],"mappings":"AA+BA,wBAAgB,IAAI;;IAEnB;AAqQD,MAAM,CAAC,OAAO,UAAU,gBAAgB,4CAoHvC"}
@@ -1,46 +1,147 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
2
3
  import { useActionMutation, useActionQuery } from "@agent-native/core/client";
4
+ import { useQueryClient } from "@tanstack/react-query";
3
5
  import { toast } from "sonner";
4
- import { IconCheck, IconCircleDashed, IconKey, IconRefresh, IconShieldCheck, IconWifi, IconWifiOff, } from "@tabler/icons-react";
6
+ import { IconCheck, IconChevronRight, IconCircleDashed, IconKey, IconLink, IconPlugConnected, } from "@tabler/icons-react";
5
7
  import { DispatchShell } from "../../components/dispatch-shell.js";
6
8
  import { Badge } from "../../components/ui/badge.js";
7
9
  import { Button } from "../../components/ui/button.js";
8
- import { Progress } from "../../components/ui/progress.js";
10
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "../../components/ui/collapsible.js";
11
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../../components/ui/dialog.js";
12
+ import { Input } from "../../components/ui/input.js";
13
+ import { Label } from "../../components/ui/label.js";
9
14
  export function meta() {
10
- return [{ title: "Integrations — Dispatch" }];
15
+ return [{ title: "Connections — Dispatch" }];
11
16
  }
12
- function StatusBadge({ configured, vaultGranted, }) {
13
- if (configured && vaultGranted) {
14
- return (_jsxs(Badge, { variant: "secondary", className: "bg-green-500/10 text-green-700 dark:text-green-400", children: [_jsx(IconShieldCheck, { size: 12, className: "mr-1" }), "Vault"] }));
17
+ function inferProviderFromKey(key, label) {
18
+ const haystack = `${key} ${label}`.toLowerCase();
19
+ for (const provider of [
20
+ "google",
21
+ "slack",
22
+ "sendgrid",
23
+ "github",
24
+ "stripe",
25
+ "hubspot",
26
+ "jira",
27
+ "bigquery",
28
+ "anthropic",
29
+ "openai",
30
+ ]) {
31
+ if (haystack.includes(provider))
32
+ return provider;
15
33
  }
16
- if (configured) {
17
- return (_jsxs(Badge, { variant: "secondary", className: "bg-green-500/10 text-green-700 dark:text-green-400", children: [_jsx(IconCheck, { size: 12, className: "mr-1" }), "Configured"] }));
34
+ return "other";
35
+ }
36
+ function ConnectDialog({ service, open, onOpenChange, }) {
37
+ const [value, setValue] = useState("");
38
+ const qc = useQueryClient();
39
+ const createSecret = useActionMutation("create-vault-secret", {});
40
+ const createGrant = useActionMutation("create-vault-grant", {});
41
+ const syncToApp = useActionMutation("sync-vault-to-app", {});
42
+ function reset() {
43
+ setValue("");
18
44
  }
19
- if (vaultGranted) {
20
- return (_jsxs(Badge, { variant: "secondary", className: "bg-blue-500/10 text-blue-700 dark:text-blue-400", children: [_jsx(IconKey, { size: 12, className: "mr-1" }), "Granted (not synced)"] }));
45
+ async function handleSave() {
46
+ const trimmed = value.trim();
47
+ if (!trimmed) {
48
+ toast.error("Enter a value to save");
49
+ return;
50
+ }
51
+ try {
52
+ // 1. Create the secret (or get the existing one — server treats key as
53
+ // the unique identifier). The server returns { secret: { id, ... } }.
54
+ const created = await createSecret.mutateAsync({
55
+ credentialKey: service.key,
56
+ name: service.label,
57
+ value: trimmed,
58
+ provider: inferProviderFromKey(service.key, service.label),
59
+ });
60
+ const secretId = created?.secret?.id ??
61
+ created?.id;
62
+ if (!secretId) {
63
+ throw new Error("Secret created but id missing");
64
+ }
65
+ // 2. Grant + sync to every app that declared this credential.
66
+ const targets = service.apps.filter((a) => !a.vaultGranted);
67
+ for (const app of targets) {
68
+ try {
69
+ await createGrant.mutateAsync({
70
+ secretId,
71
+ appId: app.appId,
72
+ });
73
+ }
74
+ catch (err) {
75
+ console.warn(`grant to ${app.appId} failed`, err);
76
+ }
77
+ }
78
+ for (const app of service.apps) {
79
+ try {
80
+ await syncToApp.mutateAsync({ appId: app.appId });
81
+ }
82
+ catch (err) {
83
+ console.warn(`sync to ${app.appId} failed`, err);
84
+ }
85
+ }
86
+ qc.invalidateQueries({
87
+ queryKey: ["action", "list-integrations-catalog"],
88
+ });
89
+ toast.success(`Connected ${service.label}`);
90
+ onOpenChange(false);
91
+ reset();
92
+ }
93
+ catch (err) {
94
+ toast.error(err?.message ?? "Failed to save credential");
95
+ }
21
96
  }
22
- return (_jsxs(Badge, { variant: "secondary", className: "bg-amber-500/10 text-amber-700 dark:text-amber-400", children: [_jsx(IconCircleDashed, { size: 12, className: "mr-1" }), "Missing"] }));
97
+ const pending = createSecret.isPending || createGrant.isPending || syncToApp.isPending;
98
+ return (_jsx(Dialog, { open: open, onOpenChange: (next) => {
99
+ if (!next)
100
+ reset();
101
+ onOpenChange(next);
102
+ }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Connect ", service.label] }), _jsxs(DialogDescription, { children: ["Used by", " ", service.apps.length === 1
103
+ ? service.apps[0].appName
104
+ : `${service.apps.length} apps`, ". Saved to the workspace vault and synced to every app that needs it."] })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Key" }), _jsx("div", { className: "font-mono text-sm", children: service.key })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "connector-value", children: "Value" }), _jsx(Input, { id: "connector-value", type: "password", autoComplete: "off", value: value, onChange: (e) => setValue(e.target.value), placeholder: `Paste your ${service.label} key…`, autoFocus: true })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: pending, children: "Cancel" }), _jsx(Button, { onClick: handleSave, disabled: pending || !value.trim(), children: pending ? "Saving…" : "Connect" })] })] }) }));
105
+ }
106
+ function ConnectorCard({ service }) {
107
+ const [open, setOpen] = useState(false);
108
+ const isConnected = service.apps.some((a) => a.configured);
109
+ const appCount = service.apps.length;
110
+ return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => setOpen(true), className: "group flex flex-col items-start gap-2 rounded-2xl border bg-card p-5 text-left transition hover:border-foreground/20 hover:bg-card/80 cursor-pointer", children: [_jsxs("div", { className: "flex w-full items-start justify-between gap-2", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-xl bg-muted", children: _jsx(IconKey, { size: 16, className: "text-muted-foreground" }) }), isConnected ? (_jsxs(Badge, { variant: "secondary", className: "bg-green-500/10 text-green-700 dark:text-green-400 gap-1", children: [_jsx(IconCheck, { size: 12 }), "Connected"] })) : (_jsxs(Badge, { variant: "secondary", className: "bg-amber-500/10 text-amber-700 dark:text-amber-400 gap-1", children: [_jsx(IconCircleDashed, { size: 12 }), "Connect"] }))] }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-sm font-semibold text-foreground truncate", children: service.label }), _jsx("div", { className: "font-mono text-xs text-muted-foreground/80 truncate", children: service.key })] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["Used by ", appCount, " ", appCount === 1 ? "app" : "apps"] })] }), _jsx(ConnectDialog, { service: service, open: open, onOpenChange: setOpen })] }));
23
111
  }
24
- function AppCard({ app }) {
25
- const syncToApp = useActionMutation("sync-vault-to-app", {
26
- onSuccess: (data) => toast.success(`Synced ${data.synced} key(s) to ${data.appId}`),
27
- onError: (err) => toast.error(String(err)),
28
- });
29
- const integrations = app.integrations || [];
30
- const configuredCount = integrations.filter((i) => i.configured).length;
31
- const total = integrations.length;
32
- const coverage = total > 0 ? Math.round((configuredCount / total) * 100) : 0;
33
- return (_jsxs("div", { className: "rounded-2xl border bg-card", children: [_jsxs("div", { className: "flex items-center justify-between border-b px-5 py-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-xl text-white text-xs font-bold", style: { backgroundColor: app.color }, children: app.appName.charAt(0).toUpperCase() }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "text-sm font-semibold text-foreground", children: app.appName }), app.reachable ? (_jsx(IconWifi, { size: 14, className: "text-green-500" })) : (_jsx(IconWifiOff, { size: 14, className: "text-muted-foreground/50" }))] }), _jsx("div", { className: "text-xs text-muted-foreground", children: app.appId })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => syncToApp.mutate({ appId: app.appId }), disabled: syncToApp.isPending || !app.reachable, children: [_jsx(IconRefresh, { size: 14, className: syncToApp.isPending ? "animate-spin" : "" }), _jsx("span", { className: "ml-1.5", children: "Sync" })] })] }), _jsx("div", { className: "px-5 py-4", children: total > 0 ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [_jsxs("span", { children: [configuredCount, "/", total, " configured"] }), _jsxs("span", { children: [coverage, "%"] })] }), _jsx(Progress, { value: coverage, className: "mt-2 h-1.5" }), _jsx("div", { className: "mt-4 space-y-2", children: integrations.map((integration) => (_jsxs("div", { className: "flex items-center justify-between rounded-lg border px-3 py-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm text-foreground", children: integration.label }), integration.required && (_jsx("span", { className: "text-xs text-red-500", children: "required" }))] }), _jsx("div", { className: "font-mono text-xs text-muted-foreground", children: integration.key })] }), _jsx(StatusBadge, { configured: integration.configured, vaultGranted: integration.vaultGranted })] }, integration.key))) })] })) : app.reachable ? (_jsx("div", { className: "py-4 text-center text-sm text-muted-foreground", children: "No declared integrations." })) : (_jsx("div", { className: "py-4 text-center text-sm text-muted-foreground", children: "App is not reachable. Start the app to see its integrations." })) })] }));
112
+ function PerAppDetailRow({ app }) {
113
+ const total = (app.integrations ?? []).length;
114
+ const ok = (app.integrations ?? []).filter((i) => i.configured).length;
115
+ return (_jsxs("div", { className: "flex items-center justify-between border-t px-4 py-2.5 first:border-t-0", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("div", { className: "h-5 w-5 rounded text-[10px] font-bold text-white flex items-center justify-center shrink-0", style: { backgroundColor: app.color }, children: app.appName.charAt(0).toUpperCase() }), _jsx("span", { className: "text-sm truncate", children: app.appName }), !app.reachable && (_jsx("span", { className: "text-xs text-muted-foreground", children: "offline" }))] }), _jsx("span", { className: "text-xs text-muted-foreground", children: total === 0 ? "no integrations" : `${ok}/${total}` })] }));
34
116
  }
35
- export default function IntegrationsRoute() {
117
+ export default function ConnectionsRoute() {
36
118
  const { data: catalog, isLoading } = useActionQuery("list-integrations-catalog", {});
37
119
  const apps = catalog || [];
38
- const reachableApps = apps.filter((a) => a.reachable);
39
- const unreachableApps = apps.filter((a) => !a.reachable);
40
- const totalIntegrations = apps.reduce((sum, a) => sum + (a.integrations?.length || 0), 0);
41
- const configuredIntegrations = apps.reduce((sum, a) => sum + (a.integrations?.filter((i) => i.configured)?.length || 0), 0);
42
- return (_jsxs(DispatchShell, { title: "Integrations", description: "See what credentials each app needs and their configuration status across the workspace.", children: [!isLoading && apps.length > 0 && (_jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [_jsxs("div", { className: "rounded-2xl border bg-card p-5", children: [_jsx("div", { className: "text-sm font-medium text-muted-foreground", children: "Apps discovered" }), _jsx("div", { className: "mt-2 text-3xl font-semibold text-foreground", children: apps.length }), _jsxs("div", { className: "mt-1 text-xs text-muted-foreground", children: [reachableApps.length, " reachable"] })] }), _jsxs("div", { className: "rounded-2xl border bg-card p-5", children: [_jsx("div", { className: "text-sm font-medium text-muted-foreground", children: "Total integrations" }), _jsx("div", { className: "mt-2 text-3xl font-semibold text-foreground", children: totalIntegrations }), _jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: "across all apps" })] }), _jsxs("div", { className: "rounded-2xl border bg-card p-5", children: [_jsx("div", { className: "text-sm font-medium text-muted-foreground", children: "Configured" }), _jsxs("div", { className: "mt-2 text-3xl font-semibold text-foreground", children: [configuredIntegrations, "/", totalIntegrations] }), _jsx(Progress, { value: totalIntegrations > 0
43
- ? Math.round((configuredIntegrations / totalIntegrations) * 100)
44
- : 0, className: "mt-2 h-1.5" })] })] })), isLoading && (_jsx("div", { className: "rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground", children: "Discovering apps and fetching integration status..." })), reachableApps.length > 0 && (_jsx("div", { className: "grid gap-4 xl:grid-cols-2", children: reachableApps.map((app) => (_jsx(AppCard, { app: app }, app.appId))) })), unreachableApps.length > 0 && (_jsxs("div", { children: [_jsx("h2", { className: "mb-3 text-sm font-medium text-muted-foreground", children: "Offline apps" }), _jsx("div", { className: "grid gap-4 xl:grid-cols-2", children: unreachableApps.map((app) => (_jsx(AppCard, { app: app }, app.appId))) })] })), !isLoading && apps.length === 0 && (_jsx("div", { className: "rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground", children: "No workspace apps found." }))] }));
120
+ const services = useMemo(() => {
121
+ const map = new Map();
122
+ for (const app of apps) {
123
+ for (const intg of app.integrations ?? []) {
124
+ if (!map.has(intg.key)) {
125
+ map.set(intg.key, {
126
+ key: intg.key,
127
+ label: intg.label,
128
+ apps: [],
129
+ });
130
+ }
131
+ map.get(intg.key).apps.push({
132
+ appId: app.appId,
133
+ appName: app.appName,
134
+ color: app.color,
135
+ configured: intg.configured,
136
+ vaultGranted: intg.vaultGranted,
137
+ vaultSecretId: intg.vaultSecretId,
138
+ });
139
+ }
140
+ }
141
+ return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label));
142
+ }, [apps]);
143
+ const available = services.filter((s) => !s.apps.some((a) => a.configured));
144
+ const connected = services.filter((s) => s.apps.some((a) => a.configured));
145
+ return (_jsxs(DispatchShell, { title: "Connections", description: "Connect services once. Apps that need them pick up the key automatically.", children: [isLoading && services.length === 0 && (_jsx("div", { className: "rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground", children: "Discovering apps and credentials\u2026" })), !isLoading && services.length === 0 && (_jsx("div", { className: "rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground", children: "No apps with declared integrations are reachable yet." })), available.length > 0 && (_jsxs("section", { children: [_jsxs("div", { className: "mb-3 flex items-baseline justify-between", children: [_jsx("h2", { className: "text-sm font-medium text-foreground", children: "Available to connect" }), _jsx("span", { className: "text-xs text-muted-foreground", children: available.length })] }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-3", children: available.map((service) => (_jsx(ConnectorCard, { service: service }, service.key))) })] })), connected.length > 0 && (_jsxs("section", { children: [_jsxs("div", { className: "mb-3 mt-2 flex items-baseline justify-between", children: [_jsx("h2", { className: "text-sm font-medium text-foreground", children: "Connected" }), _jsx("span", { className: "text-xs text-muted-foreground", children: connected.length })] }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-3", children: connected.map((service) => (_jsx(ConnectorCard, { service: service }, service.key))) })] })), apps.length > 0 && (_jsxs(Collapsible, { className: "mt-6 rounded-2xl border bg-card", children: [_jsxs(CollapsibleTrigger, { className: "flex w-full items-center justify-between px-4 py-3 text-sm", children: [_jsxs("span", { className: "flex items-center gap-2 text-muted-foreground", children: [_jsx(IconPlugConnected, { size: 14 }), "Per-app status"] }), _jsx(IconChevronRight, { size: 14, className: "text-muted-foreground transition group-data-[state=open]:rotate-90" })] }), _jsxs(CollapsibleContent, { children: [_jsx("div", { className: "border-t", children: apps.map((app) => (_jsx(PerAppDetailRow, { app: app }, app.appId))) }), _jsxs("div", { className: "flex items-center justify-end gap-1.5 border-t px-4 py-2.5 text-xs text-muted-foreground", children: [_jsx(IconLink, { size: 12 }), _jsx("a", { href: "/vault", className: "hover:underline", children: "Open vault for advanced sharing" })] })] })] }))] }));
45
146
  }
46
147
  //# sourceMappingURL=integrations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../../src/routes/pages/integrations.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useActionMutation, useActionQuery } from \"@agent-native/core/client\";\nimport { toast } from \"sonner\";\nimport {\n IconCheck,\n IconCircleDashed,\n IconKey,\n IconRefresh,\n IconShieldCheck,\n IconWifi,\n IconWifiOff,\n} from \"@tabler/icons-react\";\nimport { DispatchShell } from \"@/components/dispatch-shell\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Progress } from \"@/components/ui/progress\";\n\nexport function meta() {\n return [{ title: \"Integrations — Dispatch\" }];\n}\n\nfunction StatusBadge({\n configured,\n vaultGranted,\n}: {\n configured: boolean;\n vaultGranted: boolean;\n}) {\n if (configured && vaultGranted) {\n return (\n <Badge\n variant=\"secondary\"\n className=\"bg-green-500/10 text-green-700 dark:text-green-400\"\n >\n <IconShieldCheck size={12} className=\"mr-1\" />\n Vault\n </Badge>\n );\n }\n if (configured) {\n return (\n <Badge\n variant=\"secondary\"\n className=\"bg-green-500/10 text-green-700 dark:text-green-400\"\n >\n <IconCheck size={12} className=\"mr-1\" />\n Configured\n </Badge>\n );\n }\n if (vaultGranted) {\n return (\n <Badge\n variant=\"secondary\"\n className=\"bg-blue-500/10 text-blue-700 dark:text-blue-400\"\n >\n <IconKey size={12} className=\"mr-1\" />\n Granted (not synced)\n </Badge>\n );\n }\n return (\n <Badge\n variant=\"secondary\"\n className=\"bg-amber-500/10 text-amber-700 dark:text-amber-400\"\n >\n <IconCircleDashed size={12} className=\"mr-1\" />\n Missing\n </Badge>\n );\n}\n\nfunction AppCard({ app }: { app: any }) {\n const syncToApp = useActionMutation(\"sync-vault-to-app\", {\n onSuccess: (data: any) =>\n toast.success(`Synced ${data.synced} key(s) to ${data.appId}`),\n onError: (err) => toast.error(String(err)),\n });\n\n const integrations = app.integrations || [];\n const configuredCount = integrations.filter((i: any) => i.configured).length;\n const total = integrations.length;\n const coverage = total > 0 ? Math.round((configuredCount / total) * 100) : 0;\n\n return (\n <div className=\"rounded-2xl border bg-card\">\n <div className=\"flex items-center justify-between border-b px-5 py-4\">\n <div className=\"flex items-center gap-3\">\n <div\n className=\"flex h-9 w-9 items-center justify-center rounded-xl text-white text-xs font-bold\"\n style={{ backgroundColor: app.color }}\n >\n {app.appName.charAt(0).toUpperCase()}\n </div>\n <div>\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-sm font-semibold text-foreground\">\n {app.appName}\n </h3>\n {app.reachable ? (\n <IconWifi size={14} className=\"text-green-500\" />\n ) : (\n <IconWifiOff size={14} className=\"text-muted-foreground/50\" />\n )}\n </div>\n <div className=\"text-xs text-muted-foreground\">{app.appId}</div>\n </div>\n </div>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => syncToApp.mutate({ appId: app.appId })}\n disabled={syncToApp.isPending || !app.reachable}\n >\n <IconRefresh\n size={14}\n className={syncToApp.isPending ? \"animate-spin\" : \"\"}\n />\n <span className=\"ml-1.5\">Sync</span>\n </Button>\n </div>\n\n <div className=\"px-5 py-4\">\n {total > 0 ? (\n <>\n <div className=\"flex items-center justify-between text-xs text-muted-foreground\">\n <span>\n {configuredCount}/{total} configured\n </span>\n <span>{coverage}%</span>\n </div>\n <Progress value={coverage} className=\"mt-2 h-1.5\" />\n\n <div className=\"mt-4 space-y-2\">\n {integrations.map((integration: any) => (\n <div\n key={integration.key}\n className=\"flex items-center justify-between rounded-lg border px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-foreground\">\n {integration.label}\n </span>\n {integration.required && (\n <span className=\"text-xs text-red-500\">required</span>\n )}\n </div>\n <div className=\"font-mono text-xs text-muted-foreground\">\n {integration.key}\n </div>\n </div>\n <StatusBadge\n configured={integration.configured}\n vaultGranted={integration.vaultGranted}\n />\n </div>\n ))}\n </div>\n </>\n ) : app.reachable ? (\n <div className=\"py-4 text-center text-sm text-muted-foreground\">\n No declared integrations.\n </div>\n ) : (\n <div className=\"py-4 text-center text-sm text-muted-foreground\">\n App is not reachable. Start the app to see its integrations.\n </div>\n )}\n </div>\n </div>\n );\n}\n\nexport default function IntegrationsRoute() {\n const { data: catalog, isLoading } = useActionQuery(\n \"list-integrations-catalog\",\n {},\n );\n\n const apps = catalog || [];\n const reachableApps = apps.filter((a: any) => a.reachable);\n const unreachableApps = apps.filter((a: any) => !a.reachable);\n\n const totalIntegrations = apps.reduce(\n (sum: number, a: any) => sum + (a.integrations?.length || 0),\n 0,\n );\n const configuredIntegrations = apps.reduce(\n (sum: number, a: any) =>\n sum + (a.integrations?.filter((i: any) => i.configured)?.length || 0),\n 0,\n );\n\n return (\n <DispatchShell\n title=\"Integrations\"\n description=\"See what credentials each app needs and their configuration status across the workspace.\"\n >\n {!isLoading && apps.length > 0 && (\n <div className=\"grid gap-4 md:grid-cols-3\">\n <div className=\"rounded-2xl border bg-card p-5\">\n <div className=\"text-sm font-medium text-muted-foreground\">\n Apps discovered\n </div>\n <div className=\"mt-2 text-3xl font-semibold text-foreground\">\n {apps.length}\n </div>\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {reachableApps.length} reachable\n </div>\n </div>\n <div className=\"rounded-2xl border bg-card p-5\">\n <div className=\"text-sm font-medium text-muted-foreground\">\n Total integrations\n </div>\n <div className=\"mt-2 text-3xl font-semibold text-foreground\">\n {totalIntegrations}\n </div>\n <div className=\"mt-1 text-xs text-muted-foreground\">\n across all apps\n </div>\n </div>\n <div className=\"rounded-2xl border bg-card p-5\">\n <div className=\"text-sm font-medium text-muted-foreground\">\n Configured\n </div>\n <div className=\"mt-2 text-3xl font-semibold text-foreground\">\n {configuredIntegrations}/{totalIntegrations}\n </div>\n <Progress\n value={\n totalIntegrations > 0\n ? Math.round(\n (configuredIntegrations / totalIntegrations) * 100,\n )\n : 0\n }\n className=\"mt-2 h-1.5\"\n />\n </div>\n </div>\n )}\n\n {isLoading && (\n <div className=\"rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground\">\n Discovering apps and fetching integration status...\n </div>\n )}\n\n {reachableApps.length > 0 && (\n <div className=\"grid gap-4 xl:grid-cols-2\">\n {reachableApps.map((app: any) => (\n <AppCard key={app.appId} app={app} />\n ))}\n </div>\n )}\n\n {unreachableApps.length > 0 && (\n <div>\n <h2 className=\"mb-3 text-sm font-medium text-muted-foreground\">\n Offline apps\n </h2>\n <div className=\"grid gap-4 xl:grid-cols-2\">\n {unreachableApps.map((app: any) => (\n <AppCard key={app.appId} app={app} />\n ))}\n </div>\n </div>\n )}\n\n {!isLoading && apps.length === 0 && (\n <div className=\"rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground\">\n No workspace apps found.\n </div>\n )}\n </DispatchShell>\n );\n}\n"]}
1
+ {"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../../src/routes/pages/integrations.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from \"react\";\nimport { useActionMutation, useActionQuery } from \"@agent-native/core/client\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { toast } from \"sonner\";\nimport {\n IconCheck,\n IconChevronRight,\n IconCircleDashed,\n IconKey,\n IconLink,\n IconPlugConnected,\n} from \"@tabler/icons-react\";\nimport { DispatchShell } from \"@/components/dispatch-shell\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\n\nexport function meta() {\n return [{ title: \"Connections — Dispatch\" }];\n}\n\ninterface AppRef {\n appId: string;\n appName: string;\n color: string;\n configured: boolean;\n vaultGranted: boolean;\n vaultSecretId?: string;\n}\n\ninterface Service {\n /** Credential key shared across apps (e.g. `OPENAI_API_KEY`). */\n key: string;\n /** Human label from the first app that declares it (`\"OpenAI\"`, `\"Stripe\"`). */\n label: string;\n /** Apps in the workspace that declare this credential. */\n apps: AppRef[];\n}\n\ninterface CatalogApp {\n appId: string;\n appName: string;\n color: string;\n url: string;\n reachable: boolean;\n integrations?: Array<{\n key: string;\n label: string;\n required: boolean;\n configured: boolean;\n vaultGranted: boolean;\n vaultSecretId?: string;\n }>;\n}\n\nfunction inferProviderFromKey(key: string, label: string): string {\n const haystack = `${key} ${label}`.toLowerCase();\n for (const provider of [\n \"google\",\n \"slack\",\n \"sendgrid\",\n \"github\",\n \"stripe\",\n \"hubspot\",\n \"jira\",\n \"bigquery\",\n \"anthropic\",\n \"openai\",\n ]) {\n if (haystack.includes(provider)) return provider;\n }\n return \"other\";\n}\n\nfunction ConnectDialog({\n service,\n open,\n onOpenChange,\n}: {\n service: Service;\n open: boolean;\n onOpenChange: (next: boolean) => void;\n}) {\n const [value, setValue] = useState(\"\");\n const qc = useQueryClient();\n\n const createSecret = useActionMutation(\"create-vault-secret\", {});\n const createGrant = useActionMutation(\"create-vault-grant\", {});\n const syncToApp = useActionMutation(\"sync-vault-to-app\", {});\n\n function reset() {\n setValue(\"\");\n }\n\n async function handleSave() {\n const trimmed = value.trim();\n if (!trimmed) {\n toast.error(\"Enter a value to save\");\n return;\n }\n try {\n // 1. Create the secret (or get the existing one — server treats key as\n // the unique identifier). The server returns { secret: { id, ... } }.\n const created = await createSecret.mutateAsync({\n credentialKey: service.key,\n name: service.label,\n value: trimmed,\n provider: inferProviderFromKey(service.key, service.label),\n });\n const secretId =\n (created as { secret?: { id?: string } })?.secret?.id ??\n (created as { id?: string })?.id;\n if (!secretId) {\n throw new Error(\"Secret created but id missing\");\n }\n\n // 2. Grant + sync to every app that declared this credential.\n const targets = service.apps.filter((a) => !a.vaultGranted);\n for (const app of targets) {\n try {\n await createGrant.mutateAsync({\n secretId,\n appId: app.appId,\n });\n } catch (err) {\n console.warn(`grant to ${app.appId} failed`, err);\n }\n }\n for (const app of service.apps) {\n try {\n await syncToApp.mutateAsync({ appId: app.appId });\n } catch (err) {\n console.warn(`sync to ${app.appId} failed`, err);\n }\n }\n\n qc.invalidateQueries({\n queryKey: [\"action\", \"list-integrations-catalog\"],\n });\n toast.success(`Connected ${service.label}`);\n onOpenChange(false);\n reset();\n } catch (err: any) {\n toast.error(err?.message ?? \"Failed to save credential\");\n }\n }\n\n const pending =\n createSecret.isPending || createGrant.isPending || syncToApp.isPending;\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) reset();\n onOpenChange(next);\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Connect {service.label}</DialogTitle>\n <DialogDescription>\n Used by{\" \"}\n {service.apps.length === 1\n ? service.apps[0].appName\n : `${service.apps.length} apps`}\n . Saved to the workspace vault and synced to every app that needs\n it.\n </DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <div>\n <Label className=\"text-xs text-muted-foreground\">Key</Label>\n <div className=\"font-mono text-sm\">{service.key}</div>\n </div>\n <div>\n <Label htmlFor=\"connector-value\">Value</Label>\n <Input\n id=\"connector-value\"\n type=\"password\"\n autoComplete=\"off\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n placeholder={`Paste your ${service.label} key…`}\n autoFocus\n />\n </div>\n </div>\n <DialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={pending}\n >\n Cancel\n </Button>\n <Button onClick={handleSave} disabled={pending || !value.trim()}>\n {pending ? \"Saving…\" : \"Connect\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction ConnectorCard({ service }: { service: Service }) {\n const [open, setOpen] = useState(false);\n const isConnected = service.apps.some((a) => a.configured);\n const appCount = service.apps.length;\n\n return (\n <>\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"group flex flex-col items-start gap-2 rounded-2xl border bg-card p-5 text-left transition hover:border-foreground/20 hover:bg-card/80 cursor-pointer\"\n >\n <div className=\"flex w-full items-start justify-between gap-2\">\n <div className=\"flex h-9 w-9 items-center justify-center rounded-xl bg-muted\">\n <IconKey size={16} className=\"text-muted-foreground\" />\n </div>\n {isConnected ? (\n <Badge\n variant=\"secondary\"\n className=\"bg-green-500/10 text-green-700 dark:text-green-400 gap-1\"\n >\n <IconCheck size={12} />\n Connected\n </Badge>\n ) : (\n <Badge\n variant=\"secondary\"\n className=\"bg-amber-500/10 text-amber-700 dark:text-amber-400 gap-1\"\n >\n <IconCircleDashed size={12} />\n Connect\n </Badge>\n )}\n </div>\n <div className=\"min-w-0\">\n <div className=\"text-sm font-semibold text-foreground truncate\">\n {service.label}\n </div>\n <div className=\"font-mono text-xs text-muted-foreground/80 truncate\">\n {service.key}\n </div>\n </div>\n <div className=\"text-xs text-muted-foreground\">\n Used by {appCount} {appCount === 1 ? \"app\" : \"apps\"}\n </div>\n </button>\n <ConnectDialog service={service} open={open} onOpenChange={setOpen} />\n </>\n );\n}\n\nfunction PerAppDetailRow({ app }: { app: CatalogApp }) {\n const total = (app.integrations ?? []).length;\n const ok = (app.integrations ?? []).filter((i) => i.configured).length;\n return (\n <div className=\"flex items-center justify-between border-t px-4 py-2.5 first:border-t-0\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <div\n className=\"h-5 w-5 rounded text-[10px] font-bold text-white flex items-center justify-center shrink-0\"\n style={{ backgroundColor: app.color }}\n >\n {app.appName.charAt(0).toUpperCase()}\n </div>\n <span className=\"text-sm truncate\">{app.appName}</span>\n {!app.reachable && (\n <span className=\"text-xs text-muted-foreground\">offline</span>\n )}\n </div>\n <span className=\"text-xs text-muted-foreground\">\n {total === 0 ? \"no integrations\" : `${ok}/${total}`}\n </span>\n </div>\n );\n}\n\nexport default function ConnectionsRoute() {\n const { data: catalog, isLoading } = useActionQuery(\n \"list-integrations-catalog\",\n {},\n );\n const apps = (catalog as CatalogApp[]) || [];\n\n const services = useMemo<Service[]>(() => {\n const map = new Map<string, Service>();\n for (const app of apps) {\n for (const intg of app.integrations ?? []) {\n if (!map.has(intg.key)) {\n map.set(intg.key, {\n key: intg.key,\n label: intg.label,\n apps: [],\n });\n }\n map.get(intg.key)!.apps.push({\n appId: app.appId,\n appName: app.appName,\n color: app.color,\n configured: intg.configured,\n vaultGranted: intg.vaultGranted,\n vaultSecretId: intg.vaultSecretId,\n });\n }\n }\n return Array.from(map.values()).sort((a, b) =>\n a.label.localeCompare(b.label),\n );\n }, [apps]);\n\n const available = services.filter((s) => !s.apps.some((a) => a.configured));\n const connected = services.filter((s) => s.apps.some((a) => a.configured));\n\n return (\n <DispatchShell\n title=\"Connections\"\n description=\"Connect services once. Apps that need them pick up the key automatically.\"\n >\n {isLoading && services.length === 0 && (\n <div className=\"rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground\">\n Discovering apps and credentials…\n </div>\n )}\n\n {!isLoading && services.length === 0 && (\n <div className=\"rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground\">\n No apps with declared integrations are reachable yet.\n </div>\n )}\n\n {available.length > 0 && (\n <section>\n <div className=\"mb-3 flex items-baseline justify-between\">\n <h2 className=\"text-sm font-medium text-foreground\">\n Available to connect\n </h2>\n <span className=\"text-xs text-muted-foreground\">\n {available.length}\n </span>\n </div>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {available.map((service) => (\n <ConnectorCard key={service.key} service={service} />\n ))}\n </div>\n </section>\n )}\n\n {connected.length > 0 && (\n <section>\n <div className=\"mb-3 mt-2 flex items-baseline justify-between\">\n <h2 className=\"text-sm font-medium text-foreground\">Connected</h2>\n <span className=\"text-xs text-muted-foreground\">\n {connected.length}\n </span>\n </div>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {connected.map((service) => (\n <ConnectorCard key={service.key} service={service} />\n ))}\n </div>\n </section>\n )}\n\n {apps.length > 0 && (\n <Collapsible className=\"mt-6 rounded-2xl border bg-card\">\n <CollapsibleTrigger className=\"flex w-full items-center justify-between px-4 py-3 text-sm\">\n <span className=\"flex items-center gap-2 text-muted-foreground\">\n <IconPlugConnected size={14} />\n Per-app status\n </span>\n <IconChevronRight\n size={14}\n className=\"text-muted-foreground transition group-data-[state=open]:rotate-90\"\n />\n </CollapsibleTrigger>\n <CollapsibleContent>\n <div className=\"border-t\">\n {apps.map((app) => (\n <PerAppDetailRow key={app.appId} app={app} />\n ))}\n </div>\n <div className=\"flex items-center justify-end gap-1.5 border-t px-4 py-2.5 text-xs text-muted-foreground\">\n <IconLink size={12} />\n <a href=\"/vault\" className=\"hover:underline\">\n Open vault for advanced sharing\n </a>\n </div>\n </CollapsibleContent>\n </Collapsible>\n )}\n </DispatchShell>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/overview.tsx"],"names":[],"mappings":"AA6YA,wBAAgB,IAAI;;IAEnB;AAED,MAAM,CAAC,OAAO,UAAU,aAAa,4CA0SpC"}
1
+ {"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/overview.tsx"],"names":[],"mappings":"AAmcA,wBAAgB,IAAI;;IAEnB;AAED,MAAM,CAAC,OAAO,UAAU,aAAa,4CAoRpC"}
@@ -39,6 +39,15 @@ function HomeChatPanel() {
39
39
  function AppCardSkeleton() {
40
40
  return (_jsx("div", { className: "rounded-lg border bg-card p-4", children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [_jsx(Skeleton, { className: "h-4 w-32" }), _jsx(Skeleton, { className: "h-3 w-24" }), _jsxs("div", { className: "space-y-2 pt-1", children: [_jsx(Skeleton, { className: "h-3 w-full" }), _jsx(Skeleton, { className: "h-3 w-2/3" })] })] }), _jsx(Skeleton, { className: "h-5 w-5 rounded-md" })] }) }));
41
41
  }
42
+ function RecentActivityList({ isLoading, events, }) {
43
+ if (isLoading && events.length === 0) {
44
+ return (_jsx("div", { className: "mt-4 space-y-3", children: Array.from({ length: 3 }).map((_, index) => (_jsxs("div", { className: "rounded-xl border bg-muted/30 px-4 py-3 space-y-2", children: [_jsx(Skeleton, { className: "h-4 w-3/5" }), _jsx(Skeleton, { className: "h-3 w-2/5" })] }, index))) }));
45
+ }
46
+ if (events.length === 0) {
47
+ return (_jsx("div", { className: "mt-4 space-y-3", children: _jsx("div", { className: "rounded-xl border border-dashed px-4 py-6 text-sm text-muted-foreground", children: "No activity yet." }) }));
48
+ }
49
+ return (_jsx("div", { className: "mt-4 space-y-3", children: events.map((event) => (_jsxs("div", { className: "rounded-xl border bg-muted/30 px-4 py-3", children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: event.summary }), _jsxs("div", { className: "mt-1 text-xs text-muted-foreground", children: [event.actor, " \u00B7 ", new Date(event.createdAt).toLocaleString()] })] }, event.id))) }));
50
+ }
42
51
  function WorkspaceAppsSection({ apps, isLoading, }) {
43
52
  const filteredApps = apps.filter((app) => !app.isDispatch);
44
53
  const visibleApps = filteredApps.slice(0, 6);
@@ -188,7 +197,7 @@ export default function OverviewRoute() {
188
197
  },
189
198
  ];
190
199
  const hasIncompleteSteps = steps.some((s) => !s.complete && !s.informational);
191
- return (_jsxs(DispatchShell, { title: "Overview", description: "Create apps, manage shared keys, and route work across your workspace.", children: [_jsx(HomeChatPanel, {}), _jsx(WorkspaceAppsSection, { apps: typedWorkspaceApps, isLoading: appsLoading }), hasIncompleteSteps && (_jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(IconRocket, { size: 16, className: "text-muted-foreground" }), _jsx("h2", { className: "text-sm font-semibold text-foreground", children: "Getting started" })] }), _jsx("div", { className: "space-y-2", children: steps.map((step) => (_jsx(StepRow, { step: step }, step.number))) })] })), _jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(IconActivity, { size: 16, className: "text-muted-foreground" }), _jsx("h2", { className: "text-sm font-semibold text-foreground", children: "At a glance" })] }), _jsxs("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4", children: [_jsx(StatCard, { label: "Vault secrets", help: "Credentials stored in the workspace vault. Grant them to apps from the Vault page.", value: data?.vault?.secretCount || 0, icon: IconKey, cta: (data?.vault?.secretCount || 0) === 0 ? (_jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsx(Link, { to: "/vault", children: "Set up vault" }) })) : undefined }), _jsx(StatCard, { label: "Active grants", help: "Secrets currently granted to apps. Sync them to push credentials.", value: data?.vault?.activeGrantCount || 0, icon: IconShieldCheck }), _jsx(StatCard, { label: "Destinations", help: "Saved outbound targets used for proactive sends and scheduled jobs.", value: counts.destinations, icon: IconArrowUpRight, cta: counts.destinations === 0 ? (_jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsx(Link, { to: "/destinations", children: "Set up destinations" }) })) : undefined }), _jsx(StatCard, { label: "Agents", help: "Agents available to dispatch for delegation over A2A. This includes the built-in app suite plus any additional agents you add.", value: connectedAgentCount, icon: IconPlugConnected, cta: connectedAgentCount === 0 ? (_jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsx(Link, { to: "/agents", children: "Open agents" }) })) : undefined })] })] }), _jsxs("details", { className: "rounded-xl border", children: [_jsxs("summary", { className: "flex cursor-pointer list-none items-center justify-between gap-3 px-5 py-4 text-sm font-semibold text-foreground hover:bg-muted/30 [&::-webkit-details-marker]:hidden", children: [_jsx("span", { children: "Operations detail" }), _jsx("span", { className: "text-xs font-normal text-muted-foreground", children: "Queue, audit, and approvals" })] }), _jsxs("div", { className: "space-y-5 border-t px-5 py-5", children: [_jsx(TaskQueueSection, { stats: taskQueueStats }), _jsxs("div", { className: "grid gap-4 xl:grid-cols-3", children: [_jsxs("section", { className: "rounded-2xl border bg-card p-5 xl:col-span-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Recent activity" }), isLoading && (_jsx("span", { className: "text-xs text-muted-foreground", children: "Loading..." }))] }), _jsxs("div", { className: "mt-4 space-y-3", children: [(data?.recentAudit || []).map((event) => (_jsxs("div", { className: "rounded-xl border bg-muted/30 px-4 py-3", children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: event.summary }), _jsxs("div", { className: "mt-1 text-xs text-muted-foreground", children: [event.actor, " \u00B7", " ", new Date(event.createdAt).toLocaleString()] })] }, event.id))), !isLoading && (data?.recentAudit?.length || 0) === 0 && (_jsx("div", { className: "rounded-xl border border-dashed px-4 py-6 text-sm text-muted-foreground", children: "No activity yet." }))] })] }), _jsxs("section", { className: "rounded-2xl border bg-card p-5", children: [_jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Approval mode" }), _jsxs("div", { className: "mt-4 rounded-xl border bg-muted/30 p-4", children: [_jsx("div", { className: "text-sm font-medium text-muted-foreground", children: "Current policy" }), _jsx("div", { className: "mt-2 text-2xl font-semibold text-foreground", children: data?.settings?.enabled ? "Reviewed" : "Immediate" }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: data?.settings?.enabled
200
+ return (_jsxs(DispatchShell, { title: "Overview", description: "Create apps, manage shared keys, and route work across your workspace.", children: [_jsx(HomeChatPanel, {}), _jsx(WorkspaceAppsSection, { apps: typedWorkspaceApps, isLoading: appsLoading }), hasIncompleteSteps && (_jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(IconRocket, { size: 16, className: "text-muted-foreground" }), _jsx("h2", { className: "text-sm font-semibold text-foreground", children: "Getting started" })] }), _jsx("div", { className: "space-y-2", children: steps.map((step) => (_jsx(StepRow, { step: step }, step.number))) })] })), _jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(IconActivity, { size: 16, className: "text-muted-foreground" }), _jsx("h2", { className: "text-sm font-semibold text-foreground", children: "At a glance" })] }), _jsxs("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4", children: [_jsx(StatCard, { label: "Vault secrets", help: "Credentials stored in the workspace vault. Grant them to apps from the Vault page.", value: data?.vault?.secretCount || 0, icon: IconKey, cta: (data?.vault?.secretCount || 0) === 0 ? (_jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsx(Link, { to: "/vault", children: "Set up vault" }) })) : undefined }), _jsx(StatCard, { label: "Active grants", help: "Secrets currently granted to apps. Sync them to push credentials.", value: data?.vault?.activeGrantCount || 0, icon: IconShieldCheck }), _jsx(StatCard, { label: "Destinations", help: "Saved outbound targets used for proactive sends and scheduled jobs.", value: counts.destinations, icon: IconArrowUpRight, cta: counts.destinations === 0 ? (_jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsx(Link, { to: "/destinations", children: "Set up destinations" }) })) : undefined }), _jsx(StatCard, { label: "Agents", help: "Agents available to dispatch for delegation over A2A. This includes the built-in app suite plus any additional agents you add.", value: connectedAgentCount, icon: IconPlugConnected, cta: connectedAgentCount === 0 ? (_jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsx(Link, { to: "/agents", children: "Open agents" }) })) : undefined })] })] }), _jsxs("details", { className: "rounded-xl border", children: [_jsxs("summary", { className: "flex cursor-pointer list-none items-center justify-between gap-3 px-5 py-4 text-sm font-semibold text-foreground hover:bg-muted/30 [&::-webkit-details-marker]:hidden", children: [_jsx("span", { children: "Operations detail" }), _jsx("span", { className: "text-xs font-normal text-muted-foreground", children: "Queue, audit, and approvals" })] }), _jsxs("div", { className: "space-y-5 border-t px-5 py-5", children: [_jsx(TaskQueueSection, { stats: taskQueueStats }), _jsxs("div", { className: "grid gap-4 xl:grid-cols-3", children: [_jsxs("section", { className: "rounded-2xl border bg-card p-5 xl:col-span-2", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Recent activity" }) }), _jsx(RecentActivityList, { isLoading: isLoading, events: data?.recentAudit ?? [] })] }), _jsxs("section", { className: "rounded-2xl border bg-card p-5", children: [_jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Approval mode" }), _jsxs("div", { className: "mt-4 rounded-xl border bg-muted/30 p-4", children: [_jsx("div", { className: "text-sm font-medium text-muted-foreground", children: "Current policy" }), _jsx("div", { className: "mt-2 text-2xl font-semibold text-foreground", children: data?.settings?.enabled ? "Reviewed" : "Immediate" }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: data?.settings?.enabled
192
201
  ? "Changes wait for approval before they apply."
193
202
  : "Changes apply immediately and are recorded in audit." })] }), _jsxs("div", { className: "mt-4 space-y-2", children: [(data?.recentApprovals || []).map((approval) => (_jsxs("div", { className: "rounded-xl border px-4 py-3", children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: approval.summary }), _jsxs("div", { className: "mt-1 text-xs text-muted-foreground", children: [approval.status, " \u00B7 requested by ", approval.requestedBy] })] }, approval.id))), (data?.recentApprovals?.length || 0) === 0 && (_jsx("div", { className: "rounded-xl border border-dashed px-4 py-6 text-sm text-muted-foreground", children: "No approval requests." }))] })] })] })] })] })] }));
194
203
  }