@korso/shepherd-ui 0.3.1 → 0.5.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/LICENSE +661 -0
- package/dist/ShepherdRoot.js +13 -12
- package/dist/client.d.ts +6 -0
- package/dist/client.js +14 -0
- package/dist/components/Dashboard.d.ts +7 -1
- package/dist/components/Dashboard.js +2 -2
- package/dist/config/ConfigPanel.d.ts +14 -0
- package/dist/config/ConfigPanel.js +16 -0
- package/dist/config/ConnectAgent.d.ts +1 -2
- package/dist/config/ConnectAgent.js +6 -12
- package/dist/config/GeneralSettings.d.ts +8 -0
- package/dist/config/GeneralSettings.js +27 -0
- package/dist/config/Invites.d.ts +7 -0
- package/dist/config/Invites.js +84 -0
- package/dist/config/Members.d.ts +1 -3
- package/dist/config/Members.js +4 -23
- package/dist/config/WorkspaceSwitcher.d.ts +14 -0
- package/dist/config/WorkspaceSwitcher.js +108 -0
- package/dist/config/index.d.ts +8 -2
- package/dist/config/index.js +6 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/lib/Dashboard-C48qY8R2.js +1412 -0
- package/dist/lib/index.d.ts +63 -22
- package/dist/lib/index.js +552 -302
- package/dist/lib/selfhost.js +1 -1
- package/dist/lib/styles.css +128 -18
- package/dist/preview.js +40 -4
- package/dist/selfhost/assets/index-CK7qrsNo.css +1 -0
- package/dist/selfhost/assets/index-adPvx18g.js +40 -0
- package/dist/selfhost/index.html +2 -2
- package/dist/test/mockClient.js +3 -0
- package/package.json +2 -2
- package/dist/config/Workspaces.d.ts +0 -15
- package/dist/config/Workspaces.js +0 -116
- package/dist/lib/Dashboard-Bgi50wCo.js +0 -1396
- package/dist/selfhost/assets/index-D0C3m6st.js +0 -40
- package/dist/selfhost/assets/index-rNR1Acph.css +0 -1
package/dist/selfhost/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Shepherd</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-adPvx18g.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CK7qrsNo.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/dist/test/mockClient.js
CHANGED
|
@@ -22,6 +22,9 @@ export function makeMockClient(overrides = {}) {
|
|
|
22
22
|
mintToken: vi.fn().mockResolvedValue({ token: "shp_mock", id: "tok_mock" }),
|
|
23
23
|
listTokens: vi.fn().mockResolvedValue({ tokens: [] }),
|
|
24
24
|
revokeToken: vi.fn().mockResolvedValue(undefined),
|
|
25
|
+
mintAccountToken: vi.fn().mockResolvedValue({ token: "shp_mock", id: "tok_mock" }),
|
|
26
|
+
listAccountTokens: vi.fn().mockResolvedValue({ tokens: [] }),
|
|
27
|
+
revokeAccountToken: vi.fn().mockResolvedValue(undefined),
|
|
25
28
|
createInvite: vi.fn().mockResolvedValue({
|
|
26
29
|
code: "INV-MOCK",
|
|
27
30
|
expiresAt: null,
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@korso/shepherd-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Auth-agnostic React UI library for the Shepherd wallboard (Dashboard, client, provider). Host injects auth; the library never reads tokens.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "AGPL-3.0-only",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { WorkspaceSummaryT } from "@shepherd/shared";
|
|
2
|
-
export interface WorkspacesProps {
|
|
3
|
-
/** All workspaces the account belongs to (for the switcher / context). */
|
|
4
|
-
workspaces: WorkspaceSummaryT[];
|
|
5
|
-
/** The currently-selected workspace, or null when the account has none. */
|
|
6
|
-
selected: WorkspaceSummaryT | null;
|
|
7
|
-
/** Called after a mutation that changes membership (create / join / leave). */
|
|
8
|
-
onChanged: () => void;
|
|
9
|
-
/** Switch the active workspace (optional simple switcher). */
|
|
10
|
-
onSelect?: (workspaceId: string) => void;
|
|
11
|
-
/** Called after a mutation that may change the selected workspace's roster
|
|
12
|
-
* (an invite created, a code redeemed), so the sibling <Members> refetches. */
|
|
13
|
-
onMembersChanged?: () => void;
|
|
14
|
-
}
|
|
15
|
-
export declare function Workspaces({ workspaces, selected, onChanged, onSelect, onMembersChanged, }: WorkspacesProps): import("react").JSX.Element;
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useId, useState } from "react";
|
|
3
|
-
import { useShepherdClient } from "../context.js";
|
|
4
|
-
import { describeError } from "../client.js";
|
|
5
|
-
export function Workspaces({ workspaces, selected, onChanged, onSelect, onMembersChanged, }) {
|
|
6
|
-
const client = useShepherdClient();
|
|
7
|
-
const headingId = useId();
|
|
8
|
-
const [name, setName] = useState("");
|
|
9
|
-
const [code, setCode] = useState("");
|
|
10
|
-
const [invite, setInvite] = useState(null);
|
|
11
|
-
const [error, setError] = useState(null);
|
|
12
|
-
const [busy, setBusy] = useState(false);
|
|
13
|
-
const [emailAddress, setEmailAddress] = useState("");
|
|
14
|
-
const [emailStatus, setEmailStatus] = useState(null);
|
|
15
|
-
const [emailBusy, setEmailBusy] = useState(false);
|
|
16
|
-
const isAdmin = selected?.role === "admin";
|
|
17
|
-
async function create() {
|
|
18
|
-
if (!name.trim() || busy)
|
|
19
|
-
return;
|
|
20
|
-
setBusy(true);
|
|
21
|
-
setError(null);
|
|
22
|
-
try {
|
|
23
|
-
await client.createWorkspace({ name: name.trim() });
|
|
24
|
-
setName("");
|
|
25
|
-
onChanged();
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
setError(describeError(err));
|
|
29
|
-
}
|
|
30
|
-
finally {
|
|
31
|
-
setBusy(false);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
async function join() {
|
|
35
|
-
if (!code.trim() || busy)
|
|
36
|
-
return;
|
|
37
|
-
setBusy(true);
|
|
38
|
-
setError(null);
|
|
39
|
-
try {
|
|
40
|
-
await client.redeemInvite(code.trim());
|
|
41
|
-
setCode("");
|
|
42
|
-
onChanged();
|
|
43
|
-
onMembersChanged?.();
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
setError(describeError(err));
|
|
47
|
-
}
|
|
48
|
-
finally {
|
|
49
|
-
setBusy(false);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async function createInvite() {
|
|
53
|
-
if (!selected || busy)
|
|
54
|
-
return;
|
|
55
|
-
setBusy(true);
|
|
56
|
-
setError(null);
|
|
57
|
-
try {
|
|
58
|
-
const res = await client.createInvite(selected.id, {});
|
|
59
|
-
setInvite(res);
|
|
60
|
-
onMembersChanged?.();
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
setError(describeError(err));
|
|
64
|
-
}
|
|
65
|
-
finally {
|
|
66
|
-
setBusy(false);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
async function sendEmailInvite() {
|
|
70
|
-
if (!selected || !emailAddress.trim() || emailBusy)
|
|
71
|
-
return;
|
|
72
|
-
setEmailBusy(true);
|
|
73
|
-
setEmailStatus(null);
|
|
74
|
-
try {
|
|
75
|
-
const res = await client.inviteByEmail(selected.id, emailAddress.trim());
|
|
76
|
-
setEmailStatus(`Invite sent to ${res.email}.`);
|
|
77
|
-
setEmailAddress("");
|
|
78
|
-
onMembersChanged?.();
|
|
79
|
-
}
|
|
80
|
-
catch (err) {
|
|
81
|
-
setEmailStatus(describeError(err));
|
|
82
|
-
}
|
|
83
|
-
finally {
|
|
84
|
-
setEmailBusy(false);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
async function revokeInvite() {
|
|
88
|
-
if (!selected || !invite || busy)
|
|
89
|
-
return;
|
|
90
|
-
setBusy(true);
|
|
91
|
-
setError(null);
|
|
92
|
-
try {
|
|
93
|
-
await client.revokeInvite(selected.id, invite.code);
|
|
94
|
-
setInvite(null);
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
setError(describeError(err));
|
|
98
|
-
}
|
|
99
|
-
finally {
|
|
100
|
-
setBusy(false);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// Clear any displayed invite/email status when the selected workspace changes.
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
setInvite(null);
|
|
106
|
-
setEmailStatus(null);
|
|
107
|
-
}, [selected?.id]);
|
|
108
|
-
// InviteResponse has no `link`; build the shareable join URL from the code.
|
|
109
|
-
// Encode the code for path-safety (parity with client.ts path encoding).
|
|
110
|
-
const joinLink = invite
|
|
111
|
-
? `${window.location.origin}/shepherd/join/${encodeURIComponent(invite.code)}`
|
|
112
|
-
: null;
|
|
113
|
-
return (_jsxs("section", { className: "shepherd-workspaces", "aria-labelledby": headingId, children: [_jsxs("div", { className: "card-head", children: [_jsx("h3", { id: headingId, children: "Workspace management" }), _jsx("p", { className: "card-sub", children: "Create a workspace to get started, or join an existing one with an invite code." })] }), _jsxs("div", { className: "card-body", children: [error && _jsx("p", { role: "alert", children: error }), workspaces.length > 1 && onSelect && (_jsxs("div", { className: "field switcher", children: [_jsx("label", { htmlFor: "ws-switcher", children: "Active workspace" }), _jsx("select", { id: "ws-switcher", value: selected?.id ?? "", onChange: (e) => onSelect(e.target.value), children: workspaces.map((w) => (_jsx("option", { value: w.id, children: w.name }, w.id))) })] })), _jsxs("div", { className: "field create", children: [_jsx("label", { htmlFor: "ws-name", children: "Workspace name" }), _jsx("p", { className: "helper", children: "Start a fresh workspace \u2014 you'll be its admin." }), _jsxs("div", { className: "field__row", children: [_jsx("input", { id: "ws-name", type: "text", placeholder: "e.g. Acme Engineering", value: name, onChange: (e) => setName(e.target.value) }), _jsx("button", { type: "button", onClick: () => void create(), disabled: busy, children: "Create workspace" })] })] }), _jsxs("div", { className: "field join", children: [_jsx("label", { htmlFor: "invite-code", children: "Invite code" }), _jsx("p", { className: "helper", children: "Paste an invite code someone shared to join their workspace." }), _jsxs("div", { className: "field__row", children: [_jsx("input", { id: "invite-code", type: "text", placeholder: "Invite code", value: code, onChange: (e) => setCode(e.target.value) }), _jsx("button", { type: "button", onClick: () => void join(), disabled: busy, children: "Join" })] })] }), isAdmin && selected && (_jsxs("div", { className: "admin", children: [_jsxs("div", { className: "invites", children: [_jsx("button", { type: "button", onClick: () => void createInvite(), disabled: busy, children: "Create invite" }), invite && (_jsxs("div", { className: "invite-result", children: [_jsxs("div", { className: "invite-result__row", children: [_jsx("code", { children: invite.code }), _jsx("span", { className: "invite-uses", children: invite.maxUses === null
|
|
114
|
-
? `${invite.useCount} uses`
|
|
115
|
-
: `${invite.useCount} / ${invite.maxUses} uses` }), _jsx("button", { type: "button", onClick: () => void revokeInvite(), disabled: busy, children: "Revoke invite" })] }), _jsx("a", { href: joinLink ?? undefined, children: joinLink })] }))] }), _jsxs("div", { className: "field email-invite", children: [_jsx("label", { htmlFor: "invite-email", children: "Email invite" }), _jsx("p", { className: "helper", children: "Send a one-time-use join link directly to someone's inbox." }), _jsxs("div", { className: "field__row", children: [_jsx("input", { id: "invite-email", type: "email", placeholder: "name@example.com", value: emailAddress, onChange: (e) => setEmailAddress(e.target.value) }), _jsx("button", { type: "button", onClick: () => void sendEmailInvite(), disabled: emailBusy, children: "Send invite" })] }), emailStatus && _jsx("p", { className: "email-invite__status", children: emailStatus })] })] }))] })] }));
|
|
116
|
-
}
|