@korso/shepherd-ui 0.5.1 → 0.7.0
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.
- package/dist/ShepherdRoot.js +1 -1
- package/dist/client.d.ts +3 -1
- package/dist/client.js +13 -4
- package/dist/config/ConfigPanel.d.ts +3 -1
- package/dist/config/ConfigPanel.js +2 -2
- package/dist/config/ConfirmDeleteWorkspace.d.ts +13 -0
- package/dist/config/ConfirmDeleteWorkspace.js +28 -0
- package/dist/config/ConnectAgent.js +29 -11
- package/dist/config/GeneralSettings.d.ts +3 -1
- package/dist/config/GeneralSettings.js +41 -3
- package/dist/lib/{Dashboard-qipRa5MO.js → Dashboard-D7tbhMwK.js} +312 -307
- package/dist/lib/index.d.ts +18 -2
- package/dist/lib/index.js +446 -318
- package/dist/lib/selfhost.js +1 -1
- package/dist/lib/styles.css +43 -5
- package/dist/preview.js +4 -0
- package/dist/selfhost/assets/{index-xMSuivCM.js → index--_PPE03M.js} +7 -7
- package/dist/selfhost/assets/{index-DfVGGaT0.css → index-dBmCZSQx.css} +1 -1
- package/dist/selfhost/index.html +2 -2
- package/dist/test/mockClient.js +1 -0
- package/package.json +1 -1
package/dist/ShepherdRoot.js
CHANGED
|
@@ -48,6 +48,6 @@ export function ShepherdRoot({ hubUrl }) {
|
|
|
48
48
|
// The Config tab's content. With a workspace selected it's the sidebar
|
|
49
49
|
// <ConfigPanel>; with none it's a prompt pointing at the switcher, since
|
|
50
50
|
// create/join now live there (the sidebar has nothing to configure).
|
|
51
|
-
const configSection = (_jsxs("section", { className: "shepherd-config", "aria-labelledby": "config-heading", children: [_jsx("h2", { id: "config-heading", children: "Configuration" }), selected ? (_jsx(ConfigPanel, { workspace: selected, hubUrl: hubUrl, membersRefreshKey: membersRefreshKey, onMembersChanged: () => setMembersRefreshKey((k) => k + 1), onLeft: () => void fetchWorkspaces() })) : (_jsxs("p", { className: "config-none", children: ["You're not in a workspace yet. Use the ", _jsx("b", { children: "Get started \u25BE" }), " menu at the top to create one or join with an invite code."] }))] }));
|
|
51
|
+
const configSection = (_jsxs("section", { className: "shepherd-config", "aria-labelledby": "config-heading", children: [_jsx("h2", { id: "config-heading", children: "Configuration" }), selected ? (_jsx(ConfigPanel, { workspace: selected, hubUrl: hubUrl, membersRefreshKey: membersRefreshKey, onMembersChanged: () => setMembersRefreshKey((k) => k + 1), onLeft: () => void fetchWorkspaces(), onDeleted: () => void fetchWorkspaces() })) : (_jsxs("p", { className: "config-none", children: ["You're not in a workspace yet. Use the ", _jsx("b", { children: "Get started \u25BE" }), " menu at the top to create one or join with an invite code."] }))] }));
|
|
52
52
|
return (_jsx("div", { className: "shepherd-root", children: _jsx(Dashboard, { workspaceId: selected?.id, config: configSection, switcher: switcher, hasWorkspace: hasWorkspace }) }));
|
|
53
53
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ListWorkspacesResponseT, CreateWorkspaceRequestT, CreateWorkspaceResponseT, MintTokenRequestT, MintTokenResponseT, ListTokensResponseT, CreateInviteRequestT, InviteResponseT, InviteByEmailResponseT, RedeemInviteResponseT, ListMembersResponseT, WorkspaceLandscapeResponseT, WorkspaceAnnounceRequestT, WorkspaceAnnounceResponseT, FeedbackRequestT, FeedbackResponseT } from "@shepherd/shared";
|
|
1
|
+
import type { ListWorkspacesResponseT, CreateWorkspaceRequestT, CreateWorkspaceResponseT, DeleteWorkspaceResponseT, MintTokenRequestT, MintTokenResponseT, ListTokensResponseT, CreateInviteRequestT, InviteResponseT, InviteByEmailResponseT, RedeemInviteResponseT, ListMembersResponseT, WorkspaceLandscapeResponseT, WorkspaceAnnounceRequestT, WorkspaceAnnounceResponseT, FeedbackRequestT, FeedbackResponseT } from "@shepherd/shared";
|
|
2
2
|
/**
|
|
3
3
|
* The single error type surfaced by the client. A missing `status` means a
|
|
4
4
|
* transport failure (network error or request timeout); a present `status` is
|
|
@@ -43,6 +43,8 @@ export interface ShepherdClient {
|
|
|
43
43
|
listWorkspaces(): Promise<ListWorkspacesResponseT>;
|
|
44
44
|
/** POST a new workspace; the caller becomes its admin. */
|
|
45
45
|
createWorkspace(body: CreateWorkspaceRequestT): Promise<CreateWorkspaceResponseT>;
|
|
46
|
+
/** DELETE a workspace and ALL its data (admin only). Permanent, irreversible. */
|
|
47
|
+
deleteWorkspace(workspaceId: string): Promise<DeleteWorkspaceResponseT>;
|
|
46
48
|
/** POST a new agent token; the raw `shp_` value is returned exactly once. */
|
|
47
49
|
mintToken(workspaceId: string, body: MintTokenRequestT): Promise<MintTokenResponseT>;
|
|
48
50
|
/** GET the workspace's token metadata (never the raw token). */
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ListWorkspacesResponse, CreateWorkspaceResponse, MintTokenResponse, ListTokensResponse, InviteResponse, InviteByEmailResponse, RedeemInviteResponse, ListMembersResponse, WorkspaceLandscapeResponse, WorkspaceAnnounceResponse, FeedbackResponse, } from "@shepherd/shared";
|
|
1
|
+
import { ListWorkspacesResponse, CreateWorkspaceResponse, DeleteWorkspaceResponse, MintTokenResponse, ListTokensResponse, InviteResponse, InviteByEmailResponse, RedeemInviteResponse, ListMembersResponse, WorkspaceLandscapeResponse, WorkspaceAnnounceResponse, FeedbackResponse, } from "@shepherd/shared";
|
|
2
2
|
/** Per-request abort fires after this many ms when no timeoutMs is configured. */
|
|
3
3
|
const DEFAULT_TIMEOUT_MS = 5000;
|
|
4
4
|
/**
|
|
@@ -88,9 +88,13 @@ export function createShepherdClient(config) {
|
|
|
88
88
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
89
89
|
let res;
|
|
90
90
|
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
// Only advertise a JSON body when there actually IS one. A bodyless
|
|
92
|
+
// POST/DELETE (leave, revoke, deleteWorkspace) that still sent
|
|
93
|
+
// `Content-Type: application/json` made the hub's Fastify parser reject the
|
|
94
|
+
// empty body (FST_ERR_CTP_EMPTY_JSON_BODY), which surfaced to the user as an
|
|
95
|
+
// opaque HTTP 500. Omitting the header on bodyless calls lets the request
|
|
96
|
+
// reach the route handler instead of dying in body parsing.
|
|
97
|
+
const baseHeaders = opts.body !== undefined ? { "Content-Type": "application/json" } : {};
|
|
94
98
|
// Resolve getAuthHeader (sync or async) into a header record: a string is
|
|
95
99
|
// the Authorization value, a map merges verbatim, undefined adds nothing.
|
|
96
100
|
// Injected auth wins over the base, so a host may even override the
|
|
@@ -163,6 +167,11 @@ export function createShepherdClient(config) {
|
|
|
163
167
|
schema: CreateWorkspaceResponse,
|
|
164
168
|
});
|
|
165
169
|
},
|
|
170
|
+
deleteWorkspace(workspaceId) {
|
|
171
|
+
return request("DELETE", `/workspaces/${enc(workspaceId)}`, {
|
|
172
|
+
schema: DeleteWorkspaceResponse,
|
|
173
|
+
});
|
|
174
|
+
},
|
|
166
175
|
mintToken(workspaceId, body) {
|
|
167
176
|
return request("POST", `/workspaces/${enc(workspaceId)}/tokens`, {
|
|
168
177
|
body,
|
|
@@ -10,5 +10,7 @@ export interface ConfigPanelProps {
|
|
|
10
10
|
onMembersChanged?: () => void;
|
|
11
11
|
/** Called after a successful leave, so the shell re-lists its workspaces. */
|
|
12
12
|
onLeft?: () => void;
|
|
13
|
+
/** Called after a successful delete, so the shell re-lists its workspaces. */
|
|
14
|
+
onDeleted?: () => void;
|
|
13
15
|
}
|
|
14
|
-
export declare function ConfigPanel({ workspace, hubUrl, membersRefreshKey, onMembersChanged, onLeft, }: ConfigPanelProps): import("react").JSX.Element;
|
|
16
|
+
export declare function ConfigPanel({ workspace, hubUrl, membersRefreshKey, onMembersChanged, onLeft, onDeleted, }: ConfigPanelProps): import("react").JSX.Element;
|
|
@@ -9,8 +9,8 @@ const SECTIONS = [
|
|
|
9
9
|
{ id: "members", label: "Members" },
|
|
10
10
|
{ id: "agent", label: "Agent" },
|
|
11
11
|
];
|
|
12
|
-
export function ConfigPanel({ workspace, hubUrl, membersRefreshKey, onMembersChanged, onLeft, }) {
|
|
12
|
+
export function ConfigPanel({ workspace, hubUrl, membersRefreshKey, onMembersChanged, onLeft, onDeleted, }) {
|
|
13
13
|
const [section, setSection] = useState("general");
|
|
14
14
|
const isAdmin = workspace.role === "admin";
|
|
15
|
-
return (_jsxs("div", { className: "config-layout", children: [_jsx("nav", { className: "config-nav", "aria-label": "Configuration sections", children: SECTIONS.map(({ id, label }) => (_jsx("button", { type: "button", className: "config-nav__item" + (section === id ? " config-nav__item--on" : ""), "aria-current": section === id ? "page" : undefined, onClick: () => setSection(id), children: label }, id))) }), _jsxs("div", { className: "config-panel", children: [section === "general" && (_jsx(GeneralSettings, { workspace: workspace, onLeft: onLeft })), section === "members" && (_jsxs(_Fragment, { children: [_jsx(Members, { workspaceId: workspace.id, refreshKey: membersRefreshKey, canRemove: isAdmin }), isAdmin && (_jsx(Invites, { workspaceId: workspace.id, onMembersChanged: onMembersChanged }))] })), section === "agent" && _jsx(ConnectAgent, { hubUrl: hubUrl })] })] }));
|
|
15
|
+
return (_jsxs("div", { className: "config-layout", children: [_jsx("nav", { className: "config-nav", "aria-label": "Configuration sections", children: SECTIONS.map(({ id, label }) => (_jsx("button", { type: "button", className: "config-nav__item" + (section === id ? " config-nav__item--on" : ""), "aria-current": section === id ? "page" : undefined, onClick: () => setSection(id), children: label }, id))) }), _jsxs("div", { className: "config-panel", children: [section === "general" && (_jsx(GeneralSettings, { workspace: workspace, onLeft: onLeft, onDeleted: onDeleted })), section === "members" && (_jsxs(_Fragment, { children: [_jsx(Members, { workspaceId: workspace.id, refreshKey: membersRefreshKey, canRemove: isAdmin }), isAdmin && (_jsx(Invites, { workspaceId: workspace.id, onMembersChanged: onMembersChanged }))] })), section === "agent" && _jsx(ConnectAgent, { hubUrl: hubUrl })] })] }));
|
|
16
16
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ConfirmDeleteWorkspaceProps {
|
|
2
|
+
/** The workspace name the operator must type verbatim to enable Delete. */
|
|
3
|
+
workspaceName: string;
|
|
4
|
+
/** True while the delete request is in flight — disables the controls. */
|
|
5
|
+
busy: boolean;
|
|
6
|
+
/** A failed-delete message to show inline, or null. */
|
|
7
|
+
error: string | null;
|
|
8
|
+
/** Fired when the confirmed Delete button is pressed. */
|
|
9
|
+
onConfirm: () => void;
|
|
10
|
+
/** Fired on Cancel / Escape / backdrop click (ignored while busy). */
|
|
11
|
+
onCancel: () => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function ConfirmDeleteWorkspace({ workspaceName, busy, error, onConfirm, onCancel, }: ConfirmDeleteWorkspaceProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
3
|
+
export function ConfirmDeleteWorkspace({ workspaceName, busy, error, onConfirm, onCancel, }) {
|
|
4
|
+
const headingId = useId();
|
|
5
|
+
const descId = useId();
|
|
6
|
+
const [typed, setTyped] = useState("");
|
|
7
|
+
const inputRef = useRef(null);
|
|
8
|
+
// Focus the confirmation input when the modal opens.
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
inputRef.current?.focus();
|
|
11
|
+
}, []);
|
|
12
|
+
// Escape cancels (unless a delete is in flight).
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
function onKey(e) {
|
|
15
|
+
if (e.key === "Escape" && !busy)
|
|
16
|
+
onCancel();
|
|
17
|
+
}
|
|
18
|
+
document.addEventListener("keydown", onKey);
|
|
19
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
20
|
+
}, [busy, onCancel]);
|
|
21
|
+
const confirmed = typed === workspaceName;
|
|
22
|
+
return (_jsx("div", { className: "shepherd-modal__backdrop", onClick: () => {
|
|
23
|
+
if (!busy)
|
|
24
|
+
onCancel();
|
|
25
|
+
}, children: _jsxs("div", { className: "shepherd-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": headingId, "aria-describedby": descId,
|
|
26
|
+
// Clicks inside the dialog must not bubble to the backdrop's cancel.
|
|
27
|
+
onClick: (e) => e.stopPropagation(), children: [_jsx("h3", { id: headingId, children: "Delete workspace" }), _jsxs("p", { id: descId, className: "shepherd-modal__body", children: ["This permanently deletes ", _jsx("b", { children: workspaceName }), " and all of its data \u2014 agents, sessions, tasks, announcements, change history, tokens, invites, and members. This cannot be undone."] }), _jsxs("label", { htmlFor: `${headingId}-input`, className: "shepherd-modal__label", children: ["Type ", _jsx("b", { children: workspaceName }), " to confirm"] }), _jsx("input", { id: `${headingId}-input`, ref: inputRef, type: "text", value: typed, autoComplete: "off", disabled: busy, onChange: (e) => setTyped(e.target.value) }), error && _jsx("p", { role: "alert", children: error }), _jsxs("div", { className: "shepherd-modal__actions", children: [_jsx("button", { type: "button", onClick: onCancel, disabled: busy, children: "Cancel" }), _jsx("button", { type: "button", className: "danger", onClick: onConfirm, disabled: !confirmed || busy, children: busy ? "Deleting…" : "Delete workspace" })] })] }) }));
|
|
28
|
+
}
|
|
@@ -7,17 +7,23 @@ const TOOLS = [
|
|
|
7
7
|
{ id: "claude", label: "Claude Code" },
|
|
8
8
|
{ id: "codex", label: "Codex" },
|
|
9
9
|
{ id: "pi", label: "Pi" },
|
|
10
|
+
{ id: "cursor", label: "Cursor" },
|
|
10
11
|
{ id: "generic", label: "Generic (JSON)" },
|
|
11
12
|
];
|
|
12
13
|
// The token placeholder shown before a real token is minted. Switching tools or
|
|
13
14
|
// reading the command pre-mint shows this, never a real secret.
|
|
14
15
|
const TOKEN_PLACEHOLDER = "shp_<paste-after-generating>";
|
|
15
|
-
//
|
|
16
|
-
//
|
|
16
|
+
// Tools with an `mcp add` CLI. Pi, Cursor, and generic get a JSON config
|
|
17
|
+
// block instead: Pi reads `~/.pi/agent/mcp.json` (or `.pi/mcp.json`), Cursor
|
|
18
|
+
// reads `~/.cursor/mcp.json` (or `.cursor/mcp.json`) — same `mcpServers` shape.
|
|
17
19
|
const CLI_PREFIX = {
|
|
18
20
|
claude: "claude mcp add shepherd -s user",
|
|
19
21
|
codex: "codex mcp add shepherd",
|
|
20
|
-
|
|
22
|
+
};
|
|
23
|
+
// `claude mcp add` takes `-e KEY=val`; `codex mcp add` only accepts `--env`.
|
|
24
|
+
const ENV_FLAG = {
|
|
25
|
+
claude: "-e",
|
|
26
|
+
codex: "--env",
|
|
21
27
|
};
|
|
22
28
|
// "created 3d ago · never used" / "created 3d ago · last used 2h ago" — helps
|
|
23
29
|
// an operator tell which tokens are still active before revoking one.
|
|
@@ -27,13 +33,18 @@ function tokenMeta(token, nowMs) {
|
|
|
27
33
|
return `${created} · ${used}`;
|
|
28
34
|
}
|
|
29
35
|
function installCommand(tool, hubUrl, token) {
|
|
30
|
-
if (tool === "generic") {
|
|
36
|
+
if (tool === "generic" || tool === "pi" || tool === "cursor") {
|
|
37
|
+
// PROGRAM names the tool in the presence feed; it defaults to
|
|
38
|
+
// `claude-code`, so JSON-config tools set it explicitly.
|
|
39
|
+
const env = { HUB_URL: hubUrl, SHEPHERD_TOKEN: token };
|
|
40
|
+
if (tool !== "generic")
|
|
41
|
+
env["PROGRAM"] = tool;
|
|
31
42
|
return JSON.stringify({
|
|
32
43
|
mcpServers: {
|
|
33
44
|
shepherd: {
|
|
34
45
|
command: "npx",
|
|
35
|
-
args: ["-y", "
|
|
36
|
-
env
|
|
46
|
+
args: ["-y", "--package=@korso/shepherd", "shepherd-mcp"],
|
|
47
|
+
env,
|
|
37
48
|
},
|
|
38
49
|
},
|
|
39
50
|
}, null, 2);
|
|
@@ -41,12 +52,19 @@ function installCommand(tool, hubUrl, token) {
|
|
|
41
52
|
// A single line with no shell-specific continuation character (`\` in
|
|
42
53
|
// bash/zsh, backtick in PowerShell, `^` in cmd) so the command pastes
|
|
43
54
|
// cleanly into any terminal.
|
|
44
|
-
|
|
55
|
+
const envFlag = ENV_FLAG[tool];
|
|
56
|
+
const parts = [
|
|
45
57
|
CLI_PREFIX[tool],
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
`${envFlag} HUB_URL=${hubUrl}`,
|
|
59
|
+
`${envFlag} SHEPHERD_TOKEN=${token}`,
|
|
60
|
+
];
|
|
61
|
+
// PROGRAM defaults to claude-code, so only non-Claude tools set it.
|
|
62
|
+
if (tool === "codex")
|
|
63
|
+
parts.push(`${envFlag} PROGRAM=codex`);
|
|
64
|
+
// NOTE: long `--package=` form on purpose — `claude mcp add` (CLI) fails to
|
|
65
|
+
// parse a bare `-p` after the `--` separator ("error: unknown option '-s'").
|
|
66
|
+
parts.push("-- npx -y --package=@korso/shepherd shepherd-mcp");
|
|
67
|
+
return parts.join(" ");
|
|
50
68
|
}
|
|
51
69
|
export function ConnectAgent({ hubUrl }) {
|
|
52
70
|
const client = useShepherdClient();
|
|
@@ -4,5 +4,7 @@ export interface GeneralSettingsProps {
|
|
|
4
4
|
workspace: WorkspaceSummaryT;
|
|
5
5
|
/** Called after a successful leave, so the shell refreshes its workspace list. */
|
|
6
6
|
onLeft?: () => void;
|
|
7
|
+
/** Called after a successful delete, so the shell refreshes its workspace list. */
|
|
8
|
+
onDeleted?: () => void;
|
|
7
9
|
}
|
|
8
|
-
export declare function GeneralSettings({ workspace, onLeft }: GeneralSettingsProps): import("react").JSX.Element;
|
|
10
|
+
export declare function GeneralSettings({ workspace, onLeft, onDeleted }: GeneralSettingsProps): import("react").JSX.Element;
|
|
@@ -1,27 +1,65 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useId, useState } from "react";
|
|
3
3
|
import { useShepherdClient } from "../context.js";
|
|
4
|
-
import { describeError } from "../client.js";
|
|
5
|
-
|
|
4
|
+
import { describeError, ShepherdClientError } from "../client.js";
|
|
5
|
+
import { ConfirmDeleteWorkspace } from "./ConfirmDeleteWorkspace.js";
|
|
6
|
+
export function GeneralSettings({ workspace, onLeft, onDeleted }) {
|
|
6
7
|
const client = useShepherdClient();
|
|
7
8
|
const headingId = useId();
|
|
8
9
|
const [error, setError] = useState(null);
|
|
9
10
|
const [leaving, setLeaving] = useState(false);
|
|
11
|
+
// Set when a leave is rejected because the caller is the last admin (409): they
|
|
12
|
+
// cannot leave by design, so we surface the "delete instead" hint pointing at
|
|
13
|
+
// the Delete section below (which is visible to them, since they are an admin).
|
|
14
|
+
const [lastAdminBlocked, setLastAdminBlocked] = useState(false);
|
|
15
|
+
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
16
|
+
const [deleting, setDeleting] = useState(false);
|
|
17
|
+
const [deleteError, setDeleteError] = useState(null);
|
|
18
|
+
const isAdmin = workspace.role === "admin";
|
|
10
19
|
async function leave() {
|
|
11
20
|
if (leaving)
|
|
12
21
|
return;
|
|
13
22
|
setLeaving(true);
|
|
14
23
|
setError(null);
|
|
24
|
+
setLastAdminBlocked(false);
|
|
15
25
|
try {
|
|
16
26
|
await client.leave(workspace.id);
|
|
17
27
|
onLeft?.();
|
|
18
28
|
}
|
|
19
29
|
catch (err) {
|
|
20
30
|
setError(describeError(err));
|
|
31
|
+
// A 409 here is the last-admin guard: the workspace must always keep an
|
|
32
|
+
// admin, so this member cannot leave. Point them at Delete instead.
|
|
33
|
+
if (err instanceof ShepherdClientError && err.status === 409) {
|
|
34
|
+
setLastAdminBlocked(true);
|
|
35
|
+
}
|
|
21
36
|
}
|
|
22
37
|
finally {
|
|
23
38
|
setLeaving(false);
|
|
24
39
|
}
|
|
25
40
|
}
|
|
26
|
-
|
|
41
|
+
async function confirmDelete() {
|
|
42
|
+
if (deleting)
|
|
43
|
+
return;
|
|
44
|
+
setDeleting(true);
|
|
45
|
+
setDeleteError(null);
|
|
46
|
+
try {
|
|
47
|
+
await client.deleteWorkspace(workspace.id);
|
|
48
|
+
setConfirmOpen(false);
|
|
49
|
+
onDeleted?.();
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
setDeleteError(describeError(err));
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
setDeleting(false);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return (_jsxs("section", { className: "shepherd-general", "aria-labelledby": headingId, children: [_jsxs("div", { className: "card-head", children: [_jsx("h3", { id: headingId, children: "General" }), _jsx("p", { className: "card-sub", children: "Your active workspace and its settings." })] }), _jsxs("div", { className: "card-body", children: [error && _jsx("p", { role: "alert", children: error }), lastAdminBlocked && isAdmin && (_jsx("p", { className: "helper", children: "You're the last admin, so you can't leave. To remove this workspace entirely, delete it below." })), _jsxs("div", { className: "field", children: [_jsx("label", { children: "Workspace name" }), _jsx("p", { className: "readonly-value", children: workspace.name })] }), _jsxs("div", { className: "field", children: [_jsx("label", { children: "Your role" }), _jsx("p", { className: "readonly-value", children: workspace.role })] }), _jsxs("div", { className: "field leave", children: [_jsx("label", { children: "Leave workspace" }), _jsx("p", { className: "helper", children: "Remove yourself from this workspace. You'll need a new invite to rejoin." }), _jsx("button", { type: "button", className: "danger", onClick: () => void leave(), disabled: leaving, children: "Leave workspace" })] }), isAdmin && (_jsxs("div", { className: "field delete", children: [_jsx("label", { children: "Delete workspace" }), _jsx("p", { className: "helper", children: "Permanently delete this workspace and all of its data. This cannot be undone." }), _jsx("button", { type: "button", className: "danger", onClick: () => {
|
|
59
|
+
setDeleteError(null);
|
|
60
|
+
setConfirmOpen(true);
|
|
61
|
+
}, disabled: deleting, children: "Delete workspace" })] }))] }), confirmOpen && (_jsx(ConfirmDeleteWorkspace, { workspaceName: workspace.name, busy: deleting, error: deleteError, onConfirm: () => void confirmDelete(), onCancel: () => {
|
|
62
|
+
if (!deleting)
|
|
63
|
+
setConfirmOpen(false);
|
|
64
|
+
} }))] }));
|
|
27
65
|
}
|