cortena-ui 1.4.2 → 1.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.
Files changed (133) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +149 -1
  3. package/dist/a2ui/views.js +2 -2
  4. package/dist/agent-chat/a2ui-block.d.ts +60 -0
  5. package/dist/agent-chat/a2ui-block.js +69 -0
  6. package/dist/agent-chat/a2ui-block.js.map +1 -0
  7. package/dist/agent-chat/agui-client.d.ts +40 -0
  8. package/dist/agent-chat/agui-client.js +251 -0
  9. package/dist/agent-chat/agui-client.js.map +1 -0
  10. package/dist/agent-chat/bridge.d.ts +109 -0
  11. package/dist/agent-chat/bridge.js +349 -0
  12. package/dist/agent-chat/bridge.js.map +1 -0
  13. package/dist/agent-chat/session.d.ts +58 -0
  14. package/dist/agent-chat/session.js +249 -0
  15. package/dist/agent-chat/session.js.map +1 -0
  16. package/dist/agent-chat/store.d.ts +67 -0
  17. package/dist/agent-chat/store.js +548 -0
  18. package/dist/agent-chat/store.js.map +1 -0
  19. package/dist/agent-chat/types.d.ts +187 -0
  20. package/dist/agent-chat/types.js +17 -0
  21. package/dist/agent-chat/types.js.map +1 -0
  22. package/dist/agent-chat.d.ts +10 -0
  23. package/dist/agent-chat.js +10 -0
  24. package/dist/components/admin-permissions/admin-permissions.d.ts +66 -0
  25. package/dist/components/admin-permissions/admin-permissions.js +101 -0
  26. package/dist/components/admin-permissions/admin-permissions.js.map +1 -0
  27. package/dist/components/admin-permissions/context.d.ts +70 -0
  28. package/dist/components/admin-permissions/context.js +258 -0
  29. package/dist/components/admin-permissions/context.js.map +1 -0
  30. package/dist/components/admin-permissions/index.d.ts +10 -0
  31. package/dist/components/admin-permissions/licence.d.ts +15 -0
  32. package/dist/components/admin-permissions/licence.js +78 -0
  33. package/dist/components/admin-permissions/licence.js.map +1 -0
  34. package/dist/components/admin-permissions/matrix.d.ts +20 -0
  35. package/dist/components/admin-permissions/matrix.js +191 -0
  36. package/dist/components/admin-permissions/matrix.js.map +1 -0
  37. package/dist/components/admin-permissions/members.d.ts +18 -0
  38. package/dist/components/admin-permissions/members.js +185 -0
  39. package/dist/components/admin-permissions/members.js.map +1 -0
  40. package/dist/components/admin-permissions/role-assignment.d.ts +35 -0
  41. package/dist/components/admin-permissions/role-assignment.js +174 -0
  42. package/dist/components/admin-permissions/role-assignment.js.map +1 -0
  43. package/dist/components/admin-permissions/roles.d.ts +25 -0
  44. package/dist/components/admin-permissions/roles.js +168 -0
  45. package/dist/components/admin-permissions/roles.js.map +1 -0
  46. package/dist/components/admin-permissions/types.d.ts +152 -0
  47. package/dist/components/admin-permissions/types.js +63 -0
  48. package/dist/components/admin-permissions/types.js.map +1 -0
  49. package/dist/components/agent-chat-popup.d.ts +29 -0
  50. package/dist/components/agent-chat-popup.js +188 -0
  51. package/dist/components/agent-chat-popup.js.map +1 -0
  52. package/dist/components/agent-chat.d.ts +143 -0
  53. package/dist/components/agent-chat.js +578 -0
  54. package/dist/components/agent-chat.js.map +1 -0
  55. package/dist/components/app-shell.d.ts +126 -0
  56. package/dist/components/app-shell.js +297 -0
  57. package/dist/components/app-shell.js.map +1 -0
  58. package/dist/components/badge.d.ts +1 -1
  59. package/dist/components/button-link.js +1 -1
  60. package/dist/components/button.d.ts +2 -2
  61. package/dist/components/checkbox.d.ts +1 -1
  62. package/dist/components/combobox.d.ts +1 -1
  63. package/dist/components/combobox.js +1 -1
  64. package/dist/components/consent-screen.d.ts +65 -0
  65. package/dist/components/consent-screen.js +123 -0
  66. package/dist/components/consent-screen.js.map +1 -0
  67. package/dist/components/data-table/data-table.d.ts +15 -1
  68. package/dist/components/data-table/data-table.js +18 -4
  69. package/dist/components/data-table/data-table.js.map +1 -1
  70. package/dist/components/data-table/index.d.ts +4 -4
  71. package/dist/components/data-table/parts.d.ts +27 -3
  72. package/dist/components/data-table/parts.js +175 -55
  73. package/dist/components/data-table/parts.js.map +1 -1
  74. package/dist/components/data-table/types.d.ts +61 -0
  75. package/dist/components/data-table/use-data-table.js +91 -6
  76. package/dist/components/data-table/use-data-table.js.map +1 -1
  77. package/dist/components/data-table/use-server-source.js +119 -28
  78. package/dist/components/data-table/use-server-source.js.map +1 -1
  79. package/dist/components/help-panel.d.ts +131 -0
  80. package/dist/components/help-panel.js +545 -0
  81. package/dist/components/help-panel.js.map +1 -0
  82. package/dist/components/login-screen.d.ts +127 -0
  83. package/dist/components/login-screen.js +339 -0
  84. package/dist/components/login-screen.js.map +1 -0
  85. package/dist/components/session-guard.d.ts +268 -0
  86. package/dist/components/session-guard.js +632 -0
  87. package/dist/components/session-guard.js.map +1 -0
  88. package/dist/components/toast.d.ts +1 -1
  89. package/dist/core.d.ts +5 -1
  90. package/dist/core.js +11 -7
  91. package/dist/data-table.d.ts +13 -4
  92. package/dist/data-table.js +10 -2
  93. package/dist/hooks/use-cortena-theme.js +49 -3
  94. package/dist/hooks/use-cortena-theme.js.map +1 -1
  95. package/dist/index.d.ts +17 -4
  96. package/dist/index.js +21 -8
  97. package/dist/markdown.d.ts +2 -1
  98. package/dist/markdown.js +2 -1
  99. package/package.json +16 -4
  100. package/src/agent-chat/a2ui-block.ts +118 -0
  101. package/src/agent-chat/agui-client.ts +405 -0
  102. package/src/agent-chat/bridge.ts +433 -0
  103. package/src/agent-chat/session.ts +392 -0
  104. package/src/agent-chat/store.ts +738 -0
  105. package/src/agent-chat/types.ts +213 -0
  106. package/src/components/admin-permissions/admin-permissions.tsx +130 -0
  107. package/src/components/admin-permissions/context.tsx +376 -0
  108. package/src/components/admin-permissions/index.tsx +32 -0
  109. package/src/components/admin-permissions/licence.tsx +84 -0
  110. package/src/components/admin-permissions/matrix.tsx +257 -0
  111. package/src/components/admin-permissions/members.tsx +204 -0
  112. package/src/components/admin-permissions/role-assignment.tsx +239 -0
  113. package/src/components/admin-permissions/roles.tsx +169 -0
  114. package/src/components/admin-permissions/types.ts +231 -0
  115. package/src/components/agent-chat-popup.tsx +289 -0
  116. package/src/components/agent-chat.tsx +843 -0
  117. package/src/components/app-shell.tsx +502 -0
  118. package/src/components/consent-screen.tsx +239 -0
  119. package/src/components/data-table/data-table.tsx +36 -0
  120. package/src/components/data-table/index.tsx +6 -1
  121. package/src/components/data-table/parts.tsx +223 -47
  122. package/src/components/data-table/types.ts +68 -0
  123. package/src/components/data-table/use-data-table.ts +152 -4
  124. package/src/components/data-table/use-server-source.ts +150 -12
  125. package/src/components/help-panel.tsx +765 -0
  126. package/src/components/login-screen.tsx +479 -0
  127. package/src/components/session-guard.tsx +1071 -0
  128. package/src/entries/agent-chat.ts +113 -0
  129. package/src/entries/core.ts +8 -0
  130. package/src/entries/data-table.ts +41 -0
  131. package/src/entries/markdown.ts +25 -0
  132. package/src/hooks/use-cortena-theme.ts +63 -4
  133. package/src/index.ts +6 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matrix.js","names":[],"sources":["../../../src/components/admin-permissions/matrix.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Button } from \"@/components/button\";\nimport { Checkbox } from \"@/components/checkbox\";\nimport { EmptyState } from \"@/components/empty-state\";\nimport { ErrorBanner } from \"@/components/error-banner\";\nimport { LoadingSkeleton } from \"@/components/loading-skeleton\";\nimport { SectionCard } from \"@/components/section-card\";\nimport { Spinner } from \"@/components/spinner\";\nimport { useAdminPermissions } from \"./context\";\nimport { adminErrorMessage, type AdminPermissionMatrix } from \"./types\";\n\nfunction grantsEqual(a: Record<string, string[]>, b: Record<string, string[]>): boolean {\n const keys = new Set([...Object.keys(a), ...Object.keys(b)]);\n for (const key of keys) {\n const left = [...(a[key] ?? [])].sort();\n const right = [...(b[key] ?? [])].sort();\n if (left.length !== right.length) return false;\n if (left.some((value, i) => value !== right[i])) return false;\n }\n return true;\n}\n\n/**\n * `/admin/permissions` — the extension's own roles × actions matrix, the one\n * document `may()` reads (§13.2, P-14).\n *\n * A grid of checkboxes, and nothing clever: rows are the roles the extension\n * declares, columns are the actions it declares, and a tick is a grant. The\n * screen holds a draft rather than writing per click — a permission matrix is\n * edited as a whole thought, and a half-applied one is a security hole with a\n * progress spinner over it — so the edits accumulate, Save PUTs the document\n * and Discard throws the draft away.\n *\n * The roles and the actions are both read-only here: they are the extension's\n * declared vocabulary, changed in its source, not in an admin screen.\n */\nexport function AdminMatrix() {\n const { matrix, matrixState, saveMatrix } = useAdminPermissions();\n const [draft, setDraft] = React.useState<Record<string, string[]> | null>(null);\n const [saving, setSaving] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n\n // Re-seed when a different document arrives: a reload, the optimistic copy\n // this screen just wrote, or the rollback that put the server's back.\n // Adjusting state during render, not an effect.\n //\n // The error is deliberately NOT cleared here. A failed save rolls the\n // optimistic copy back, which lands as exactly this re-seed, in the same\n // batch as the message — clearing it would wipe the message that explains\n // why the grid just moved. Save and Discard clear it instead.\n const [seeded, setSeeded] = React.useState<AdminPermissionMatrix | null>(matrix);\n if (matrix !== seeded) {\n setSeeded(matrix);\n setDraft(null);\n }\n\n if (matrixState.error) {\n return <ErrorBanner message={matrixState.error.message} onRetry={matrixState.reload} />;\n }\n if (!matrix) {\n return <LoadingSkeleton preset=\"table\" data-slot=\"admin-matrix-loading\" />;\n }\n\n const grants = draft ?? matrix.grants;\n const dirty = draft !== null && !grantsEqual(draft, matrix.grants);\n\n const toggle = (role: string, action: string, checked: boolean) => {\n setDraft((prev) => {\n const base = prev ?? matrix.grants;\n const current = base[role] ?? [];\n const next = checked\n ? current.includes(action)\n ? current\n : [...current, action]\n : current.filter((a) => a !== action);\n return { ...base, [role]: next };\n });\n };\n\n const save = async () => {\n if (!draft) return;\n setSaving(true);\n setError(null);\n try {\n // Only what the grid showed. A key whose grants are all off is dropped\n // rather than written back as an empty array, so revoking an unknown\n // role really removes it from the document.\n const grants = Object.fromEntries(\n Object.entries(draft).filter(([, actions]) => actions.length > 0),\n );\n await saveMatrix({ roles: matrix.roles, actions: matrix.actions, grants });\n setDraft(null);\n } catch (err) {\n // `saveMatrix` has already put the stored document back, so the grid is\n // showing the server's grants again rather than an edit that never\n // landed. The envelope's own message says why (§15.6).\n setError(adminErrorMessage(err));\n } finally {\n setSaving(false);\n }\n };\n\n /**\n * Grants naming a role or an action the extension no longer declares.\n *\n * They exist routinely: a release renames `document.write` or retires\n * `reviewer`, and the stored document keeps the old key. Rendering only the\n * declared vocabulary hid them — and Save then PUT the whole `grants` object\n * back, so every invisible grant was rewritten, unchanged, by an\n * administrator who had just been looking at the screen and believed they\n * had seen the whole policy. `may()` reads the document, not this grid, so\n * a hidden `reviewer: [\"admin.manage\"]` is live permission nobody can see or\n * revoke.\n *\n * The rule `role-assignment.tsx` already follows for a retired role: show\n * it, mark it, and let it be taken away.\n */\n const grantedRoles = Object.keys(grants).filter((role) => (grants[role] ?? []).length > 0);\n const unknownRoles = grantedRoles.filter((role) => !matrix.roles.includes(role)).sort();\n const grantedActions = [...new Set(Object.values(grants).flat())];\n const unknownActions = grantedActions.filter((a) => !matrix.actions.includes(a)).sort();\n const roleRows = [...matrix.roles, ...unknownRoles];\n const actionColumns = [...matrix.actions, ...unknownActions];\n const hasUnknown = unknownRoles.length > 0 || unknownActions.length > 0;\n\n const empty = roleRows.length === 0 || actionColumns.length === 0;\n\n return (\n <div data-slot=\"admin-matrix\" className=\"flex flex-col gap-3\">\n {error ? <ErrorBanner message={error} data-slot=\"admin-matrix-error\" /> : null}\n <SectionCard\n title=\"Permissions\"\n description=\"This extension's own matrix, in its own database. One policy function reads it, and every route and every MCP tool goes through that function.\"\n actions={\n empty ? null : (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={!dirty || saving}\n onClick={() => {\n setDraft(null);\n setError(null);\n }}\n >\n Discard\n </Button>\n <Button size=\"sm\" disabled={!dirty || saving} onClick={save}>\n {saving ? <Spinner tone=\"inverse\" /> : null}\n Save\n </Button>\n </>\n )\n }\n flush={!empty}\n >\n {empty ? (\n <EmptyState\n size=\"compact\"\n title=\"Nothing declared yet\"\n description=\"The matrix is the roles and actions this extension declares. Until it declares both, there is nothing to grant.\"\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <table\n data-slot=\"admin-matrix-grid\"\n className=\"w-full border-collapse text-[length:var(--ds-text-caption-lg)]\"\n >\n <caption className=\"sr-only\">Roles and the actions each one grants</caption>\n <thead>\n <tr className=\"bg-[var(--ds-muted)] text-[color:var(--ds-muted-foreground)]\">\n <th\n scope=\"col\"\n className=\"sticky left-0 z-10 bg-[var(--ds-muted)] px-4 py-2.5 text-left font-medium\"\n >\n Role\n </th>\n {actionColumns.map((action) => (\n <th\n key={action}\n scope=\"col\"\n data-action={action}\n data-unknown={unknownActions.includes(action) ? \"\" : undefined}\n title={\n unknownActions.includes(action)\n ? \"This extension no longer declares this action\"\n : undefined\n }\n className=\"whitespace-nowrap px-3 py-2.5 text-center font-medium\"\n >\n {action}\n {unknownActions.includes(action) ? (\n <span className=\"ml-1 text-[color:var(--ds-warning)]\">(unknown)</span>\n ) : null}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {roleRows.map((role) => (\n <tr\n key={role}\n data-slot=\"admin-matrix-row\"\n data-role={role}\n data-unknown={unknownRoles.includes(role) ? \"\" : undefined}\n className=\"border-t border-[var(--ds-border-subtle)]\"\n >\n <th\n scope=\"row\"\n className=\"sticky left-0 z-10 bg-[var(--ds-card)] px-4 py-2 text-left font-medium text-[color:var(--ds-foreground)]\"\n >\n {role}\n {unknownRoles.includes(role) ? (\n <span\n data-slot=\"admin-matrix-unknown\"\n className=\"ml-1 font-normal text-[length:var(--ds-text-caption)] text-[color:var(--ds-warning)]\"\n >\n unknown role\n </span>\n ) : null}\n </th>\n {actionColumns.map((action) => {\n const checked = (grants[role] ?? []).includes(action);\n return (\n <td key={action} className=\"px-3 py-2 text-center\">\n <Checkbox\n checked={checked}\n data-role={role}\n data-action={action}\n aria-label={`${role} may ${action}`}\n onCheckedChange={(next) => toggle(role, action, Boolean(next))}\n />\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n {hasUnknown ? (\n <p\n data-slot=\"admin-matrix-unknown-note\"\n className=\"px-4 py-2.5 text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\"\n >\n Rows and columns marked unknown are grants on a role or an action this\n extension no longer declares. They are still live — untick them and save to\n remove them.\n </p>\n ) : null}\n </div>\n )}\n </SectionCard>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ //#region src/components/admin-permissions/members.d.ts
