@tangle-network/agent-app 0.28.0 → 0.29.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/{DesignCanvasEditor-TN7CWUJW.js → DesignCanvasEditor-IB2FMBBD.js} +3 -3
- package/dist/InviteAcceptPage-EUOGZO2X.js +7 -0
- package/dist/InviteAcceptPage-EUOGZO2X.js.map +1 -0
- package/dist/MembersPanel-4CGUQRPF.js +8 -0
- package/dist/MembersPanel-4CGUQRPF.js.map +1 -0
- package/dist/access-ChAHG4Tp.d.ts +539 -0
- package/dist/chunk-3G334FVA.js +162 -0
- package/dist/chunk-3G334FVA.js.map +1 -0
- package/dist/chunk-3SVAA3MA.js +41 -0
- package/dist/chunk-3SVAA3MA.js.map +1 -0
- package/dist/chunk-535V6BH6.js +128 -0
- package/dist/chunk-535V6BH6.js.map +1 -0
- package/dist/chunk-63CE7FEZ.js +63 -0
- package/dist/chunk-63CE7FEZ.js.map +1 -0
- package/dist/{chunk-7I37CO5D.js → chunk-N4ZFKQ5C.js} +4 -4
- package/dist/{chunk-J7JNV5BH.js → chunk-S5CAJX6O.js} +2 -2
- package/dist/design-canvas-react/index.js +3 -3
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/index.js +7 -7
- package/dist/roles-CLtYKHHl.d.ts +49 -0
- package/dist/teams/drizzle.d.ts +42 -0
- package/dist/teams/drizzle.js +194 -0
- package/dist/teams/drizzle.js.map +1 -0
- package/dist/teams/index.d.ts +44 -0
- package/dist/teams/index.js +39 -0
- package/dist/teams/index.js.map +1 -0
- package/dist/teams/members-api.d.ts +152 -0
- package/dist/teams/members-api.js +261 -0
- package/dist/teams/members-api.js.map +1 -0
- package/dist/teams-react/index.d.ts +81 -0
- package/dist/teams-react/index.js +12 -0
- package/dist/teams-react/index.js.map +1 -0
- package/dist/teams-react/lazy.d.ts +9 -0
- package/dist/teams-react/lazy.js +13 -0
- package/dist/teams-react/lazy.js.map +1 -0
- package/package.json +26 -1
- /package/dist/{DesignCanvasEditor-TN7CWUJW.js.map → DesignCanvasEditor-IB2FMBBD.js.map} +0 -0
- /package/dist/{chunk-7I37CO5D.js.map → chunk-N4ZFKQ5C.js.map} +0 -0
- /package/dist/{chunk-J7JNV5BH.js.map → chunk-S5CAJX6O.js.map} +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasWorkspaceRole
|
|
3
|
+
} from "./chunk-63CE7FEZ.js";
|
|
4
|
+
|
|
5
|
+
// src/teams-react/components/MembersPanel.tsx
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
var ASSIGNABLE = [
|
|
9
|
+
{ value: "viewer", label: "Viewer" },
|
|
10
|
+
{ value: "editor", label: "Editor" },
|
|
11
|
+
{ value: "admin", label: "Admin" }
|
|
12
|
+
];
|
|
13
|
+
function MembersPanel({
|
|
14
|
+
members,
|
|
15
|
+
currentRole,
|
|
16
|
+
onInvite,
|
|
17
|
+
onChangeRole,
|
|
18
|
+
onRemove,
|
|
19
|
+
onNotice
|
|
20
|
+
}) {
|
|
21
|
+
const [inviteEmail, setInviteEmail] = useState("");
|
|
22
|
+
const [inviteRole, setInviteRole] = useState("editor");
|
|
23
|
+
const [inviting, setInviting] = useState(false);
|
|
24
|
+
const canManage = hasWorkspaceRole(currentRole, "admin");
|
|
25
|
+
function notify(kind, message) {
|
|
26
|
+
onNotice?.({ kind, message });
|
|
27
|
+
}
|
|
28
|
+
async function submitInvite() {
|
|
29
|
+
const email = inviteEmail.trim();
|
|
30
|
+
if (!email || inviting) return;
|
|
31
|
+
setInviting(true);
|
|
32
|
+
try {
|
|
33
|
+
const result = await onInvite({ email, role: inviteRole });
|
|
34
|
+
if (result && "inviteUrl" in result && result.inviteUrl) {
|
|
35
|
+
notify("success", `Invite link ready for ${email}`);
|
|
36
|
+
} else {
|
|
37
|
+
notify("success", `Invited ${email}`);
|
|
38
|
+
}
|
|
39
|
+
setInviteEmail("");
|
|
40
|
+
} catch (err) {
|
|
41
|
+
notify("error", err instanceof Error ? err.message : "Failed to invite");
|
|
42
|
+
} finally {
|
|
43
|
+
setInviting(false);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function changeRole(memberId, role) {
|
|
47
|
+
try {
|
|
48
|
+
await onChangeRole({ memberId, role });
|
|
49
|
+
notify("success", "Role updated");
|
|
50
|
+
} catch (err) {
|
|
51
|
+
notify("error", err instanceof Error ? err.message : "Failed to update role");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function remove(memberId) {
|
|
55
|
+
try {
|
|
56
|
+
await onRemove({ memberId });
|
|
57
|
+
notify("success", "Member removed");
|
|
58
|
+
} catch (err) {
|
|
59
|
+
notify("error", err instanceof Error ? err.message : "Failed to remove member");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-4", children: [
|
|
63
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
64
|
+
members.map((member) => /* @__PURE__ */ jsx(
|
|
65
|
+
MemberRow,
|
|
66
|
+
{
|
|
67
|
+
member,
|
|
68
|
+
canManage,
|
|
69
|
+
onChangeRole: changeRole,
|
|
70
|
+
onRemove: remove
|
|
71
|
+
},
|
|
72
|
+
member.id
|
|
73
|
+
)),
|
|
74
|
+
members.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-[var(--text-muted)]", children: "No team members yet." })
|
|
75
|
+
] }),
|
|
76
|
+
canManage && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 border-t border-[var(--border-default)] pt-3", children: [
|
|
77
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-[var(--text-muted)]", children: "Invite member" }),
|
|
78
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
79
|
+
/* @__PURE__ */ jsx(
|
|
80
|
+
"input",
|
|
81
|
+
{
|
|
82
|
+
type: "email",
|
|
83
|
+
placeholder: "Email address",
|
|
84
|
+
value: inviteEmail,
|
|
85
|
+
"aria-label": "Invite email address",
|
|
86
|
+
onChange: (event) => setInviteEmail(event.target.value),
|
|
87
|
+
onKeyDown: (event) => {
|
|
88
|
+
if (event.key === "Enter") void submitInvite();
|
|
89
|
+
},
|
|
90
|
+
className: "flex-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-muted)]"
|
|
91
|
+
}
|
|
92
|
+
),
|
|
93
|
+
/* @__PURE__ */ jsx(
|
|
94
|
+
"select",
|
|
95
|
+
{
|
|
96
|
+
value: inviteRole,
|
|
97
|
+
"aria-label": "Invite role",
|
|
98
|
+
onChange: (event) => setInviteRole(event.target.value),
|
|
99
|
+
className: "rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-1.5 text-xs text-[var(--text-secondary)]",
|
|
100
|
+
children: ASSIGNABLE.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
|
|
101
|
+
}
|
|
102
|
+
),
|
|
103
|
+
/* @__PURE__ */ jsx(
|
|
104
|
+
"button",
|
|
105
|
+
{
|
|
106
|
+
type: "button",
|
|
107
|
+
onClick: () => void submitInvite(),
|
|
108
|
+
disabled: inviting || !inviteEmail.trim(),
|
|
109
|
+
className: "rounded bg-[var(--brand-primary)] px-3 py-1.5 text-sm text-white disabled:opacity-50",
|
|
110
|
+
children: inviting ? "Inviting\u2026" : "Invite"
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
] })
|
|
114
|
+
] })
|
|
115
|
+
] });
|
|
116
|
+
}
|
|
117
|
+
function MemberRow({ member, canManage, onChangeRole, onRemove }) {
|
|
118
|
+
const pending = member.acceptedAt == null;
|
|
119
|
+
const label = member.name ?? member.email ?? "Unknown";
|
|
120
|
+
const initial = (member.name?.[0] ?? member.email?.[0] ?? "?").toUpperCase();
|
|
121
|
+
const isOwner = member.role === "owner";
|
|
122
|
+
const editable = canManage && !isOwner && !member.inherited;
|
|
123
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-[var(--border-default)] py-2 last:border-0", children: [
|
|
124
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
125
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-[var(--bg-input)] text-xs font-bold text-[var(--text-secondary)]", children: initial }),
|
|
126
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
127
|
+
/* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 text-sm font-medium text-[var(--text-primary)]", children: [
|
|
128
|
+
label,
|
|
129
|
+
pending && /* @__PURE__ */ jsx("span", { className: "rounded border border-[var(--border-default)] px-1.5 py-0.5 text-[10px] uppercase text-[var(--text-muted)]", children: "Pending" })
|
|
130
|
+
] }),
|
|
131
|
+
member.name && member.email && /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: member.email })
|
|
132
|
+
] })
|
|
133
|
+
] }),
|
|
134
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: isOwner ? /* @__PURE__ */ jsx("span", { className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-xs text-[var(--text-secondary)]", children: member.inherited ? "Org Admin" : "Owner" }) : editable ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
135
|
+
/* @__PURE__ */ jsx(
|
|
136
|
+
"select",
|
|
137
|
+
{
|
|
138
|
+
value: member.role,
|
|
139
|
+
"aria-label": `Role for ${label}`,
|
|
140
|
+
onChange: (event) => onChangeRole(member.id, event.target.value),
|
|
141
|
+
className: "rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-1 text-xs text-[var(--text-secondary)]",
|
|
142
|
+
children: ASSIGNABLE.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
/* @__PURE__ */ jsx(
|
|
146
|
+
"button",
|
|
147
|
+
{
|
|
148
|
+
type: "button",
|
|
149
|
+
"aria-label": `Remove ${label}`,
|
|
150
|
+
onClick: () => onRemove(member.id),
|
|
151
|
+
className: "rounded px-2 py-1 text-xs text-[var(--text-danger)] hover:bg-[var(--border-default)]",
|
|
152
|
+
children: "Remove"
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-xs capitalize text-[var(--text-secondary)]", children: member.role }) })
|
|
156
|
+
] });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export {
|
|
160
|
+
MembersPanel
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=chunk-3G334FVA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/teams-react/components/MembersPanel.tsx"],"sourcesContent":["/**\n * Workspace members panel: list members, invite by email + role, change a\n * member's role, remove a member. Fully callback-driven — the host supplies the\n * data and the async `onInvite`/`onChangeRole`/`onRemove` callbacks (backed by\n * `./teams/members-api`), so this imports no app router, fetch client, or toast.\n * Styled with the shipped Tangle Quiet tokens (`var(--*)`).\n *\n * Role gating mirrors the API: only admins/owners see role selects and the\n * remove control; inherited org owners and explicit owners are not editable\n * here (org-level concern). The invite role select offers `admin` only to\n * admins/owners.\n */\n\nimport { useState } from 'react'\nimport type { WorkspaceRole } from '../../teams/roles'\nimport { hasWorkspaceRole } from '../../teams/roles'\nimport type { MemberView, MembersPanelProps } from '../contracts'\n\nconst ASSIGNABLE: { value: WorkspaceRole; label: string }[] = [\n { value: 'viewer', label: 'Viewer' },\n { value: 'editor', label: 'Editor' },\n { value: 'admin', label: 'Admin' },\n]\n\nexport function MembersPanel({\n members,\n currentRole,\n onInvite,\n onChangeRole,\n onRemove,\n onNotice,\n}: MembersPanelProps) {\n const [inviteEmail, setInviteEmail] = useState('')\n const [inviteRole, setInviteRole] = useState<WorkspaceRole>('editor')\n const [inviting, setInviting] = useState(false)\n\n const canManage = hasWorkspaceRole(currentRole, 'admin')\n\n function notify(kind: 'success' | 'error', message: string) {\n onNotice?.({ kind, message })\n }\n\n async function submitInvite() {\n const email = inviteEmail.trim()\n if (!email || inviting) return\n setInviting(true)\n try {\n const result = await onInvite({ email, role: inviteRole })\n if (result && 'inviteUrl' in result && result.inviteUrl) {\n notify('success', `Invite link ready for ${email}`)\n } else {\n notify('success', `Invited ${email}`)\n }\n setInviteEmail('')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to invite')\n } finally {\n setInviting(false)\n }\n }\n\n async function changeRole(memberId: string, role: WorkspaceRole) {\n try {\n await onChangeRole({ memberId, role })\n notify('success', 'Role updated')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to update role')\n }\n }\n\n async function remove(memberId: string) {\n try {\n await onRemove({ memberId })\n notify('success', 'Member removed')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to remove member')\n }\n }\n\n return (\n <section className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2\">\n {members.map((member) => (\n <MemberRow\n key={member.id}\n member={member}\n canManage={canManage}\n onChangeRole={changeRole}\n onRemove={remove}\n />\n ))}\n {members.length === 0 && (\n <p className=\"text-sm text-[var(--text-muted)]\">No team members yet.</p>\n )}\n </div>\n\n {canManage && (\n <div className=\"flex flex-col gap-1.5 border-t border-[var(--border-default)] pt-3\">\n <label className=\"text-xs font-medium text-[var(--text-muted)]\">Invite member</label>\n <div className=\"flex gap-2\">\n <input\n type=\"email\"\n placeholder=\"Email address\"\n value={inviteEmail}\n aria-label=\"Invite email address\"\n onChange={(event) => setInviteEmail(event.target.value)}\n onKeyDown={(event) => { if (event.key === 'Enter') void submitInvite() }}\n className=\"flex-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-muted)]\"\n />\n <select\n value={inviteRole}\n aria-label=\"Invite role\"\n onChange={(event) => setInviteRole(event.target.value as WorkspaceRole)}\n className=\"rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-1.5 text-xs text-[var(--text-secondary)]\"\n >\n {ASSIGNABLE.map((option) => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => void submitInvite()}\n disabled={inviting || !inviteEmail.trim()}\n className=\"rounded bg-[var(--brand-primary)] px-3 py-1.5 text-sm text-white disabled:opacity-50\"\n >\n {inviting ? 'Inviting…' : 'Invite'}\n </button>\n </div>\n </div>\n )}\n </section>\n )\n}\n\ninterface MemberRowProps {\n member: MemberView\n canManage: boolean\n onChangeRole(memberId: string, role: WorkspaceRole): void\n onRemove(memberId: string): void\n}\n\nfunction MemberRow({ member, canManage, onChangeRole, onRemove }: MemberRowProps) {\n const pending = member.acceptedAt == null\n const label = member.name ?? member.email ?? 'Unknown'\n const initial = (member.name?.[0] ?? member.email?.[0] ?? '?').toUpperCase()\n const isOwner = member.role === 'owner'\n const editable = canManage && !isOwner && !member.inherited\n\n return (\n <div className=\"flex items-center justify-between border-b border-[var(--border-default)] py-2 last:border-0\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-8 w-8 items-center justify-center rounded-full bg-[var(--bg-input)] text-xs font-bold text-[var(--text-secondary)]\">\n {initial}\n </div>\n <div>\n <p className=\"flex items-center gap-2 text-sm font-medium text-[var(--text-primary)]\">\n {label}\n {pending && (\n <span className=\"rounded border border-[var(--border-default)] px-1.5 py-0.5 text-[10px] uppercase text-[var(--text-muted)]\">\n Pending\n </span>\n )}\n </p>\n {member.name && member.email && (\n <p className=\"text-xs text-[var(--text-muted)]\">{member.email}</p>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {isOwner ? (\n <span className=\"rounded border border-[var(--border-default)] px-2 py-0.5 text-xs text-[var(--text-secondary)]\">\n {member.inherited ? 'Org Admin' : 'Owner'}\n </span>\n ) : editable ? (\n <>\n <select\n value={member.role}\n aria-label={`Role for ${label}`}\n onChange={(event) => onChangeRole(member.id, event.target.value as WorkspaceRole)}\n className=\"rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-1 text-xs text-[var(--text-secondary)]\"\n >\n {ASSIGNABLE.map((option) => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\n </select>\n <button\n type=\"button\"\n aria-label={`Remove ${label}`}\n onClick={() => onRemove(member.id)}\n className=\"rounded px-2 py-1 text-xs text-[var(--text-danger)] hover:bg-[var(--border-default)]\"\n >\n Remove\n </button>\n </>\n ) : (\n <span className=\"rounded border border-[var(--border-default)] px-2 py-0.5 text-xs capitalize text-[var(--text-secondary)]\">\n {member.role}\n </span>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/teams/invite.ts
|
|
2
|
+
var INVITE_TOKEN_BYTES = 24;
|
|
3
|
+
var INVITE_TOKEN_PATTERN = /^[A-Za-z0-9_-]{16,}$/;
|
|
4
|
+
function generateInviteToken() {
|
|
5
|
+
const bytes = new Uint8Array(INVITE_TOKEN_BYTES);
|
|
6
|
+
crypto.getRandomValues(bytes);
|
|
7
|
+
return base64UrlEncode(bytes);
|
|
8
|
+
}
|
|
9
|
+
function isInviteTokenShape(value) {
|
|
10
|
+
return typeof value === "string" && INVITE_TOKEN_PATTERN.test(value);
|
|
11
|
+
}
|
|
12
|
+
function validateInviteToken(invite, opts = {}) {
|
|
13
|
+
if (invite.acceptedAt != null) return { ok: false, reason: "already-accepted" };
|
|
14
|
+
if (invite.expiresAt != null) {
|
|
15
|
+
const now = (opts.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
16
|
+
const expires = invite.expiresAt instanceof Date ? invite.expiresAt.getTime() : Number(invite.expiresAt);
|
|
17
|
+
if (Number.isFinite(expires) && now >= expires) return { ok: false, reason: "expired" };
|
|
18
|
+
}
|
|
19
|
+
if (invite.inviteEmail && opts.acceptingEmail) {
|
|
20
|
+
if (invite.inviteEmail.trim().toLowerCase() !== opts.acceptingEmail.trim().toLowerCase()) {
|
|
21
|
+
return { ok: false, reason: "email-mismatch" };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { ok: true };
|
|
25
|
+
}
|
|
26
|
+
function base64UrlEncode(bytes) {
|
|
27
|
+
let binary = "";
|
|
28
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
29
|
+
const base64 = typeof btoa === "function" ? btoa(binary) : bufferToBase64(binary);
|
|
30
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
31
|
+
}
|
|
32
|
+
function bufferToBase64(binary) {
|
|
33
|
+
return Buffer.from(binary, "binary").toString("base64");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
generateInviteToken,
|
|
38
|
+
isInviteTokenShape,
|
|
39
|
+
validateInviteToken
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=chunk-3SVAA3MA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/teams/invite.ts"],"sourcesContent":["/**\n * Pure invite-token helpers — generation, shape validation, and expiry math.\n * No I/O: the members API persists/looks up tokens; these functions only\n * produce well-formed tokens and decide, given values the caller already\n * loaded, whether an invite is usable.\n *\n * A token is an opaque high-entropy URL-safe string. It is the bearer secret\n * in `/invite/:token`, so it must be unguessable and never derived from the\n * email or workspace. `generateInviteToken` uses Web Crypto (`crypto`), which\n * is present in Workers, Node 18+, Deno, and browsers — no Node-only import,\n * so this stays a pure leaf.\n */\n\nconst INVITE_TOKEN_BYTES = 24\nconst INVITE_TOKEN_PATTERN = /^[A-Za-z0-9_-]{16,}$/\n\n/** Cryptographically-random, URL-safe (base64url) invite token. */\nexport function generateInviteToken(): string {\n const bytes = new Uint8Array(INVITE_TOKEN_BYTES)\n crypto.getRandomValues(bytes)\n return base64UrlEncode(bytes)\n}\n\n/** True when `value` has the shape of an invite token (not whether it exists). */\nexport function isInviteTokenShape(value: unknown): value is string {\n return typeof value === 'string' && INVITE_TOKEN_PATTERN.test(value)\n}\n\n/** A pending invite row, narrowed to the fields invite acceptance reasons over. */\nexport interface InviteTokenState {\n /** null until accepted — a non-null value means the invite was already used. */\n acceptedAt: Date | number | null | undefined\n /** Email the invite was addressed to, if any. */\n inviteEmail?: string | null\n /** Optional hard expiry; omit/undefined for invites that never expire. */\n expiresAt?: Date | number | null\n}\n\nexport type InviteRejectionReason = 'already-accepted' | 'expired' | 'email-mismatch'\n\nexport interface InviteValidationResult {\n ok: boolean\n reason?: InviteRejectionReason\n}\n\n/**\n * Decide whether a loaded invite can be accepted by `acceptingEmail` at `now`.\n * Pure: the caller has already fetched the row by token; this only judges it.\n * Email match is case-insensitive and only enforced when the invite was\n * addressed to a specific email (an open invite has no `inviteEmail`).\n */\nexport function validateInviteToken(\n invite: InviteTokenState,\n opts: { acceptingEmail?: string | null; now?: Date } = {},\n): InviteValidationResult {\n if (invite.acceptedAt != null) return { ok: false, reason: 'already-accepted' }\n\n if (invite.expiresAt != null) {\n const now = (opts.now ?? new Date()).getTime()\n const expires = invite.expiresAt instanceof Date ? invite.expiresAt.getTime() : Number(invite.expiresAt)\n if (Number.isFinite(expires) && now >= expires) return { ok: false, reason: 'expired' }\n }\n\n if (invite.inviteEmail && opts.acceptingEmail) {\n if (invite.inviteEmail.trim().toLowerCase() !== opts.acceptingEmail.trim().toLowerCase()) {\n return { ok: false, reason: 'email-mismatch' }\n }\n }\n\n return { ok: true }\n}\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = ''\n for (const byte of bytes) binary += String.fromCharCode(byte)\n const base64 = typeof btoa === 'function' ? btoa(binary) : bufferToBase64(binary)\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction bufferToBase64(binary: string): string {\n // Node without a global `btoa` (older runtimes); Buffer is always present there.\n return Buffer.from(binary, 'binary').toString('base64')\n}\n"],"mappings":";AAaA,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAGtB,SAAS,sBAA8B;AAC5C,QAAM,QAAQ,IAAI,WAAW,kBAAkB;AAC/C,SAAO,gBAAgB,KAAK;AAC5B,SAAO,gBAAgB,KAAK;AAC9B;AAGO,SAAS,mBAAmB,OAAiC;AAClE,SAAO,OAAO,UAAU,YAAY,qBAAqB,KAAK,KAAK;AACrE;AAyBO,SAAS,oBACd,QACA,OAAuD,CAAC,GAChC;AACxB,MAAI,OAAO,cAAc,KAAM,QAAO,EAAE,IAAI,OAAO,QAAQ,mBAAmB;AAE9E,MAAI,OAAO,aAAa,MAAM;AAC5B,UAAM,OAAO,KAAK,OAAO,oBAAI,KAAK,GAAG,QAAQ;AAC7C,UAAM,UAAU,OAAO,qBAAqB,OAAO,OAAO,UAAU,QAAQ,IAAI,OAAO,OAAO,SAAS;AACvG,QAAI,OAAO,SAAS,OAAO,KAAK,OAAO,QAAS,QAAO,EAAE,IAAI,OAAO,QAAQ,UAAU;AAAA,EACxF;AAEA,MAAI,OAAO,eAAe,KAAK,gBAAgB;AAC7C,QAAI,OAAO,YAAY,KAAK,EAAE,YAAY,MAAM,KAAK,eAAe,KAAK,EAAE,YAAY,GAAG;AACxF,aAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAAA,IAC/C;AAAA,EACF;AAEA,SAAO,EAAE,IAAI,KAAK;AACpB;AAEA,SAAS,gBAAgB,OAA2B;AAClD,MAAI,SAAS;AACb,aAAW,QAAQ,MAAO,WAAU,OAAO,aAAa,IAAI;AAC5D,QAAM,SAAS,OAAO,SAAS,aAAa,KAAK,MAAM,IAAI,eAAe,MAAM;AAChF,SAAO,OAAO,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AACzE;AAEA,SAAS,eAAe,QAAwB;AAE9C,SAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,QAAQ;AACxD;","names":[]}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// src/teams-react/components/InviteAcceptPage.tsx
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
function InviteAcceptPage({ details, onAccept, onNavigate }) {
|
|
5
|
+
const [accepting, setAccepting] = useState(false);
|
|
6
|
+
const [acceptError, setAcceptError] = useState(null);
|
|
7
|
+
const [accepted, setAccepted] = useState(false);
|
|
8
|
+
if (details.status === "invalid") {
|
|
9
|
+
return /* @__PURE__ */ jsx(Shell, { title: "Invalid invite", body: "This invite link is invalid or has expired.", children: /* @__PURE__ */ jsx(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Go to sign in" }) });
|
|
10
|
+
}
|
|
11
|
+
if (details.status === "already-accepted") {
|
|
12
|
+
return /* @__PURE__ */ jsx(Shell, { title: "Already accepted", body: "This invite has already been accepted.", children: /* @__PURE__ */ jsx(PrimaryButton, { onClick: () => onNavigate({ kind: "open-app" }), children: "Open workspace" }) });
|
|
13
|
+
}
|
|
14
|
+
const workspaceName = details.workspaceName ?? "a workspace";
|
|
15
|
+
const inviterPrefix = details.inviterName ? `${details.inviterName} invited you` : "You've been invited";
|
|
16
|
+
const roleSuffix = details.role ? ` as ${details.role}` : "";
|
|
17
|
+
if (!details.currentUserEmail) {
|
|
18
|
+
return /* @__PURE__ */ jsxs(Shell, { title: "You've been invited", children: [
|
|
19
|
+
/* @__PURE__ */ jsxs("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
|
|
20
|
+
inviterPrefix,
|
|
21
|
+
" to join ",
|
|
22
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
|
|
23
|
+
roleSuffix,
|
|
24
|
+
"."
|
|
25
|
+
] }),
|
|
26
|
+
details.inviteEmail && /* @__PURE__ */ jsxs("p", { className: "mb-6 text-sm text-[var(--text-secondary)]", children: [
|
|
27
|
+
"Sign in or create an account with",
|
|
28
|
+
" ",
|
|
29
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: details.inviteEmail }),
|
|
30
|
+
" to accept."
|
|
31
|
+
] }),
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
33
|
+
/* @__PURE__ */ jsx(PrimaryButton, { onClick: () => onNavigate({ kind: "sign-in" }), children: "Sign in" }),
|
|
34
|
+
/* @__PURE__ */ jsx(SecondaryButton, { onClick: () => onNavigate({ kind: "sign-up" }), children: "Create account" })
|
|
35
|
+
] })
|
|
36
|
+
] });
|
|
37
|
+
}
|
|
38
|
+
if (accepted) {
|
|
39
|
+
return /* @__PURE__ */ jsx(Shell, { title: "Welcome!", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-[var(--text-secondary)]", children: [
|
|
40
|
+
"You've joined ",
|
|
41
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
|
|
42
|
+
"."
|
|
43
|
+
] }) });
|
|
44
|
+
}
|
|
45
|
+
const emailMismatch = Boolean(
|
|
46
|
+
details.inviteEmail && details.inviteEmail.toLowerCase() !== details.currentUserEmail.toLowerCase()
|
|
47
|
+
);
|
|
48
|
+
async function handleAccept() {
|
|
49
|
+
setAccepting(true);
|
|
50
|
+
setAcceptError(null);
|
|
51
|
+
try {
|
|
52
|
+
const result = await onAccept();
|
|
53
|
+
setAccepted(true);
|
|
54
|
+
if (result && "workspaceId" in result && result.workspaceId) {
|
|
55
|
+
onNavigate({ kind: "open-app", workspaceId: result.workspaceId });
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
setAcceptError(err instanceof Error ? err.message : "Failed to accept invite");
|
|
59
|
+
} finally {
|
|
60
|
+
setAccepting(false);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return /* @__PURE__ */ jsxs(Shell, { title: "Join workspace", children: [
|
|
64
|
+
/* @__PURE__ */ jsxs("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
|
|
65
|
+
inviterPrefix,
|
|
66
|
+
" to join ",
|
|
67
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: workspaceName }),
|
|
68
|
+
roleSuffix,
|
|
69
|
+
"."
|
|
70
|
+
] }),
|
|
71
|
+
/* @__PURE__ */ jsxs("p", { className: "mb-4 text-sm text-[var(--text-secondary)]", children: [
|
|
72
|
+
"Signed in as ",
|
|
73
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: details.currentUserEmail })
|
|
74
|
+
] }),
|
|
75
|
+
emailMismatch && /* @__PURE__ */ jsxs(
|
|
76
|
+
"div",
|
|
77
|
+
{
|
|
78
|
+
role: "alert",
|
|
79
|
+
className: "mb-4 rounded-md border border-[var(--border-default)] px-4 py-2 text-sm text-[var(--text-warning)]",
|
|
80
|
+
children: [
|
|
81
|
+
"This invite was sent to ",
|
|
82
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: details.inviteEmail }),
|
|
83
|
+
". Switch to that account to accept it."
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
acceptError && /* @__PURE__ */ jsx("p", { role: "alert", className: "mb-4 text-sm text-[var(--text-danger)]", children: acceptError }),
|
|
88
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
89
|
+
emailMismatch ? /* @__PURE__ */ jsx(PrimaryButton, { onClick: () => onNavigate({ kind: "switch-account" }), children: "Switch account" }) : /* @__PURE__ */ jsx(PrimaryButton, { onClick: () => void handleAccept(), disabled: accepting, children: accepting ? "Accepting\u2026" : "Accept invite" }),
|
|
90
|
+
/* @__PURE__ */ jsx(SecondaryButton, { onClick: () => onNavigate({ kind: "open-app" }), children: "Not now" })
|
|
91
|
+
] })
|
|
92
|
+
] });
|
|
93
|
+
}
|
|
94
|
+
function Shell({ title, body, children }) {
|
|
95
|
+
return /* @__PURE__ */ jsxs("div", { className: "mx-auto flex w-full max-w-sm flex-col", children: [
|
|
96
|
+
/* @__PURE__ */ jsx("h1", { className: "mb-1 text-xl font-semibold tracking-tight text-[var(--text-primary)]", children: title }),
|
|
97
|
+
body && /* @__PURE__ */ jsx("p", { className: "mb-6 text-sm text-[var(--text-secondary)]", children: body }),
|
|
98
|
+
children
|
|
99
|
+
] });
|
|
100
|
+
}
|
|
101
|
+
function PrimaryButton({ children, onClick, disabled }) {
|
|
102
|
+
return /* @__PURE__ */ jsx(
|
|
103
|
+
"button",
|
|
104
|
+
{
|
|
105
|
+
type: "button",
|
|
106
|
+
onClick,
|
|
107
|
+
disabled,
|
|
108
|
+
className: "flex-1 rounded bg-[var(--brand-primary)] px-4 py-2 text-sm text-white disabled:opacity-50",
|
|
109
|
+
children
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
function SecondaryButton({ children, onClick }) {
|
|
114
|
+
return /* @__PURE__ */ jsx(
|
|
115
|
+
"button",
|
|
116
|
+
{
|
|
117
|
+
type: "button",
|
|
118
|
+
onClick,
|
|
119
|
+
className: "flex-1 rounded border border-[var(--border-default)] px-4 py-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)]",
|
|
120
|
+
children
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
InviteAcceptPage
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=chunk-535V6BH6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/teams-react/components/InviteAcceptPage.tsx"],"sourcesContent":["/**\n * Invite-accept surface for `/invite/:token`. Renders the invite state (valid /\n * invalid / already-accepted), handles the signed-out path (sign in or create\n * an account with the invited email), the email-mismatch path (switch account),\n * and the accept action. Callback-driven: the host supplies the resolved\n * `details` and `onAccept` / `onNavigate`, so this imports no app router or\n * fetch client. Styled with the shipped Tangle Quiet tokens (`var(--*)`).\n */\n\nimport { useState } from 'react'\nimport type { InviteAcceptPageProps } from '../contracts'\n\nexport function InviteAcceptPage({ details, onAccept, onNavigate }: InviteAcceptPageProps) {\n const [accepting, setAccepting] = useState(false)\n const [acceptError, setAcceptError] = useState<string | null>(null)\n const [accepted, setAccepted] = useState(false)\n\n if (details.status === 'invalid') {\n return (\n <Shell title=\"Invalid invite\" body=\"This invite link is invalid or has expired.\">\n <PrimaryButton onClick={() => onNavigate({ kind: 'sign-in' })}>Go to sign in</PrimaryButton>\n </Shell>\n )\n }\n\n if (details.status === 'already-accepted') {\n return (\n <Shell title=\"Already accepted\" body=\"This invite has already been accepted.\">\n <PrimaryButton onClick={() => onNavigate({ kind: 'open-app' })}>Open workspace</PrimaryButton>\n </Shell>\n )\n }\n\n const workspaceName = details.workspaceName ?? 'a workspace'\n const inviterPrefix = details.inviterName ? `${details.inviterName} invited you` : \"You've been invited\"\n const roleSuffix = details.role ? ` as ${details.role}` : ''\n\n if (!details.currentUserEmail) {\n return (\n <Shell title=\"You've been invited\">\n <p className=\"mb-4 text-sm text-[var(--text-secondary)]\">\n {inviterPrefix} to join <span className=\"font-medium text-[var(--text-primary)]\">{workspaceName}</span>{roleSuffix}.\n </p>\n {details.inviteEmail && (\n <p className=\"mb-6 text-sm text-[var(--text-secondary)]\">\n Sign in or create an account with{' '}\n <span className=\"font-medium text-[var(--text-primary)]\">{details.inviteEmail}</span> to accept.\n </p>\n )}\n <div className=\"flex gap-2\">\n <PrimaryButton onClick={() => onNavigate({ kind: 'sign-in' })}>Sign in</PrimaryButton>\n <SecondaryButton onClick={() => onNavigate({ kind: 'sign-up' })}>Create account</SecondaryButton>\n </div>\n </Shell>\n )\n }\n\n if (accepted) {\n return (\n <Shell title=\"Welcome!\">\n <p className=\"text-sm text-[var(--text-secondary)]\">\n You've joined <span className=\"font-medium text-[var(--text-primary)]\">{workspaceName}</span>.\n </p>\n </Shell>\n )\n }\n\n const emailMismatch = Boolean(\n details.inviteEmail &&\n details.inviteEmail.toLowerCase() !== details.currentUserEmail.toLowerCase(),\n )\n\n async function handleAccept() {\n setAccepting(true)\n setAcceptError(null)\n try {\n const result = await onAccept()\n setAccepted(true)\n if (result && 'workspaceId' in result && result.workspaceId) {\n onNavigate({ kind: 'open-app', workspaceId: result.workspaceId })\n }\n } catch (err) {\n setAcceptError(err instanceof Error ? err.message : 'Failed to accept invite')\n } finally {\n setAccepting(false)\n }\n }\n\n return (\n <Shell title=\"Join workspace\">\n <p className=\"mb-4 text-sm text-[var(--text-secondary)]\">\n {inviterPrefix} to join <span className=\"font-medium text-[var(--text-primary)]\">{workspaceName}</span>{roleSuffix}.\n </p>\n <p className=\"mb-4 text-sm text-[var(--text-secondary)]\">\n Signed in as <span className=\"font-medium text-[var(--text-primary)]\">{details.currentUserEmail}</span>\n </p>\n {emailMismatch && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-md border border-[var(--border-default)] px-4 py-2 text-sm text-[var(--text-warning)]\"\n >\n This invite was sent to <span className=\"font-medium\">{details.inviteEmail}</span>. Switch to that account to accept it.\n </div>\n )}\n {acceptError && (\n <p role=\"alert\" className=\"mb-4 text-sm text-[var(--text-danger)]\">{acceptError}</p>\n )}\n <div className=\"flex gap-2\">\n {emailMismatch ? (\n <PrimaryButton onClick={() => onNavigate({ kind: 'switch-account' })}>Switch account</PrimaryButton>\n ) : (\n <PrimaryButton onClick={() => void handleAccept()} disabled={accepting}>\n {accepting ? 'Accepting…' : 'Accept invite'}\n </PrimaryButton>\n )}\n <SecondaryButton onClick={() => onNavigate({ kind: 'open-app' })}>Not now</SecondaryButton>\n </div>\n </Shell>\n )\n}\n\nfunction Shell({ title, body, children }: { title: string; body?: string; children: React.ReactNode }) {\n return (\n <div className=\"mx-auto flex w-full max-w-sm flex-col\">\n <h1 className=\"mb-1 text-xl font-semibold tracking-tight text-[var(--text-primary)]\">{title}</h1>\n {body && <p className=\"mb-6 text-sm text-[var(--text-secondary)]\">{body}</p>}\n {children}\n </div>\n )\n}\n\nfunction PrimaryButton({ children, onClick, disabled }: { children: React.ReactNode; onClick(): void; disabled?: boolean }) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n className=\"flex-1 rounded bg-[var(--brand-primary)] px-4 py-2 text-sm text-white disabled:opacity-50\"\n >\n {children}\n </button>\n )\n}\n\nfunction SecondaryButton({ children, onClick }: { children: React.ReactNode; onClick(): void }) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className=\"flex-1 rounded border border-[var(--border-default)] px-4 py-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)]\"\n >\n {children}\n </button>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/teams/roles.ts
|
|
2
|
+
var WORKSPACE_ROLES = ["viewer", "editor", "admin", "owner"];
|
|
3
|
+
var ASSIGNABLE_WORKSPACE_ROLES = ["viewer", "editor", "admin"];
|
|
4
|
+
var ORGANIZATION_ROLES = ["owner", "admin", "member", "billing"];
|
|
5
|
+
var WORKSPACE_ROLE_RANK = {
|
|
6
|
+
viewer: 0,
|
|
7
|
+
editor: 1,
|
|
8
|
+
admin: 2,
|
|
9
|
+
owner: 3
|
|
10
|
+
};
|
|
11
|
+
var ORGANIZATION_ROLE_RANK = {
|
|
12
|
+
member: 0,
|
|
13
|
+
billing: 1,
|
|
14
|
+
admin: 2,
|
|
15
|
+
owner: 3
|
|
16
|
+
};
|
|
17
|
+
function hasWorkspaceRole(actual, minimum) {
|
|
18
|
+
return WORKSPACE_ROLE_RANK[actual] >= WORKSPACE_ROLE_RANK[minimum];
|
|
19
|
+
}
|
|
20
|
+
function hasOrganizationRole(actual, minimum) {
|
|
21
|
+
return ORGANIZATION_ROLE_RANK[actual] >= ORGANIZATION_ROLE_RANK[minimum];
|
|
22
|
+
}
|
|
23
|
+
function isAssignableWorkspaceRole(value) {
|
|
24
|
+
return typeof value === "string" && ASSIGNABLE_WORKSPACE_ROLES.includes(value);
|
|
25
|
+
}
|
|
26
|
+
function organizationRoleGrantsWorkspaceOwner(role) {
|
|
27
|
+
return role === "owner" || role === "admin";
|
|
28
|
+
}
|
|
29
|
+
function resolveWorkspaceRole(organizationRole, workspaceRole) {
|
|
30
|
+
return organizationRoleGrantsWorkspaceOwner(organizationRole) ? "owner" : workspaceRole ?? null;
|
|
31
|
+
}
|
|
32
|
+
function canManageWorkspaceMemberRole(actorRole, targetRole) {
|
|
33
|
+
return actorRole === "owner" || !hasWorkspaceRole(targetRole, actorRole);
|
|
34
|
+
}
|
|
35
|
+
function workspaceRoleToCollaborationAccess(role) {
|
|
36
|
+
return role === "viewer" ? "read" : "write";
|
|
37
|
+
}
|
|
38
|
+
function workspaceRoleToSandboxRole(role) {
|
|
39
|
+
const mapping = {
|
|
40
|
+
owner: "owner",
|
|
41
|
+
admin: "admin",
|
|
42
|
+
editor: "developer",
|
|
43
|
+
viewer: "viewer"
|
|
44
|
+
};
|
|
45
|
+
return mapping[role];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
WORKSPACE_ROLES,
|
|
50
|
+
ASSIGNABLE_WORKSPACE_ROLES,
|
|
51
|
+
ORGANIZATION_ROLES,
|
|
52
|
+
WORKSPACE_ROLE_RANK,
|
|
53
|
+
ORGANIZATION_ROLE_RANK,
|
|
54
|
+
hasWorkspaceRole,
|
|
55
|
+
hasOrganizationRole,
|
|
56
|
+
isAssignableWorkspaceRole,
|
|
57
|
+
organizationRoleGrantsWorkspaceOwner,
|
|
58
|
+
resolveWorkspaceRole,
|
|
59
|
+
canManageWorkspaceMemberRole,
|
|
60
|
+
workspaceRoleToCollaborationAccess,
|
|
61
|
+
workspaceRoleToSandboxRole
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=chunk-63CE7FEZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/teams/roles.ts"],"sourcesContent":["/**\n * Pure role algebra for the teams capability — the tenancy/membership model\n * shared across the fleet. Zero dependencies: no drizzle, no env, no react, no\n * I/O. The DB layer (`./teams/drizzle`), the members API (`./teams/members-api`)\n * and the React surface (`./teams-react`) all build on these functions; this\n * leaf imports nothing back, so a consumer can pull just the role math.\n *\n * Two role ladders, deliberately distinct:\n * - Organization roles rank the tenant (who owns/administers the org and its\n * billing). An org owner/admin is an owner of every workspace under it.\n * - Workspace roles rank a single workspace. They are the access primitive\n * every route checks via `hasWorkspaceRole(actual, minimum)`.\n *\n * `resolveWorkspaceRole` is the bridge: it folds the org role and the\n * per-workspace role into the one effective workspace role a request runs at.\n */\n\nexport const WORKSPACE_ROLES = ['viewer', 'editor', 'admin', 'owner'] as const\nexport type WorkspaceRole = typeof WORKSPACE_ROLES[number]\n\nexport const ASSIGNABLE_WORKSPACE_ROLES = ['viewer', 'editor', 'admin'] as const\nexport type AssignableWorkspaceRole = typeof ASSIGNABLE_WORKSPACE_ROLES[number]\n\nexport const ORGANIZATION_ROLES = ['owner', 'admin', 'member', 'billing'] as const\nexport type OrganizationRole = typeof ORGANIZATION_ROLES[number]\n\nexport type WorkspaceCollaborationAccess = 'read' | 'write'\nexport type SandboxWorkspaceRole = 'owner' | 'admin' | 'developer' | 'viewer'\n\nexport const WORKSPACE_ROLE_RANK: Record<WorkspaceRole, number> = {\n viewer: 0,\n editor: 1,\n admin: 2,\n owner: 3,\n}\n\nexport const ORGANIZATION_ROLE_RANK: Record<OrganizationRole, number> = {\n member: 0,\n billing: 1,\n admin: 2,\n owner: 3,\n}\n\n/** True when `actual` is at least `minimum` on the workspace ladder. */\nexport function hasWorkspaceRole(actual: WorkspaceRole, minimum: WorkspaceRole): boolean {\n return WORKSPACE_ROLE_RANK[actual] >= WORKSPACE_ROLE_RANK[minimum]\n}\n\n/** True when `actual` is at least `minimum` on the organization ladder. */\nexport function hasOrganizationRole(actual: OrganizationRole, minimum: OrganizationRole): boolean {\n return ORGANIZATION_ROLE_RANK[actual] >= ORGANIZATION_ROLE_RANK[minimum]\n}\n\nexport function isAssignableWorkspaceRole(value: unknown): value is AssignableWorkspaceRole {\n return typeof value === 'string' && ASSIGNABLE_WORKSPACE_ROLES.includes(value as AssignableWorkspaceRole)\n}\n\n/** Org owners and admins are workspace owners across the whole org. */\nexport function organizationRoleGrantsWorkspaceOwner(role: OrganizationRole | string | null | undefined): boolean {\n return role === 'owner' || role === 'admin'\n}\n\n/**\n * The effective workspace role a request runs at: org owner/admin → owner of\n * every workspace; otherwise the explicit per-workspace role (or null = no\n * access). This is the single fold every access check goes through.\n */\nexport function resolveWorkspaceRole(\n organizationRole: OrganizationRole | string | null | undefined,\n workspaceRole: WorkspaceRole | null | undefined,\n): WorkspaceRole | null {\n return organizationRoleGrantsWorkspaceOwner(organizationRole) ? 'owner' : workspaceRole ?? null\n}\n\n/**\n * Whether `actorRole` may set/clear a member currently at `targetRole`. Owners\n * can manage anyone; everyone else can only manage members strictly below\n * their own rank (an admin cannot demote another admin or an owner).\n */\nexport function canManageWorkspaceMemberRole(actorRole: WorkspaceRole, targetRole: WorkspaceRole): boolean {\n return actorRole === 'owner' || !hasWorkspaceRole(targetRole, actorRole)\n}\n\nexport function workspaceRoleToCollaborationAccess(role: WorkspaceRole): WorkspaceCollaborationAccess {\n return role === 'viewer' ? 'read' : 'write'\n}\n\nexport function workspaceRoleToSandboxRole(role: WorkspaceRole): SandboxWorkspaceRole {\n const mapping: Record<WorkspaceRole, SandboxWorkspaceRole> = {\n owner: 'owner',\n admin: 'admin',\n editor: 'developer',\n viewer: 'viewer',\n }\n return mapping[role]\n}\n"],"mappings":";AAiBO,IAAM,kBAAkB,CAAC,UAAU,UAAU,SAAS,OAAO;AAG7D,IAAM,6BAA6B,CAAC,UAAU,UAAU,OAAO;AAG/D,IAAM,qBAAqB,CAAC,SAAS,SAAS,UAAU,SAAS;AAMjE,IAAM,sBAAqD;AAAA,EAChE,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AACT;AAEO,IAAM,yBAA2D;AAAA,EACtE,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,OAAO;AACT;AAGO,SAAS,iBAAiB,QAAuB,SAAiC;AACvF,SAAO,oBAAoB,MAAM,KAAK,oBAAoB,OAAO;AACnE;AAGO,SAAS,oBAAoB,QAA0B,SAAoC;AAChG,SAAO,uBAAuB,MAAM,KAAK,uBAAuB,OAAO;AACzE;AAEO,SAAS,0BAA0B,OAAkD;AAC1F,SAAO,OAAO,UAAU,YAAY,2BAA2B,SAAS,KAAgC;AAC1G;AAGO,SAAS,qCAAqC,MAA6D;AAChH,SAAO,SAAS,WAAW,SAAS;AACtC;AAOO,SAAS,qBACd,kBACA,eACsB;AACtB,SAAO,qCAAqC,gBAAgB,IAAI,UAAU,iBAAiB;AAC7F;AAOO,SAAS,6BAA6B,WAA0B,YAAoC;AACzG,SAAO,cAAc,WAAW,CAAC,iBAAiB,YAAY,SAAS;AACzE;AAEO,SAAS,mCAAmC,MAAmD;AACpG,SAAO,SAAS,WAAW,SAAS;AACtC;AAEO,SAAS,2BAA2B,MAA2C;AACpF,QAAM,UAAuD;AAAA,IAC3D,OAAO;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACA,SAAO,QAAQ,IAAI;AACrB;","names":[]}
|
|
@@ -22,15 +22,15 @@ import {
|
|
|
22
22
|
setAttrsCommand,
|
|
23
23
|
ungroupElementCommand
|
|
24
24
|
} from "./chunk-XWJXZ6WE.js";
|
|
25
|
+
import {
|
|
26
|
+
lightTheme
|
|
27
|
+
} from "./chunk-Y2SZVW35.js";
|
|
25
28
|
import {
|
|
26
29
|
SCENE_SCHEMA_VERSION,
|
|
27
30
|
boundsIntersect,
|
|
28
31
|
elementAabb,
|
|
29
32
|
findElement
|
|
30
33
|
} from "./chunk-M2EBUDZ7.js";
|
|
31
|
-
import {
|
|
32
|
-
lightTheme
|
|
33
|
-
} from "./chunk-Y2SZVW35.js";
|
|
34
34
|
|
|
35
35
|
// src/design-canvas-react/components/Workspace.tsx
|
|
36
36
|
import {
|
|
@@ -1772,4 +1772,4 @@ export {
|
|
|
1772
1772
|
Workspace,
|
|
1773
1773
|
DesignCanvasEditor
|
|
1774
1774
|
};
|
|
1775
|
-
//# sourceMappingURL=chunk-
|
|
1775
|
+
//# sourceMappingURL=chunk-N4ZFKQ5C.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/design-canvas-react/lazy.tsx
|
|
2
2
|
import { lazy } from "react";
|
|
3
3
|
var DesignCanvasLazy = lazy(
|
|
4
|
-
() => import("./DesignCanvasEditor-
|
|
4
|
+
() => import("./DesignCanvasEditor-IB2FMBBD.js").then((m) => ({ default: m.DesignCanvasEditor }))
|
|
5
5
|
);
|
|
6
6
|
var DesignCanvasChromeLazy = lazy(
|
|
7
7
|
() => import("./DesignCanvas-AOIVN3SJ.js").then((m) => ({ default: m.DesignCanvas }))
|
|
@@ -11,4 +11,4 @@ export {
|
|
|
11
11
|
DesignCanvasLazy,
|
|
12
12
|
DesignCanvasChromeLazy
|
|
13
13
|
};
|
|
14
|
-
//# sourceMappingURL=chunk-
|
|
14
|
+
//# sourceMappingURL=chunk-S5CAJX6O.js.map
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
downloadDataUrl,
|
|
12
12
|
exportDocumentJson,
|
|
13
13
|
exportPageDataUrl
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-N4ZFKQ5C.js";
|
|
15
15
|
import {
|
|
16
16
|
BTN,
|
|
17
17
|
BTN_ACTIVE,
|
|
@@ -85,13 +85,13 @@ import {
|
|
|
85
85
|
import {
|
|
86
86
|
DesignCanvasChromeLazy,
|
|
87
87
|
DesignCanvasLazy
|
|
88
|
-
} from "../chunk-
|
|
88
|
+
} from "../chunk-S5CAJX6O.js";
|
|
89
|
+
import "../chunk-Y2SZVW35.js";
|
|
89
90
|
import {
|
|
90
91
|
assertSceneMediaSrc,
|
|
91
92
|
bleedAwareExportBounds,
|
|
92
93
|
scaleForPreset
|
|
93
94
|
} from "../chunk-M2EBUDZ7.js";
|
|
94
|
-
import "../chunk-Y2SZVW35.js";
|
|
95
95
|
import "../chunk-HFC4BTWJ.js";
|
|
96
96
|
|
|
97
97
|
// src/design-canvas-react/components/CanvasInsertPanel.tsx
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "./chunk-UAKFCMWK.js";
|
|
1
2
|
import {
|
|
2
3
|
CANVAS_ELEMENT_KINDS,
|
|
3
4
|
CANVAS_MCP_TOOLS,
|
|
@@ -12,6 +13,12 @@ import {
|
|
|
12
13
|
listTemplateSlots,
|
|
13
14
|
validateBindings
|
|
14
15
|
} from "./chunk-IOREXWLM.js";
|
|
16
|
+
import {
|
|
17
|
+
darkTheme,
|
|
18
|
+
lightTheme,
|
|
19
|
+
themeColor,
|
|
20
|
+
themeToCssVars
|
|
21
|
+
} from "./chunk-Y2SZVW35.js";
|
|
15
22
|
import {
|
|
16
23
|
CHANNEL_PRESETS,
|
|
17
24
|
EXPORT_PRESETS,
|
|
@@ -46,13 +53,6 @@ import {
|
|
|
46
53
|
validateSceneOperations,
|
|
47
54
|
validateSlotValue
|
|
48
55
|
} from "./chunk-M2EBUDZ7.js";
|
|
49
|
-
import "./chunk-UAKFCMWK.js";
|
|
50
|
-
import {
|
|
51
|
-
darkTheme,
|
|
52
|
-
lightTheme,
|
|
53
|
-
themeColor,
|
|
54
|
-
themeToCssVars
|
|
55
|
-
} from "./chunk-Y2SZVW35.js";
|
|
56
56
|
import {
|
|
57
57
|
DEFAULT_REDACTION_PATTERNS,
|
|
58
58
|
buildRedactedDocument,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure role algebra for the teams capability — the tenancy/membership model
|
|
3
|
+
* shared across the fleet. Zero dependencies: no drizzle, no env, no react, no
|
|
4
|
+
* I/O. The DB layer (`./teams/drizzle`), the members API (`./teams/members-api`)
|
|
5
|
+
* and the React surface (`./teams-react`) all build on these functions; this
|
|
6
|
+
* leaf imports nothing back, so a consumer can pull just the role math.
|
|
7
|
+
*
|
|
8
|
+
* Two role ladders, deliberately distinct:
|
|
9
|
+
* - Organization roles rank the tenant (who owns/administers the org and its
|
|
10
|
+
* billing). An org owner/admin is an owner of every workspace under it.
|
|
11
|
+
* - Workspace roles rank a single workspace. They are the access primitive
|
|
12
|
+
* every route checks via `hasWorkspaceRole(actual, minimum)`.
|
|
13
|
+
*
|
|
14
|
+
* `resolveWorkspaceRole` is the bridge: it folds the org role and the
|
|
15
|
+
* per-workspace role into the one effective workspace role a request runs at.
|
|
16
|
+
*/
|
|
17
|
+
declare const WORKSPACE_ROLES: readonly ["viewer", "editor", "admin", "owner"];
|
|
18
|
+
type WorkspaceRole = typeof WORKSPACE_ROLES[number];
|
|
19
|
+
declare const ASSIGNABLE_WORKSPACE_ROLES: readonly ["viewer", "editor", "admin"];
|
|
20
|
+
type AssignableWorkspaceRole = typeof ASSIGNABLE_WORKSPACE_ROLES[number];
|
|
21
|
+
declare const ORGANIZATION_ROLES: readonly ["owner", "admin", "member", "billing"];
|
|
22
|
+
type OrganizationRole = typeof ORGANIZATION_ROLES[number];
|
|
23
|
+
type WorkspaceCollaborationAccess = 'read' | 'write';
|
|
24
|
+
type SandboxWorkspaceRole = 'owner' | 'admin' | 'developer' | 'viewer';
|
|
25
|
+
declare const WORKSPACE_ROLE_RANK: Record<WorkspaceRole, number>;
|
|
26
|
+
declare const ORGANIZATION_ROLE_RANK: Record<OrganizationRole, number>;
|
|
27
|
+
/** True when `actual` is at least `minimum` on the workspace ladder. */
|
|
28
|
+
declare function hasWorkspaceRole(actual: WorkspaceRole, minimum: WorkspaceRole): boolean;
|
|
29
|
+
/** True when `actual` is at least `minimum` on the organization ladder. */
|
|
30
|
+
declare function hasOrganizationRole(actual: OrganizationRole, minimum: OrganizationRole): boolean;
|
|
31
|
+
declare function isAssignableWorkspaceRole(value: unknown): value is AssignableWorkspaceRole;
|
|
32
|
+
/** Org owners and admins are workspace owners across the whole org. */
|
|
33
|
+
declare function organizationRoleGrantsWorkspaceOwner(role: OrganizationRole | string | null | undefined): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The effective workspace role a request runs at: org owner/admin → owner of
|
|
36
|
+
* every workspace; otherwise the explicit per-workspace role (or null = no
|
|
37
|
+
* access). This is the single fold every access check goes through.
|
|
38
|
+
*/
|
|
39
|
+
declare function resolveWorkspaceRole(organizationRole: OrganizationRole | string | null | undefined, workspaceRole: WorkspaceRole | null | undefined): WorkspaceRole | null;
|
|
40
|
+
/**
|
|
41
|
+
* Whether `actorRole` may set/clear a member currently at `targetRole`. Owners
|
|
42
|
+
* can manage anyone; everyone else can only manage members strictly below
|
|
43
|
+
* their own rank (an admin cannot demote another admin or an owner).
|
|
44
|
+
*/
|
|
45
|
+
declare function canManageWorkspaceMemberRole(actorRole: WorkspaceRole, targetRole: WorkspaceRole): boolean;
|
|
46
|
+
declare function workspaceRoleToCollaborationAccess(role: WorkspaceRole): WorkspaceCollaborationAccess;
|
|
47
|
+
declare function workspaceRoleToSandboxRole(role: WorkspaceRole): SandboxWorkspaceRole;
|
|
48
|
+
|
|
49
|
+
export { ASSIGNABLE_WORKSPACE_ROLES as A, type OrganizationRole as O, type SandboxWorkspaceRole as S, WORKSPACE_ROLES as W, type AssignableWorkspaceRole as a, ORGANIZATION_ROLES as b, ORGANIZATION_ROLE_RANK as c, WORKSPACE_ROLE_RANK as d, type WorkspaceCollaborationAccess as e, type WorkspaceRole as f, canManageWorkspaceMemberRole as g, hasOrganizationRole as h, hasWorkspaceRole as i, isAssignableWorkspaceRole as j, workspaceRoleToSandboxRole as k, organizationRoleGrantsWorkspaceOwner as o, resolveWorkspaceRole as r, workspaceRoleToCollaborationAccess as w };
|