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.
- package/LICENSE +7 -0
- package/README.md +149 -1
- package/dist/a2ui/views.js +2 -2
- package/dist/agent-chat/a2ui-block.d.ts +60 -0
- package/dist/agent-chat/a2ui-block.js +69 -0
- package/dist/agent-chat/a2ui-block.js.map +1 -0
- package/dist/agent-chat/agui-client.d.ts +40 -0
- package/dist/agent-chat/agui-client.js +251 -0
- package/dist/agent-chat/agui-client.js.map +1 -0
- package/dist/agent-chat/bridge.d.ts +109 -0
- package/dist/agent-chat/bridge.js +349 -0
- package/dist/agent-chat/bridge.js.map +1 -0
- package/dist/agent-chat/session.d.ts +58 -0
- package/dist/agent-chat/session.js +249 -0
- package/dist/agent-chat/session.js.map +1 -0
- package/dist/agent-chat/store.d.ts +67 -0
- package/dist/agent-chat/store.js +548 -0
- package/dist/agent-chat/store.js.map +1 -0
- package/dist/agent-chat/types.d.ts +187 -0
- package/dist/agent-chat/types.js +17 -0
- package/dist/agent-chat/types.js.map +1 -0
- package/dist/agent-chat.d.ts +10 -0
- package/dist/agent-chat.js +10 -0
- package/dist/components/admin-permissions/admin-permissions.d.ts +66 -0
- package/dist/components/admin-permissions/admin-permissions.js +101 -0
- package/dist/components/admin-permissions/admin-permissions.js.map +1 -0
- package/dist/components/admin-permissions/context.d.ts +70 -0
- package/dist/components/admin-permissions/context.js +258 -0
- package/dist/components/admin-permissions/context.js.map +1 -0
- package/dist/components/admin-permissions/index.d.ts +10 -0
- package/dist/components/admin-permissions/licence.d.ts +15 -0
- package/dist/components/admin-permissions/licence.js +78 -0
- package/dist/components/admin-permissions/licence.js.map +1 -0
- package/dist/components/admin-permissions/matrix.d.ts +20 -0
- package/dist/components/admin-permissions/matrix.js +191 -0
- package/dist/components/admin-permissions/matrix.js.map +1 -0
- package/dist/components/admin-permissions/members.d.ts +18 -0
- package/dist/components/admin-permissions/members.js +185 -0
- package/dist/components/admin-permissions/members.js.map +1 -0
- package/dist/components/admin-permissions/role-assignment.d.ts +35 -0
- package/dist/components/admin-permissions/role-assignment.js +174 -0
- package/dist/components/admin-permissions/role-assignment.js.map +1 -0
- package/dist/components/admin-permissions/roles.d.ts +25 -0
- package/dist/components/admin-permissions/roles.js +168 -0
- package/dist/components/admin-permissions/roles.js.map +1 -0
- package/dist/components/admin-permissions/types.d.ts +152 -0
- package/dist/components/admin-permissions/types.js +63 -0
- package/dist/components/admin-permissions/types.js.map +1 -0
- package/dist/components/agent-chat-popup.d.ts +29 -0
- package/dist/components/agent-chat-popup.js +188 -0
- package/dist/components/agent-chat-popup.js.map +1 -0
- package/dist/components/agent-chat.d.ts +143 -0
- package/dist/components/agent-chat.js +578 -0
- package/dist/components/agent-chat.js.map +1 -0
- package/dist/components/app-shell.d.ts +126 -0
- package/dist/components/app-shell.js +297 -0
- package/dist/components/app-shell.js.map +1 -0
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button-link.js +1 -1
- package/dist/components/button.d.ts +2 -2
- package/dist/components/checkbox.d.ts +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js +1 -1
- package/dist/components/consent-screen.d.ts +65 -0
- package/dist/components/consent-screen.js +123 -0
- package/dist/components/consent-screen.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts +15 -1
- package/dist/components/data-table/data-table.js +18 -4
- package/dist/components/data-table/data-table.js.map +1 -1
- package/dist/components/data-table/index.d.ts +4 -4
- package/dist/components/data-table/parts.d.ts +27 -3
- package/dist/components/data-table/parts.js +175 -55
- package/dist/components/data-table/parts.js.map +1 -1
- package/dist/components/data-table/types.d.ts +61 -0
- package/dist/components/data-table/use-data-table.js +91 -6
- package/dist/components/data-table/use-data-table.js.map +1 -1
- package/dist/components/data-table/use-server-source.js +119 -28
- package/dist/components/data-table/use-server-source.js.map +1 -1
- package/dist/components/help-panel.d.ts +131 -0
- package/dist/components/help-panel.js +545 -0
- package/dist/components/help-panel.js.map +1 -0
- package/dist/components/login-screen.d.ts +127 -0
- package/dist/components/login-screen.js +339 -0
- package/dist/components/login-screen.js.map +1 -0
- package/dist/components/session-guard.d.ts +268 -0
- package/dist/components/session-guard.js +632 -0
- package/dist/components/session-guard.js.map +1 -0
- package/dist/components/toast.d.ts +1 -1
- package/dist/core.d.ts +5 -1
- package/dist/core.js +11 -7
- package/dist/data-table.d.ts +13 -4
- package/dist/data-table.js +10 -2
- package/dist/hooks/use-cortena-theme.js +49 -3
- package/dist/hooks/use-cortena-theme.js.map +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.js +21 -8
- package/dist/markdown.d.ts +2 -1
- package/dist/markdown.js +2 -1
- package/package.json +16 -4
- package/src/agent-chat/a2ui-block.ts +118 -0
- package/src/agent-chat/agui-client.ts +405 -0
- package/src/agent-chat/bridge.ts +433 -0
- package/src/agent-chat/session.ts +392 -0
- package/src/agent-chat/store.ts +738 -0
- package/src/agent-chat/types.ts +213 -0
- package/src/components/admin-permissions/admin-permissions.tsx +130 -0
- package/src/components/admin-permissions/context.tsx +376 -0
- package/src/components/admin-permissions/index.tsx +32 -0
- package/src/components/admin-permissions/licence.tsx +84 -0
- package/src/components/admin-permissions/matrix.tsx +257 -0
- package/src/components/admin-permissions/members.tsx +204 -0
- package/src/components/admin-permissions/role-assignment.tsx +239 -0
- package/src/components/admin-permissions/roles.tsx +169 -0
- package/src/components/admin-permissions/types.ts +231 -0
- package/src/components/agent-chat-popup.tsx +289 -0
- package/src/components/agent-chat.tsx +843 -0
- package/src/components/app-shell.tsx +502 -0
- package/src/components/consent-screen.tsx +239 -0
- package/src/components/data-table/data-table.tsx +36 -0
- package/src/components/data-table/index.tsx +6 -1
- package/src/components/data-table/parts.tsx +223 -47
- package/src/components/data-table/types.ts +68 -0
- package/src/components/data-table/use-data-table.ts +152 -4
- package/src/components/data-table/use-server-source.ts +150 -12
- package/src/components/help-panel.tsx +765 -0
- package/src/components/login-screen.tsx +479 -0
- package/src/components/session-guard.tsx +1071 -0
- package/src/entries/agent-chat.ts +113 -0
- package/src/entries/core.ts +8 -0
- package/src/entries/data-table.ts +41 -0
- package/src/entries/markdown.ts +25 -0
- package/src/hooks/use-cortena-theme.ts +63 -4
- package/src/index.ts +6 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport contract `AgentChat` is written against.
|
|
3
|
+
*
|
|
4
|
+
* Everything the chat surface needs from a server is behind `AgentChatClient`,
|
|
5
|
+
* and nothing in the interface names AG-UI, HTTP or React. That is what makes
|
|
6
|
+
* the surface testable — `test/agent-chat.test.tsx` drives it from a fake that
|
|
7
|
+
* replays scripted event sequences — and it is what keeps `@ag-ui/client` out
|
|
8
|
+
* of every module except `./agui-client.ts`.
|
|
9
|
+
*
|
|
10
|
+
* The three sinks are subscriptions rather than constructor callbacks so one
|
|
11
|
+
* client can outlive several mounts of the component, which is what a pop-up
|
|
12
|
+
* that collapses and reopens mid-run does.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { A2UIChatBlock } from "./a2ui-block";
|
|
16
|
+
|
|
17
|
+
/* ── what a session is ───────────────────────────────────────────────────── */
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A row of `sessions.list`.
|
|
21
|
+
*
|
|
22
|
+
* Titles fall back in a fixed order (§17.2 of the extension how-to):
|
|
23
|
+
* `label`, `autoTitle`, `derivedTitle`, `displayName`, `lastMessagePreview`,
|
|
24
|
+
* then "New conversation". `sessionTitle` below is that order, in one place.
|
|
25
|
+
*/
|
|
26
|
+
export interface AgentSession {
|
|
27
|
+
/** The session key, verbatim — this is also the AG-UI `threadId`. */
|
|
28
|
+
key: string;
|
|
29
|
+
label?: string | null;
|
|
30
|
+
autoTitle?: string | null;
|
|
31
|
+
derivedTitle?: string | null;
|
|
32
|
+
displayName?: string | null;
|
|
33
|
+
lastMessagePreview?: string | null;
|
|
34
|
+
updatedAt?: number | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The title to show for a session row. */
|
|
38
|
+
export function sessionTitle(session: AgentSession): string {
|
|
39
|
+
for (const candidate of [
|
|
40
|
+
session.label,
|
|
41
|
+
session.autoTitle,
|
|
42
|
+
session.derivedTitle,
|
|
43
|
+
session.displayName,
|
|
44
|
+
session.lastMessagePreview,
|
|
45
|
+
]) {
|
|
46
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
47
|
+
return candidate.trim();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return "New conversation";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ── what a message is ───────────────────────────────────────────────────── */
|
|
54
|
+
|
|
55
|
+
export interface AgentChatMessage {
|
|
56
|
+
id: string;
|
|
57
|
+
role: "user" | "assistant";
|
|
58
|
+
text: string;
|
|
59
|
+
timestamp: number;
|
|
60
|
+
attachments?: string[];
|
|
61
|
+
/** The chain of thought behind this turn, collapsed by default. */
|
|
62
|
+
thinkingText?: string;
|
|
63
|
+
isError?: boolean;
|
|
64
|
+
/** True when the wire record was a tool call or a tool result; hidden. */
|
|
65
|
+
isToolMessage?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The run that produced this message. Absent on anything loaded from
|
|
68
|
+
* history. A `final` folds into the previous bubble only when the two share
|
|
69
|
+
* this value, so one run's answer cannot be rewritten by the next one's.
|
|
70
|
+
*/
|
|
71
|
+
runId?: string;
|
|
72
|
+
/** UI the agent drew, carried outside the prose. */
|
|
73
|
+
a2ui?: A2UIChatBlock[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ── the three sinks ─────────────────────────────────────────────────────── */
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* One chat event. The same `{ state: "delta" | "final" | "error" }` shape
|
|
80
|
+
* cortenacore broadcasts, so a client for a different transport has a
|
|
81
|
+
* documented target to hit.
|
|
82
|
+
*
|
|
83
|
+
* `delta` carries a SNAPSHOT, not an increment: every delta repeats all the
|
|
84
|
+
* text, all the thinking and every A2UI block seen so far. Two transports that
|
|
85
|
+
* disagree about this leave the store in different states for the same run.
|
|
86
|
+
*/
|
|
87
|
+
export interface AgentChatEvent {
|
|
88
|
+
runId: string;
|
|
89
|
+
sessionKey: string;
|
|
90
|
+
seq: number;
|
|
91
|
+
state: "delta" | "final" | "error";
|
|
92
|
+
message?: {
|
|
93
|
+
role?: string;
|
|
94
|
+
content?: Array<{ type: string; text?: string; thinking?: string; a2ui?: unknown }>;
|
|
95
|
+
timestamp?: number;
|
|
96
|
+
};
|
|
97
|
+
errorMessage?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** One step of the tool strip. */
|
|
101
|
+
export interface AgentToolEvent {
|
|
102
|
+
event: "tool.start" | "tool.args" | "tool.progress" | "tool.complete" | "tool.error";
|
|
103
|
+
data: {
|
|
104
|
+
id?: string;
|
|
105
|
+
name?: string;
|
|
106
|
+
/** `tool.args`: the call's input, parsed when it was valid JSON. */
|
|
107
|
+
input?: unknown;
|
|
108
|
+
output?: unknown;
|
|
109
|
+
startedAt?: number;
|
|
110
|
+
completedAt?: number;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Decisions cortenacore accepts on `POST /agui/approval`. */
|
|
115
|
+
export type AgentApprovalDecision = "allow-once" | "allow-always" | "deny";
|
|
116
|
+
|
|
117
|
+
export interface AgentApprovalRequest {
|
|
118
|
+
id: string;
|
|
119
|
+
/** The command, or the tool, the approval is about. */
|
|
120
|
+
toolName: string;
|
|
121
|
+
args: Record<string, unknown>;
|
|
122
|
+
timestamp: number;
|
|
123
|
+
/**
|
|
124
|
+
* The run and the session this request arrived on, captured when it arrived.
|
|
125
|
+
*
|
|
126
|
+
* A decision is bound to them rather than resolved against whatever run
|
|
127
|
+
* happens to be in flight when the user presses a button. The two come
|
|
128
|
+
* apart routinely: an approval pauses a run, the user opens another session
|
|
129
|
+
* in the drawer or starts a second turn, and the client's idea of "the
|
|
130
|
+
* current thread" has moved on. Answering "allow" against the wrong thread
|
|
131
|
+
* is a command approved in a conversation nobody was asked about.
|
|
132
|
+
*/
|
|
133
|
+
runId: string;
|
|
134
|
+
sessionKey: string;
|
|
135
|
+
/** Set when a decision was made and the server would not take it. */
|
|
136
|
+
error?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Where an approval came from, and therefore where the decision must go. */
|
|
140
|
+
export interface AgentApprovalContext {
|
|
141
|
+
runId: string;
|
|
142
|
+
sessionKey: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export type AgentApprovalEvent =
|
|
146
|
+
| { type: "request"; request: AgentApprovalRequest }
|
|
147
|
+
/** `decision: null` is an expiry. It is treated as a denial. */
|
|
148
|
+
| { type: "resolved"; id: string; decision: AgentApprovalDecision | null };
|
|
149
|
+
|
|
150
|
+
/* ── the client ──────────────────────────────────────────────────────────── */
|
|
151
|
+
|
|
152
|
+
export interface AgentChatSendInput {
|
|
153
|
+
/** The session key to run in. `threadId` is this value verbatim. */
|
|
154
|
+
sessionKey: string;
|
|
155
|
+
/** What the user typed. Empty for an action sent by rendered UI. */
|
|
156
|
+
message: string;
|
|
157
|
+
/** The run id; also the idempotency key. */
|
|
158
|
+
runId: string;
|
|
159
|
+
attachments?: unknown[];
|
|
160
|
+
thinking?: "low" | "medium" | "high";
|
|
161
|
+
model?: { provider: string; model: string };
|
|
162
|
+
/** A click inside an A2UI surface. Never a sentence the user appears to type. */
|
|
163
|
+
a2uiAction?: { surfaceId: string; componentId: string; name: string; payload: Record<string, unknown> };
|
|
164
|
+
/** The same, from inside a rendered MCP App. */
|
|
165
|
+
mcpAppAction?: Record<string, unknown>;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Everything `AgentChat` needs from a server.
|
|
170
|
+
*
|
|
171
|
+
* `request` is deliberately a method name plus params rather than a URL: the
|
|
172
|
+
* two calls the chat makes — `sessions.list` and `chat.history` — are control
|
|
173
|
+
* plane methods, and a host that proxies them through its own gateway chooses
|
|
174
|
+
* its own paths.
|
|
175
|
+
*/
|
|
176
|
+
export interface AgentChatClient {
|
|
177
|
+
/** The agent this client talks to. Session keys are `agent:<agentId>:…`. */
|
|
178
|
+
readonly agentId: string;
|
|
179
|
+
/** Start a run. Resolves when the run has been accepted, not when it ends. */
|
|
180
|
+
send(input: AgentChatSendInput): Promise<void>;
|
|
181
|
+
/** Stop the run in flight. Best-effort; local state resets either way. */
|
|
182
|
+
abort(params?: { runId?: string; sessionKey?: string }): Promise<void>;
|
|
183
|
+
/**
|
|
184
|
+
* Answer an approval. `context` is the `{ runId, sessionKey }` the request
|
|
185
|
+
* carried, not the run in flight — see `AgentApprovalRequest`. Rejects when
|
|
186
|
+
* the server refuses the decision, so the caller can put the card back.
|
|
187
|
+
*/
|
|
188
|
+
resolveApproval(
|
|
189
|
+
id: string,
|
|
190
|
+
decision: AgentApprovalDecision,
|
|
191
|
+
context: AgentApprovalContext,
|
|
192
|
+
): Promise<void>;
|
|
193
|
+
/** `sessions.list` and `chat.history`. */
|
|
194
|
+
request<T = unknown>(method: string, params: Record<string, unknown>): Promise<T>;
|
|
195
|
+
/** Chat deltas, finals and errors. Returns an unsubscribe. */
|
|
196
|
+
onChat(sink: (event: AgentChatEvent) => void): () => void;
|
|
197
|
+
/** Tool starts, arguments, progress and results. Returns an unsubscribe. */
|
|
198
|
+
onTool(sink: (event: AgentToolEvent) => void): () => void;
|
|
199
|
+
/** Approval requests and resolutions. Returns an unsubscribe. */
|
|
200
|
+
onApproval(sink: (event: AgentApprovalEvent) => void): () => void;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** What `sessions.list` answers. */
|
|
204
|
+
export interface AgentSessionsListResponse {
|
|
205
|
+
sessions?: AgentSession[];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** What `chat.history` answers. `messages` are raw transcript records. */
|
|
209
|
+
export interface AgentChatHistoryResponse {
|
|
210
|
+
messages?: unknown[];
|
|
211
|
+
hasMore?: boolean;
|
|
212
|
+
total?: number;
|
|
213
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { PageHeader } from "@/components/page-header";
|
|
5
|
+
import { Tabs, TabsList, TabsTrigger } from "@/components/tabs";
|
|
6
|
+
import { cn } from "@/lib/cn";
|
|
7
|
+
import { AdminPermissionsProvider } from "./context";
|
|
8
|
+
import { AdminLicenceScreen } from "./licence";
|
|
9
|
+
import { AdminMatrix } from "./matrix";
|
|
10
|
+
import { AdminMembers } from "./members";
|
|
11
|
+
import { AdminRoles } from "./roles";
|
|
12
|
+
import {
|
|
13
|
+
ADMIN_ROUTES,
|
|
14
|
+
ADMIN_ROUTE_LABELS,
|
|
15
|
+
type AdminPermissionsApi,
|
|
16
|
+
type AdminRoute,
|
|
17
|
+
} from "./types";
|
|
18
|
+
|
|
19
|
+
export interface AdminPermissionsProps
|
|
20
|
+
extends Omit<React.ComponentProps<"div">, "children" | "title"> {
|
|
21
|
+
/** Everything the screen reads and writes. The extension's, entirely. */
|
|
22
|
+
api: AdminPermissionsApi;
|
|
23
|
+
/**
|
|
24
|
+
* The screen to show. Pass it and the host's router owns the URL; omit it
|
|
25
|
+
* and the composition keeps the current screen in its own state.
|
|
26
|
+
*/
|
|
27
|
+
route?: AdminRoute;
|
|
28
|
+
/** The screen to open with when `route` is not passed. @default "members" */
|
|
29
|
+
defaultRoute?: AdminRoute;
|
|
30
|
+
/**
|
|
31
|
+
* Fired when the administrator picks another screen. Always fired, whether
|
|
32
|
+
* or not `route` is controlled, so a host can push the URL without also
|
|
33
|
+
* having to hold the state.
|
|
34
|
+
*/
|
|
35
|
+
onNavigate?: (route: AdminRoute) => void;
|
|
36
|
+
/** `false` hides the title block, for a host that renders its own. */
|
|
37
|
+
header?: boolean;
|
|
38
|
+
title?: React.ReactNode;
|
|
39
|
+
description?: React.ReactNode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* AdminPermissions — the consistent admin screen for every Cortena extension
|
|
44
|
+
* (§13, DESIGN-D11 as amended by DESIGN-D22; audit rules P-14, P-42, P-43).
|
|
45
|
+
*
|
|
46
|
+
* Four screens behind `admin/*`: `/admin/members`, `/admin/roles`,
|
|
47
|
+
* `/admin/licence`, `/admin/permissions`. It renders whatever roles and
|
|
48
|
+
* actions the extension declares and **defines none of its own** — there is
|
|
49
|
+
* not one role name, action name or grant in this folder. That is exactly what
|
|
50
|
+
* lets a verification workflow and a work tracker have completely different
|
|
51
|
+
* role models and still look like one product.
|
|
52
|
+
*
|
|
53
|
+
* ```tsx
|
|
54
|
+
* <Route path="admin/*" element={
|
|
55
|
+
* <AdminPermissions
|
|
56
|
+
* api={adminApi}
|
|
57
|
+
* route={screenFromPath(location.pathname)}
|
|
58
|
+
* onNavigate={(next) => navigate(`/admin/${next}`)}
|
|
59
|
+
* />
|
|
60
|
+
* } />
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* **Router-agnostic, by a `route` prop plus `onNavigate`.** The component
|
|
64
|
+
* imports no router and never touches `history` or `location`. Those four
|
|
65
|
+
* paths are real URLs — deep-linkable, bookmarkable, and answerable to the
|
|
66
|
+
* back button — and only the host's router can own a URL; a component that
|
|
67
|
+
* pushed history itself would fight whichever router the extension chose.
|
|
68
|
+
* `onNavigate` reports the intent, the host turns it into a navigation, and
|
|
69
|
+
* `route` comes back as the answer.
|
|
70
|
+
*
|
|
71
|
+
* An internal fallback exists for the case with no router in it — the guide,
|
|
72
|
+
* a test, a small app: omit `route` and the composition keeps the screen in
|
|
73
|
+
* its own state. `onNavigate` still fires, so a host can start uncontrolled
|
|
74
|
+
* and add a router later without changing anything else.
|
|
75
|
+
*
|
|
76
|
+
* The navigation is a real tablist rather than a row of links, because the
|
|
77
|
+
* choice is between panels of one screen; the host is free to hide it with
|
|
78
|
+
* `header={false}` and drive `route` from its own chrome.
|
|
79
|
+
*/
|
|
80
|
+
export function AdminPermissions({
|
|
81
|
+
api,
|
|
82
|
+
route,
|
|
83
|
+
defaultRoute = "members",
|
|
84
|
+
onNavigate,
|
|
85
|
+
header = true,
|
|
86
|
+
title = "Administration",
|
|
87
|
+
description = "Who is in this organisation, what your roles let them do, and the licence behind it.",
|
|
88
|
+
className,
|
|
89
|
+
...props
|
|
90
|
+
}: AdminPermissionsProps) {
|
|
91
|
+
const [internal, setInternal] = React.useState<AdminRoute>(defaultRoute);
|
|
92
|
+
const active = route ?? internal;
|
|
93
|
+
|
|
94
|
+
const go = (next: AdminRoute) => {
|
|
95
|
+
if (route === undefined) setInternal(next);
|
|
96
|
+
onNavigate?.(next);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<AdminPermissionsProvider api={api}>
|
|
101
|
+
<div
|
|
102
|
+
data-slot="admin-permissions"
|
|
103
|
+
data-route={active}
|
|
104
|
+
className={cn("flex flex-col gap-5", className)}
|
|
105
|
+
{...props}
|
|
106
|
+
>
|
|
107
|
+
{header ? <PageHeader title={title} description={description} /> : null}
|
|
108
|
+
<Tabs
|
|
109
|
+
value={active}
|
|
110
|
+
onValueChange={(value) => go(value as AdminRoute)}
|
|
111
|
+
data-slot="admin-permissions-nav"
|
|
112
|
+
>
|
|
113
|
+
<TabsList aria-label="Administration screens">
|
|
114
|
+
{ADMIN_ROUTES.map((item) => (
|
|
115
|
+
<TabsTrigger key={item} value={item} data-route={item}>
|
|
116
|
+
{ADMIN_ROUTE_LABELS[item]}
|
|
117
|
+
</TabsTrigger>
|
|
118
|
+
))}
|
|
119
|
+
</TabsList>
|
|
120
|
+
</Tabs>
|
|
121
|
+
<div data-slot="admin-permissions-screen">
|
|
122
|
+
{active === "members" ? <AdminMembers /> : null}
|
|
123
|
+
{active === "roles" ? <AdminRoles /> : null}
|
|
124
|
+
{active === "licence" ? <AdminLicenceScreen /> : null}
|
|
125
|
+
{active === "permissions" ? <AdminMatrix /> : null}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</AdminPermissionsProvider>
|
|
129
|
+
);
|
|
130
|
+
}
|