4
+ /**
5
+ * `/admin/members` — the join described in §13.3, on the DataTable **server**
6
+ * source: an org's membership is unbounded, so it is paged, searched and
7
+ * sorted by the extension's own endpoint.
8
+ *
9
+ * The left half of every row is cortena-auth's and read-only: the person,
10
+ * their status, and the groups they belong to. The right half is the
11
+ * extension's: the direct assignment, and the effective roles `may()` resolves
12
+ * from it plus every group's roles, each labelled with its source. Only the
13
+ * direct assignment is editable, and the groups column disappears entirely
14
+ * when the org has no groups.
15
+ */
16
+ export declare function AdminMembers(): React.JSX.Element;
17
+ //#endregion
18
+ //# sourceMappingURL=members.d.ts.map
@@ -0,0 +1,185 @@
1
+ "use client";
2
+ "use client";
3
+ import { Button } from "../button.js";
4
+ import { Badge } from "../badge.js";
5
+ import { Chip } from "../chip.js";
6
+ import { ErrorBanner } from "../error-banner.js";
7
+ import { createColumnHelper } from "../data-table/features.js";
8
+ import { DataTable } from "../data-table/data-table.js";
9
+ import { groupsOf, sourceGroupOf } from "./types.js";
10
+ import { directRolesOf, effectiveRolesWithOverride, useAdminPermissions } from "./context.js";
11
+ import { EffectiveRoles, RoleAssignmentDialog } from "./role-assignment.js";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ import * as React from "react";
14
+ //#region src/components/admin-permissions/members.tsx
15
+ const helper = /* @__PURE__ */ createColumnHelper();
16
+ function statusVariant(status) {
17
+ const s = status.toLowerCase();
18
+ if (s === "active") return "success";
19
+ if (s === "invited" || s === "pending") return "warning";
20
+ if (s === "suspended" || s === "disabled" || s === "removed") return "destructive";
21
+ return "default";
22
+ }
23
+ function formatLastActive(value) {
24
+ if (!value) return "Never";
25
+ const date = new Date(value);
26
+ if (Number.isNaN(date.getTime())) return value;
27
+ return date.toLocaleDateString(void 0, {
28
+ year: "numeric",
29
+ month: "short",
30
+ day: "numeric"
31
+ });
32
+ }
33
+ /**
34
+ * `/admin/members` — the join described in §13.3, on the DataTable **server**
35
+ * source: an org's membership is unbounded, so it is paged, searched and
36
+ * sorted by the extension's own endpoint.
37
+ *
38
+ * The left half of every row is cortena-auth's and read-only: the person,
39
+ * their status, and the groups they belong to. The right half is the
40
+ * extension's: the direct assignment, and the effective roles `may()` resolves
41
+ * from it plus every group's roles, each labelled with its source. Only the
42
+ * direct assignment is editable, and the groups column disappears entirely
43
+ * when the org has no groups.
44
+ */
45
+ function AdminMembers() {
46
+ const { api, hasGroups, groupsUnknown, groupsState, revision, memberRoles, reconcileMembers } = useAdminPermissions();
47
+ const [editing, setEditing] = React.useState(null);
48
+ const [editingRoles, setEditingRoles] = React.useState([]);
49
+ const columns = React.useMemo(() => helper.columns([
50
+ helper.accessor("name", {
51
+ header: "Member",
52
+ cell: ({ row }) => /* @__PURE__ */ jsxs("div", {
53
+ className: "flex flex-col",
54
+ children: [/* @__PURE__ */ jsx("span", {
55
+ className: "font-medium text-[color:var(--ds-foreground)]",
56
+ children: row.original.name
57
+ }), /* @__PURE__ */ jsx("span", {
58
+ className: "text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]",
59
+ children: row.original.email
60
+ })]
61
+ }),
62
+ meta: { label: "Member" }
63
+ }),
64
+ helper.accessor("status", {
65
+ header: "Status",
66
+ cell: ({ row }) => /* @__PURE__ */ jsx(Badge, {
67
+ variant: statusVariant(row.original.status),
68
+ children: row.original.status
69
+ }),
70
+ meta: { label: "Status" }
71
+ }),
72
+ ...hasGroups || groupsUnknown ? [helper.display({
73
+ id: "groups",
74
+ header: "Groups",
75
+ cell: ({ row }) => {
76
+ const groups = groupsOf(row.original);
77
+ if (groups.length === 0) return /* @__PURE__ */ jsx("span", {
78
+ className: "text-[length:var(--ds-text-caption)] text-[color:var(--ds-text-tertiary)]",
79
+ children: "None"
80
+ });
81
+ return /* @__PURE__ */ jsx("div", {
82
+ className: "flex flex-wrap items-center gap-1",
83
+ children: groups.map((group) => /* @__PURE__ */ jsx(Chip, {
84
+ size: "sm",
85
+ "data-group": group.id,
86
+ children: group.name
87
+ }, group.id))
88
+ });
89
+ },
90
+ meta: {
91
+ label: "Groups",
92
+ exportValue: (row) => groupsOf(row).map((g) => g.name).join(", ")
93
+ },
94
+ enableSorting: false
95
+ })] : [],
96
+ helper.display({
97
+ id: "effectiveRoles",
98
+ header: "Effective roles",
99
+ cell: ({ row }) => /* @__PURE__ */ jsx(EffectiveRoles, { roles: effectiveRolesWithOverride(row.original, memberRoles) }),
100
+ meta: {
101
+ label: "Effective roles",
102
+ exportValue: (row) => effectiveRolesWithOverride(row, memberRoles).map((entry) => {
103
+ const group = sourceGroupOf(entry.source);
104
+ return group ? `${entry.role} (via ${group.name})` : entry.role;
105
+ }).join(", ")
106
+ },
107
+ enableSorting: false
108
+ }),
109
+ helper.accessor("lastActiveAt", {
110
+ header: "Last active",
111
+ cell: ({ row }) => formatLastActive(row.original.lastActiveAt),
112
+ meta: { label: "Last active" }
113
+ }),
114
+ helper.display({
115
+ id: "actions",
116
+ header: () => /* @__PURE__ */ jsx("span", {
117
+ className: "sr-only",
118
+ children: "Actions"
119
+ }),
120
+ cell: ({ row }) => /* @__PURE__ */ jsx("div", {
121
+ className: "flex justify-end",
122
+ children: /* @__PURE__ */ jsx(Button, {
123
+ variant: "outline",
124
+ size: "sm",
125
+ "aria-label": `Edit roles for ${row.original.name}`,
126
+ onClick: () => {
127
+ setEditingRoles(directRolesOf(row.original, memberRoles));
128
+ setEditing({
129
+ kind: "user",
130
+ id: row.original.userId,
131
+ name: row.original.name
132
+ });
133
+ },
134
+ children: "Edit roles"
135
+ })
136
+ }),
137
+ meta: {
138
+ align: "end",
139
+ exportable: false
140
+ },
141
+ enableSorting: false,
142
+ enableHiding: false
143
+ })
144
+ ]), [
145
+ hasGroups,
146
+ groupsUnknown,
147
+ memberRoles
148
+ ]);
149
+ return /* @__PURE__ */ jsxs("div", {
150
+ "data-slot": "admin-members",
151
+ className: "flex flex-col gap-3",
152
+ children: [
153
+ groupsState.error ? /* @__PURE__ */ jsx(ErrorBanner, {
154
+ "data-slot": "admin-members-groups-error",
155
+ message: `Group membership could not be loaded, so inherited roles may be missing. ${groupsState.error.message}`,
156
+ onRetry: groupsState.reload
157
+ }) : null,
158
+ /* @__PURE__ */ jsx(DataTable, {
159
+ columns,
160
+ dataSource: {
161
+ kind: "server",
162
+ fetch: async (query, signal) => {
163
+ const result = await api.members.list(query, signal);
164
+ reconcileMembers(result.rows);
165
+ return result;
166
+ },
167
+ queryKey: ["cortena-admin-members", revision]
168
+ },
169
+ getRowId: (row) => row.userId,
170
+ searchPlaceholder: "Search members…",
171
+ emptyMessage: "No members",
172
+ exportFileName: "members"
173
+ }),
174
+ /* @__PURE__ */ jsx(RoleAssignmentDialog, {
175
+ principal: editing,
176
+ current: editingRoles,
177
+ onOpenChange: (open) => !open && setEditing(null)
178
+ })
179
+ ]
180
+ });
181
+ }
182
+ //#endregion
183
+ export { AdminMembers };
184
+
185
+ //# sourceMappingURL=members.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"members.js","names":[],"sources":["../../../src/components/admin-permissions/members.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Badge } from \"@/components/badge\";\nimport { Button } from \"@/components/button\";\nimport { Chip } from \"@/components/chip\";\nimport { DataTable, createColumnHelper } from \"@/components/data-table\";\nimport { ErrorBanner } from \"@/components/error-banner\";\nimport { directRolesOf, effectiveRolesWithOverride, useAdminPermissions } from \"./context\";\nimport { EffectiveRoles, RoleAssignmentDialog } from \"./role-assignment\";\nimport { groupsOf, sourceGroupOf, type AdminMemberRow, type AdminPrincipal } from \"./types\";\n\nconst helper = /* @__PURE__ */ createColumnHelper<AdminMemberRow>();\n\nfunction statusVariant(status: string): \"default\" | \"success\" | \"warning\" | \"destructive\" {\n const s = status.toLowerCase();\n if (s === \"active\") return \"success\";\n if (s === \"invited\" || s === \"pending\") return \"warning\";\n if (s === \"suspended\" || s === \"disabled\" || s === \"removed\") return \"destructive\";\n return \"default\";\n}\n\nfunction formatLastActive(value: string | null | undefined): string {\n if (!value) return \"Never\";\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return value;\n return date.toLocaleDateString(undefined, { year: \"numeric\", month: \"short\", day: \"numeric\" });\n}\n\n/**\n * `/admin/members` — the join described in §13.3, on the DataTable **server**\n * source: an org's membership is unbounded, so it is paged, searched and\n * sorted by the extension's own endpoint.\n *\n * The left half of every row is cortena-auth's and read-only: the person,\n * their status, and the groups they belong to. The right half is the\n * extension's: the direct assignment, and the effective roles `may()` resolves\n * from it plus every group's roles, each labelled with its source. Only the\n * direct assignment is editable, and the groups column disappears entirely\n * when the org has no groups.\n */\nexport function AdminMembers() {\n const { api, hasGroups, groupsUnknown, groupsState, revision, memberRoles, reconcileMembers } =\n useAdminPermissions();\n const [editing, setEditing] = React.useState<AdminPrincipal | null>(null);\n const [editingRoles, setEditingRoles] = React.useState<readonly string[]>([]);\n\n const columns = React.useMemo(\n () =>\n helper.columns([\n helper.accessor(\"name\", {\n header: \"Member\",\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium text-[color:var(--ds-foreground)]\">\n {row.original.name}\n </span>\n <span className=\"text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\">\n {row.original.email}\n </span>\n </div>\n ),\n meta: { label: \"Member\" },\n }),\n helper.accessor(\"status\", {\n header: \"Status\",\n cell: ({ row }) => (\n <Badge variant={statusVariant(row.original.status)}>{row.original.status}</Badge>\n ),\n // Deliberately not a faceted filter: a server source loads one page,\n // so the facets would have to be a fixed list, and membership status\n // is cortena-auth's vocabulary rather than this component's to\n // invent. The search box already reaches it.\n meta: { label: \"Status\" },\n }),\n ...(hasGroups || groupsUnknown\n ? [\n helper.display({\n id: \"groups\",\n header: \"Groups\",\n cell: ({ row }) => {\n const groups = groupsOf(row.original);\n if (groups.length === 0) {\n return (\n <span className=\"text-[length:var(--ds-text-caption)] text-[color:var(--ds-text-tertiary)]\">\n None\n </span>\n );\n }\n return (\n <div className=\"flex flex-wrap items-center gap-1\">\n {groups.map((group) => (\n <Chip key={group.id} size=\"sm\" data-group={group.id}>\n {group.name}\n </Chip>\n ))}\n </div>\n );\n },\n meta: {\n label: \"Groups\",\n exportValue: (row: AdminMemberRow) =>\n groupsOf(row)\n .map((g) => g.name)\n .join(\", \"),\n },\n enableSorting: false,\n }),\n ]\n : []),\n helper.display({\n id: \"effectiveRoles\",\n header: \"Effective roles\",\n cell: ({ row }) => (\n <EffectiveRoles roles={effectiveRolesWithOverride(row.original, memberRoles)} />\n ),\n meta: {\n label: \"Effective roles\",\n exportValue: (row: AdminMemberRow) =>\n effectiveRolesWithOverride(row, memberRoles)\n .map((entry) => {\n const group = sourceGroupOf(entry.source);\n return group ? `${entry.role} (via ${group.name})` : entry.role;\n })\n .join(\", \"),\n },\n enableSorting: false,\n }),\n helper.accessor(\"lastActiveAt\", {\n header: \"Last active\",\n cell: ({ row }) => formatLastActive(row.original.lastActiveAt),\n meta: { label: \"Last active\" },\n }),\n helper.display({\n id: \"actions\",\n header: () => <span className=\"sr-only\">Actions</span>,\n cell: ({ row }) => (\n <div className=\"flex justify-end\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n aria-label={`Edit roles for ${row.original.name}`}\n onClick={() => {\n setEditingRoles(directRolesOf(row.original, memberRoles));\n setEditing({\n kind: \"user\",\n id: row.original.userId,\n name: row.original.name,\n });\n }}\n >\n Edit roles\n </Button>\n </div>\n ),\n meta: { align: \"end\", exportable: false },\n enableSorting: false,\n enableHiding: false,\n }),\n ]),\n [hasGroups, groupsUnknown, memberRoles],\n );\n\n return (\n <div data-slot=\"admin-members\" className=\"flex flex-col gap-3\">\n {/* A failed `groups.list()` renders exactly like an org with no groups:\n every inherited role silently disappears and the effective-roles\n column reads as a fact about the member. Say so instead. */}\n {groupsState.error ? (\n <ErrorBanner\n data-slot=\"admin-members-groups-error\"\n message={`Group membership could not be loaded, so inherited roles may be missing. ${groupsState.error.message}`}\n onRetry={groupsState.reload}\n />\n ) : null}\n <DataTable\n columns={columns}\n dataSource={{\n kind: \"server\",\n fetch: async (query, signal) => {\n const result = await api.members.list(query, signal);\n // The server has spoken: any optimistic entry it now agrees with\n // has done its job and is retired.\n reconcileMembers(result.rows);\n return result;\n },\n // The revision is part of the cache scope, so a successful write —\n // to this member or to a group they belong to — invalidates the page\n // rather than leaving a stale resolution on screen.\n queryKey: [\"cortena-admin-members\", revision],\n }}\n getRowId={(row) => row.userId}\n searchPlaceholder=\"Search members…\"\n emptyMessage=\"No members\"\n exportFileName=\"members\"\n />\n <RoleAssignmentDialog\n principal={editing}\n current={editingRoles}\n onOpenChange={(open) => !open && setEditing(null)}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { AdminEffectiveRole, AdminPrincipal } from "./types.js";
3
+ import * as React from "react";
4
+ //#region src/components/admin-permissions/role-assignment.d.ts
5
+ /**
6
+ * The effective-roles cell. Every role carries the assignment that granted it:
7
+ * nothing for a direct one, and the group's name for an inherited one. This is
8
+ * the whole point of §13.4 and P-43 — an administrator who cannot see why
9
+ * somebody has a permission removes the wrong assignment and believes the job
10
+ * is done.
11
+ */
12
+ export declare function EffectiveRoles({ roles, className }: {
13
+ roles: readonly AdminEffectiveRole[];
14
+ className?: string;
15
+ }): React.JSX.Element;
16
+ /** A plain list of directly assigned roles, for the group list. */
17
+ export declare function DirectRoles({ roles }: {
18
+ roles: readonly string[];
19
+ }): React.JSX.Element;
20
+ export interface RoleAssignmentDialogProps {
21
+ /** The principal being edited. A user and a group take the same dialog. */
22
+ principal: AdminPrincipal | null;
23
+ /** The principal's current direct assignment. */
24
+ current: readonly string[];
25
+ onOpenChange: (open: boolean) => void;
26
+ }
27
+ /**
28
+ * The one editor for a role assignment, for both principal kinds (DESIGN-D22).
29
+ * It offers the roles the extension declared and nothing else; a role already
30
+ * assigned that the matrix no longer declares is still listed, marked, and
31
+ * removable, because hiding it would leave a grant nobody can see or revoke.
32
+ */
33
+ export declare function RoleAssignmentDialog({ principal, current, onOpenChange }: RoleAssignmentDialogProps): React.JSX.Element;
34
+ //#endregion
35
+ //# sourceMappingURL=role-assignment.d.ts.map
@@ -0,0 +1,174 @@
1
+ "use client";
2
+ "use client";
3
+ import { cn } from "../../lib/cn.js";
4
+ import { Button } from "../button.js";
5
+ import { Spinner } from "../spinner.js";
6
+ import { Checkbox } from "../checkbox.js";
7
+ import { Chip } from "../chip.js";
8
+ import { Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../dialog.js";
9
+ import { ErrorBanner } from "../error-banner.js";
10
+ import { adminErrorMessage, sourceGroupOf } from "./types.js";
11
+ import { useAdminPermissions } from "./context.js";
12
+ import { Users } from "lucide-react";
13
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
+ import * as React from "react";
15
+ //#region src/components/admin-permissions/role-assignment.tsx
16
+ /**
17
+ * The effective-roles cell. Every role carries the assignment that granted it:
18
+ * nothing for a direct one, and the group's name for an inherited one. This is
19
+ * the whole point of §13.4 and P-43 — an administrator who cannot see why
20
+ * somebody has a permission removes the wrong assignment and believes the job
21
+ * is done.
22
+ */
23
+ function EffectiveRoles({ roles, className }) {
24
+ if (roles.length === 0) return /* @__PURE__ */ jsx("span", {
25
+ "data-slot": "admin-effective-roles-empty",
26
+ className: "text-[length:var(--ds-text-caption)] text-[color:var(--ds-text-tertiary)]",
27
+ children: "No roles"
28
+ });
29
+ return /* @__PURE__ */ jsx("div", {
30
+ "data-slot": "admin-effective-roles",
31
+ className: cn("flex flex-wrap items-center gap-1", className),
32
+ children: roles.map((entry, index) => {
33
+ const group = sourceGroupOf(entry.source);
34
+ return /* @__PURE__ */ jsxs(Chip, {
35
+ size: "sm",
36
+ variant: group ? "outline" : "accent",
37
+ "data-role": entry.role,
38
+ "data-source": group ? group.id : "direct",
39
+ title: group ? `${entry.role} — via group ${group.name}` : `${entry.role} — assigned directly`,
40
+ children: [entry.role, group ? /* @__PURE__ */ jsxs("span", {
41
+ className: "inline-flex items-center gap-1 text-[color:var(--ds-text-tertiary)]",
42
+ children: [
43
+ /* @__PURE__ */ jsx(Users, { "aria-hidden": true }),
44
+ "via ",
45
+ group.name
46
+ ]
47
+ }) : null]
48
+ }, `${entry.role}-${group?.id ?? "direct"}-${index}`);
49
+ })
50
+ });
51
+ }
52
+ /** A plain list of directly assigned roles, for the group list. */
53
+ function DirectRoles({ roles }) {
54
+ if (roles.length === 0) return /* @__PURE__ */ jsx("span", {
55
+ "data-slot": "admin-direct-roles-empty",
56
+ className: "text-[length:var(--ds-text-caption)] text-[color:var(--ds-text-tertiary)]",
57
+ children: "None"
58
+ });
59
+ return /* @__PURE__ */ jsx("div", {
60
+ "data-slot": "admin-direct-roles",
61
+ className: "flex flex-wrap items-center gap-1",
62
+ children: roles.map((role) => /* @__PURE__ */ jsx(Chip, {
63
+ size: "sm",
64
+ variant: "accent",
65
+ "data-role": role,
66
+ children: role
67
+ }, role))
68
+ });
69
+ }
70
+ /**
71
+ * The one editor for a role assignment, for both principal kinds (DESIGN-D22).
72
+ * It offers the roles the extension declared and nothing else; a role already
73
+ * assigned that the matrix no longer declares is still listed, marked, and
74
+ * removable, because hiding it would leave a grant nobody can see or revoke.
75
+ */
76
+ function RoleAssignmentDialog({ principal, current, onOpenChange }) {
77
+ const { matrix, assignRoles } = useAdminPermissions();
78
+ const [selected, setSelected] = React.useState(current);
79
+ const [saving, setSaving] = React.useState(false);
80
+ const [error, setError] = React.useState(null);
81
+ const key = principal ? `${principal.kind}:${principal.id}` : null;
82
+ const [seededKey, setSeededKey] = React.useState(key);
83
+ if (key !== seededKey) {
84
+ setSeededKey(key);
85
+ setSelected(current);
86
+ setError(null);
87
+ }
88
+ const declared = matrix?.roles ?? [];
89
+ const retired = current.filter((role) => !declared.includes(role));
90
+ const options = [...declared, ...retired];
91
+ const toggle = (role, checked) => {
92
+ setSelected((prev) => checked ? [...prev, role] : prev.filter((r) => r !== role));
93
+ };
94
+ const save = async () => {
95
+ if (!principal) return;
96
+ setSaving(true);
97
+ setError(null);
98
+ try {
99
+ await assignRoles(principal, [...selected]);
100
+ onOpenChange(false);
101
+ } catch (err) {
102
+ setError(adminErrorMessage(err));
103
+ } finally {
104
+ setSaving(false);
105
+ }
106
+ };
107
+ return /* @__PURE__ */ jsx(Dialog, {
108
+ open: principal !== null,
109
+ onOpenChange: (open) => !open && onOpenChange(false),
110
+ children: /* @__PURE__ */ jsxs(DialogContent, {
111
+ "data-slot": "admin-role-dialog",
112
+ children: [
113
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: "Edit roles" }), /* @__PURE__ */ jsx(DialogDescription, { children: principal?.kind === "group" ? /* @__PURE__ */ jsxs(Fragment, { children: [
114
+ "Every member of ",
115
+ /* @__PURE__ */ jsx("strong", { children: principal.name ?? principal.id }),
116
+ " inherits the roles you select here, as soon as they join."
117
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
118
+ "Direct assignment for ",
119
+ /* @__PURE__ */ jsx("strong", { children: principal?.name ?? principal?.id }),
120
+ ". Roles inherited from a group are changed on the group, not here."
121
+ ] }) })] }),
122
+ /* @__PURE__ */ jsxs(DialogBody, {
123
+ className: "flex flex-col gap-2",
124
+ children: [error ? /* @__PURE__ */ jsx(ErrorBanner, {
125
+ message: error,
126
+ "data-slot": "admin-role-dialog-error"
127
+ }) : null, options.length === 0 ? /* @__PURE__ */ jsx("p", {
128
+ className: "text-[length:var(--ds-text-body-sm)] text-[color:var(--ds-muted-foreground)]",
129
+ children: "This extension has not declared any roles yet."
130
+ }) : /* @__PURE__ */ jsx("ul", {
131
+ className: "flex flex-col gap-0.5",
132
+ "data-slot": "admin-role-options",
133
+ children: options.map((role) => {
134
+ const checked = selected.includes(role);
135
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", {
136
+ className: cn("flex cursor-pointer items-center gap-2.5 rounded-[var(--ds-radius-md)] px-2 py-1.5", "text-[length:var(--ds-text-body-sm)] text-[color:var(--ds-foreground)]", "transition-colors duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]", "hover:bg-[var(--ds-hover)]"),
137
+ children: [
138
+ /* @__PURE__ */ jsx(Checkbox, {
139
+ checked,
140
+ onCheckedChange: (next) => toggle(role, Boolean(next))
141
+ }),
142
+ /* @__PURE__ */ jsx("span", {
143
+ className: "flex-1",
144
+ children: role
145
+ }),
146
+ declared.includes(role) ? null : /* @__PURE__ */ jsx(Chip, {
147
+ size: "sm",
148
+ variant: "outline",
149
+ title: "Not in the declared vocabulary",
150
+ children: "retired"
151
+ })
152
+ ]
153
+ }) }, role);
154
+ })
155
+ })]
156
+ }),
157
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
158
+ variant: "outline",
159
+ onClick: () => onOpenChange(false),
160
+ disabled: saving,
161
+ children: "Cancel"
162
+ }), /* @__PURE__ */ jsxs(Button, {
163
+ onClick: save,
164
+ disabled: saving || options.length === 0,
165
+ children: [saving ? /* @__PURE__ */ jsx(Spinner, { tone: "inverse" }) : null, "Save roles"]
166
+ })] })
167
+ ]
168
+ })
169
+ });
170
+ }
171
+ //#endregion
172
+ export { DirectRoles, EffectiveRoles, RoleAssignmentDialog };
173
+
174
+ //# sourceMappingURL=role-assignment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-assignment.js","names":[],"sources":["../../../src/components/admin-permissions/role-assignment.tsx"],"sourcesContent":["\"use client\";\n\nimport { Users } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button } from \"@/components/button\";\nimport { Checkbox } from \"@/components/checkbox\";\nimport { Chip } from \"@/components/chip\";\nimport {\n Dialog,\n DialogBody,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@/components/dialog\";\nimport { ErrorBanner } from \"@/components/error-banner\";\nimport { Spinner } from \"@/components/spinner\";\nimport { cn } from \"@/lib/cn\";\nimport { useAdminPermissions } from \"./context\";\nimport {\n adminErrorMessage,\n sourceGroupOf,\n type AdminEffectiveRole,\n type AdminPrincipal,\n} from \"./types\";\n\n/**\n * The effective-roles cell. Every role carries the assignment that granted it:\n * nothing for a direct one, and the group's name for an inherited one. This is\n * the whole point of §13.4 and P-43 — an administrator who cannot see why\n * somebody has a permission removes the wrong assignment and believes the job\n * is done.\n */\nexport function EffectiveRoles({\n roles,\n className,\n}: {\n roles: readonly AdminEffectiveRole[];\n className?: string;\n}) {\n if (roles.length === 0) {\n return (\n <span\n data-slot=\"admin-effective-roles-empty\"\n className=\"text-[length:var(--ds-text-caption)] text-[color:var(--ds-text-tertiary)]\"\n >\n No roles\n </span>\n );\n }\n return (\n <div\n data-slot=\"admin-effective-roles\"\n className={cn(\"flex flex-wrap items-center gap-1\", className)}\n >\n {roles.map((entry, index) => {\n const group = sourceGroupOf(entry.source);\n return (\n <Chip\n key={`${entry.role}-${group?.id ?? \"direct\"}-${index}`}\n size=\"sm\"\n variant={group ? \"outline\" : \"accent\"}\n data-role={entry.role}\n data-source={group ? group.id : \"direct\"}\n title={\n group\n ? `${entry.role} — via group ${group.name}`\n : `${entry.role} — assigned directly`\n }\n >\n {entry.role}\n {group ? (\n <span className=\"inline-flex items-center gap-1 text-[color:var(--ds-text-tertiary)]\">\n <Users aria-hidden />\n via {group.name}\n </span>\n ) : null}\n </Chip>\n );\n })}\n </div>\n );\n}\n\n/** A plain list of directly assigned roles, for the group list. */\nexport function DirectRoles({ roles }: { roles: readonly string[] }) {\n if (roles.length === 0) {\n return (\n <span\n data-slot=\"admin-direct-roles-empty\"\n className=\"text-[length:var(--ds-text-caption)] text-[color:var(--ds-text-tertiary)]\"\n >\n None\n </span>\n );\n }\n return (\n <div data-slot=\"admin-direct-roles\" className=\"flex flex-wrap items-center gap-1\">\n {roles.map((role) => (\n <Chip key={role} size=\"sm\" variant=\"accent\" data-role={role}>\n {role}\n </Chip>\n ))}\n </div>\n );\n}\n\nexport interface RoleAssignmentDialogProps {\n /** The principal being edited. A user and a group take the same dialog. */\n principal: AdminPrincipal | null;\n /** The principal's current direct assignment. */\n current: readonly string[];\n onOpenChange: (open: boolean) => void;\n}\n\n/**\n * The one editor for a role assignment, for both principal kinds (DESIGN-D22).\n * It offers the roles the extension declared and nothing else; a role already\n * assigned that the matrix no longer declares is still listed, marked, and\n * removable, because hiding it would leave a grant nobody can see or revoke.\n */\nexport function RoleAssignmentDialog({\n principal,\n current,\n onOpenChange,\n}: RoleAssignmentDialogProps) {\n const { matrix, assignRoles } = useAdminPermissions();\n const [selected, setSelected] = React.useState<readonly string[]>(current);\n const [saving, setSaving] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n\n // Re-seed whenever a different principal is opened — the documented\n // adjust-state-during-render pattern, not an effect, so the dialog never\n // paints one principal's roles under another principal's name.\n const key = principal ? `${principal.kind}:${principal.id}` : null;\n const [seededKey, setSeededKey] = React.useState<string | null>(key);\n if (key !== seededKey) {\n setSeededKey(key);\n setSelected(current);\n setError(null);\n }\n\n const declared = matrix?.roles ?? [];\n const retired = current.filter((role) => !declared.includes(role));\n const options = [...declared, ...retired];\n\n const toggle = (role: string, checked: boolean) => {\n setSelected((prev) => (checked ? [...prev, role] : prev.filter((r) => r !== role)));\n };\n\n const save = async () => {\n if (!principal) return;\n setSaving(true);\n setError(null);\n try {\n await assignRoles(principal, [...selected]);\n onOpenChange(false);\n } catch (err) {\n // The envelope's own message, not a house one (§15.6). The optimistic\n // write has already been rolled back by `assignRoles`.\n setError(adminErrorMessage(err));\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <Dialog open={principal !== null} onOpenChange={(open) => !open && onOpenChange(false)}>\n <DialogContent data-slot=\"admin-role-dialog\">\n <DialogHeader>\n <DialogTitle>Edit roles</DialogTitle>\n <DialogDescription>\n {principal?.kind === \"group\" ? (\n <>\n Every member of <strong>{principal.name ?? principal.id}</strong> inherits the roles\n you select here, as soon as they join.\n </>\n ) : (\n <>\n Direct assignment for <strong>{principal?.name ?? principal?.id}</strong>. Roles\n inherited from a group are changed on the group, not here.\n </>\n )}\n </DialogDescription>\n </DialogHeader>\n <DialogBody className=\"flex flex-col gap-2\">\n {error ? <ErrorBanner message={error} data-slot=\"admin-role-dialog-error\" /> : null}\n {options.length === 0 ? (\n <p className=\"text-[length:var(--ds-text-body-sm)] text-[color:var(--ds-muted-foreground)]\">\n This extension has not declared any roles yet.\n </p>\n ) : (\n <ul className=\"flex flex-col gap-0.5\" data-slot=\"admin-role-options\">\n {options.map((role) => {\n const checked = selected.includes(role);\n return (\n <li key={role}>\n <label\n className={cn(\n \"flex cursor-pointer items-center gap-2.5 rounded-[var(--ds-radius-md)] px-2 py-1.5\",\n \"text-[length:var(--ds-text-body-sm)] text-[color:var(--ds-foreground)]\",\n \"transition-colors duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]\",\n \"hover:bg-[var(--ds-hover)]\",\n )}\n >\n {/* No `aria-label`: the wrapping <label> already names\n the box, and Base UI points `aria-labelledby` at it.\n Both would compute the name as \"author author\". */}\n <Checkbox\n checked={checked}\n onCheckedChange={(next) => toggle(role, Boolean(next))}\n />\n <span className=\"flex-1\">{role}</span>\n {declared.includes(role) ? null : (\n <Chip size=\"sm\" variant=\"outline\" title=\"Not in the declared vocabulary\">\n retired\n </Chip>\n )}\n </label>\n </li>\n );\n })}\n </ul>\n )}\n </DialogBody>\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)} disabled={saving}>\n Cancel\n </Button>\n <Button onClick={save} disabled={saving || options.length === 0}>\n {saving ? <Spinner tone=\"inverse\" /> : null}\n Save roles\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ //#region src/components/admin-permissions/roles.d.ts
4
+ /**
5
+ * `/admin/roles` — the extension's own vocabulary, and the group principals
6
+ * that hold it.
7
+ *
8
+ * Two halves. The first is what each declared role grants, read straight off
9
+ * the matrix, so an administrator deciding whether to hand somebody "editor"
10
+ * can see what "editor" means without leaving the screen. The second is group
11
+ * assignment: the other kind of principal a role attaches to (DESIGN-D22,
12
+ * P-42), on the same screen as the users because it is the same decision.
13
+ *
14
+ * The groups list is a **client** source, and the members list is not, for one
15
+ * reason: an org's groups are a short, bounded list that arrives in a single
16
+ * `groups.list()`, so paging it server-side would be a round-trip per keypress
17
+ * for nothing. Where the groups come from — a directory sync, or somebody
18
+ * typing them into cortena-auth — is invisible here by design.
19
+ *
20
+ * The whole section disappears when the api has no `groups`, and equally when
21
+ * it has some and the org has none.
22
+ */
23
+ export declare function AdminRoles(): React.JSX.Element;
24
+ //#endregion
25
+ //# sourceMappingURL=roles.d.ts.map