@stapel/workspaces-react 0.4.1 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @stapel/workspaces-react
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6ef6c44: Gate top-level "the caller's own …" query hooks on `@stapel/core`'s new
8
+ `useActiveSessionReady()` (owner-diagnosed live incident, 2026-07-17): a hook
9
+ with no natural `enabled` condition of its own (`useWorkspaces`, `useWallet`,
10
+ `useTransactions`, `useSubscription`, `useNotificationFeed`/
11
+ `useInfiniteNotificationFeed`, `useCalendar`/`useEvents`/`useAvailability`,
12
+ `useRecordings`) fires the instant a component mounts — which used to race a
13
+ session still bootstrapping and read a live one as "expired". Detail hooks
14
+ keyed by an id (`useWorkspace`/`useMembers`/`useEvent`/`useRecording`) now
15
+ ALSO gate on session readiness in addition to their existing non-empty-id
16
+ check, since an id can be known synchronously (e.g. a URL param) before the
17
+ session settles.
18
+
19
+ Deliberately NOT gated: `useCatalog` (billing) and `useLanguages` (profiles,
20
+ unaffected by this changeset but worth noting for symmetry) — both are
21
+ public reference lists a signed-out visitor legitimately needs.
22
+
23
+ Zero manual wiring at any call site: `useActiveSessionReady()` reads
24
+ whichever `SessionManager` a session-owning module (e.g.
25
+ `@stapel/auth-react`'s `createAuthRuntime`) registered as "active", and
26
+ defaults to `true` (never blocks) when no such module exists in the host at
27
+ all.
28
+
29
+ ## 0.5.0
30
+
31
+ ### Minor Changes
32
+
33
+ - f15c6be: Add the pair's first `/default` settings skin: `WorkspaceSettings` (rename, danger-zone delete gated to the `owner` role) and `MembersManager` (roster with per-row role change and removal, plus an invite dialog for emails + role — all gated by a host-supplied `canManage` prop derived from the caller's own membership).
34
+
3
35
  ## 0.4.1
4
36
 
5
37
  ### Patch Changes
@@ -0,0 +1,14 @@
1
+ import type { ReactElement } from "react";
2
+ export interface MembersManagerProps {
3
+ workspaceId: string;
4
+ /**
5
+ * Whether the caller may invite, change roles, and remove members. The
6
+ * host already knows the caller's own role in this workspace (e.g. from
7
+ * `useWorkspace(workspaceId).data?.my_role`); this component doesn't
8
+ * re-derive it — pass `false` for a read-only roster (e.g. a `viewer`/
9
+ * `member` looking at their teammates). Default `true`.
10
+ */
11
+ canManage?: boolean;
12
+ }
13
+ export declare function MembersManager(props: MembersManagerProps): ReactElement;
14
+ //# sourceMappingURL=MembersManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MembersManager.d.ts","sourceRoot":"","sources":["../../src/default/MembersManager.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAQ1C,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAkBD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,YAAY,CA4IvE"}
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * `<MembersManager/>` — default skin for the "members & roles" settings
4
+ * screen (owner directive: "настройки воркспейса (имя/участники/роли/
5
+ * инвайты)"). Built entirely on this pair's EXISTING `Members` headless
6
+ * wrapper (`useMembers`/`useInviteMembers`/`useUpdateMemberRole`/
7
+ * `useRemoveMember`) — no new backend surface.
8
+ */
9
+ import { useState } from "react";
10
+ import { Alert, Button, Card, Input, Modal, Popconfirm, Select, Table, Typography } from "antd";
11
+ import { useT } from "@stapel/core";
12
+ import { Members } from "../headless/Members.js";
13
+ import { WORKSPACES_I18N_KEYS } from "../i18n/keys.js";
14
+ const ROLES = ["owner", "admin", "member", "viewer"];
15
+ const ROLE_KEY = {
16
+ owner: "roleOwner",
17
+ admin: "roleAdmin",
18
+ member: "roleMember",
19
+ viewer: "roleViewer",
20
+ };
21
+ function isWorkspaceRole(value) {
22
+ return ROLES.includes(value);
23
+ }
24
+ export function MembersManager(props) {
25
+ const t = useT();
26
+ const canManage = props.canManage ?? true;
27
+ const [inviteOpen, setInviteOpen] = useState(false);
28
+ const [emailsText, setEmailsText] = useState("");
29
+ const [inviteRole, setInviteRole] = useState("member");
30
+ return (_jsx(Members, { workspaceId: props.workspaceId, children: ({ members, isLoading, isError, error, invite, isInviting, updateRole, remove }) => {
31
+ function openInvite() {
32
+ setEmailsText("");
33
+ setInviteRole("member");
34
+ setInviteOpen(true);
35
+ }
36
+ function submitInvite() {
37
+ const emails = emailsText
38
+ .split(/[,\s]+/)
39
+ .map((e) => e.trim())
40
+ .filter((e) => e.length > 0);
41
+ if (emails.length === 0)
42
+ return;
43
+ invite({ emails, role: inviteRole });
44
+ setInviteOpen(false);
45
+ }
46
+ const columns = [
47
+ {
48
+ title: "Email",
49
+ dataIndex: "email",
50
+ key: "email",
51
+ render: (value) => value ?? "—",
52
+ },
53
+ {
54
+ title: t(WORKSPACES_I18N_KEYS.membersInviteRoleLabel),
55
+ key: "role",
56
+ render: (_, member) => {
57
+ const role = isWorkspaceRole(member.role) ? member.role : "member";
58
+ return canManage ? (_jsx(Select, { value: role, style: { width: 140 }, onChange: (next) => updateRole({ userId: member.user_id, role: next }), options: ROLES.map((r) => ({ value: r, label: t(WORKSPACES_I18N_KEYS[ROLE_KEY[r]]) })) })) : (_jsx("span", { children: t(WORKSPACES_I18N_KEYS[ROLE_KEY[role]]) }));
59
+ },
60
+ },
61
+ ...(canManage
62
+ ? [
63
+ {
64
+ title: "",
65
+ key: "actions",
66
+ render: (_, member) => (_jsx(Popconfirm, { title: t(WORKSPACES_I18N_KEYS.membersRemoveConfirm), onConfirm: () => remove(member.user_id), children: _jsx(Button, { danger: true, type: "link", children: t(WORKSPACES_I18N_KEYS.membersRemove) }) })),
67
+ },
68
+ ]
69
+ : []),
70
+ ];
71
+ return (_jsxs(Card, { "data-testid": "members-manager", children: [_jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 16 }, children: [_jsxs("div", { children: [_jsx(Typography.Title, { level: 4, style: { marginTop: 0 }, children: t(WORKSPACES_I18N_KEYS.membersTitle) }), _jsx(Typography.Text, { type: "secondary", children: t(WORKSPACES_I18N_KEYS.membersSubtitle) })] }), canManage && (_jsx(Button, { type: "primary", onClick: openInvite, "data-analytics": "none", "data-analytics-reason": "local-ui-open-invite-dialog", children: t(WORKSPACES_I18N_KEYS.membersInvite) }))] }), isError && error && (_jsx(Alert, { style: { marginTop: 12 }, type: "error", showIcon: true, message: error.message })), _jsx(Table, { style: { marginTop: 16 }, size: "small", loading: isLoading, rowKey: (member) => member.id, dataSource: members, columns: columns, pagination: false }), _jsx(Modal, { title: t(WORKSPACES_I18N_KEYS.membersInviteDialogTitle), open: inviteOpen, onCancel: () => setInviteOpen(false), onOk: submitInvite, okButtonProps: { loading: isInviting, disabled: emailsText.trim().length === 0 }, children: _jsxs("div", { style: { display: "grid", gap: 12 }, children: [_jsxs("div", { children: [_jsx(Typography.Text, { children: t(WORKSPACES_I18N_KEYS.membersInviteEmailsLabel) }), _jsx(Input, { value: emailsText, onChange: (e) => setEmailsText(e.target.value), placeholder: t(WORKSPACES_I18N_KEYS.membersInviteEmailsPlaceholder) })] }), _jsxs("div", { children: [_jsx(Typography.Text, { children: t(WORKSPACES_I18N_KEYS.membersInviteRoleLabel) }), _jsx(Select, { value: inviteRole, onChange: setInviteRole, style: { width: "100%" }, options: ROLES.filter((r) => r !== "owner").map((r) => ({
72
+ value: r,
73
+ label: t(WORKSPACES_I18N_KEYS[ROLE_KEY[r]]),
74
+ })) })] })] }) })] }));
75
+ } }));
76
+ }
77
+ //# sourceMappingURL=MembersManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MembersManager.js","sourceRoot":"","sources":["../../src/default/MembersManager.tsx"],"names":[],"mappings":";AAAA;;;;;;GAMG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEhG,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAcvD,MAAM,KAAK,GAA6B,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAE/E,MAAM,QAAQ,GAGV;IACF,KAAK,EAAE,WAAW;IAClB,KAAK,EAAE,WAAW;IAClB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,YAAY;CACrB,CAAC;AAEF,SAAS,eAAe,CAAC,KAAa;IACpC,OAAQ,KAA2B,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAA0B;IACvD,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC;IAC1C,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACjD,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAgB,QAAQ,CAAC,CAAC;IAEtE,OAAO,CACL,KAAC,OAAO,IAAC,WAAW,EAAE,KAAK,CAAC,WAAW,YACpC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE;YAClF,SAAS,UAAU;gBACjB,aAAa,CAAC,EAAE,CAAC,CAAC;gBAClB,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACxB,aAAa,CAAC,IAAI,CAAC,CAAC;YACtB,CAAC;YAED,SAAS,YAAY;gBACnB,MAAM,MAAM,GAAG,UAAU;qBACtB,KAAK,CAAC,QAAQ,CAAC;qBACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;qBACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC/B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;oBAAE,OAAO;gBAChC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;gBACrC,aAAa,CAAC,KAAK,CAAC,CAAC;YACvB,CAAC;YAED,MAAM,OAAO,GAAkC;gBAC7C;oBACE,KAAK,EAAE,OAAO;oBACd,SAAS,EAAE,OAAO;oBAClB,GAAG,EAAE,OAAO;oBACZ,MAAM,EAAE,CAAC,KAAoB,EAAE,EAAE,CAAC,KAAK,IAAI,GAAG;iBAC/C;gBACD;oBACE,KAAK,EAAE,CAAC,CAAC,oBAAoB,CAAC,sBAAsB,CAAC;oBACrD,GAAG,EAAE,MAAM;oBACX,MAAM,EAAE,CAAC,CAAU,EAAE,MAAc,EAAE,EAAE;wBACrC,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;wBACnE,OAAO,SAAS,CAAC,CAAC,CAAC,CACjB,KAAC,MAAM,IACL,KAAK,EAAE,IAAI,EACX,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EACtE,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GACtF,CACH,CAAC,CAAC,CAAC,CACF,yBAAO,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,GAAQ,CACvD,CAAC;oBACJ,CAAC;iBACF;gBACD,GAAG,CAAC,SAAS;oBACX,CAAC,CAAC;wBACE;4BACE,KAAK,EAAE,EAAE;4BACT,GAAG,EAAE,SAAS;4BACd,MAAM,EAAE,CAAC,CAAU,EAAE,MAAc,EAAE,EAAE,CAAC,CACtC,KAAC,UAAU,IACT,KAAK,EAAE,CAAC,CAAC,oBAAoB,CAAC,oBAAoB,CAAC,EACnD,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,YAEvC,KAAC,MAAM,IAAC,MAAM,QAAC,IAAI,EAAC,MAAM,YACvB,CAAC,CAAC,oBAAoB,CAAC,aAAa,CAAC,GAC/B,GACE,CACd;yBACF;qBACF;oBACH,CAAC,CAAC,EAAE,CAAC;aACR,CAAC;YAEF,OAAO,CACL,MAAC,IAAI,mBAAa,iBAAiB,aACjC,eAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,EAAE,EAAE,EAAE,aACjG,0BACE,KAAC,UAAU,CAAC,KAAK,IAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,YAChD,CAAC,CAAC,oBAAoB,CAAC,YAAY,CAAC,GACpB,EACnB,KAAC,UAAU,CAAC,IAAI,IAAC,IAAI,EAAC,WAAW,YAAE,CAAC,CAAC,oBAAoB,CAAC,eAAe,CAAC,GAAmB,IACzF,EACL,SAAS,IAAI,CACZ,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,OAAO,EAAE,UAAU,oBACJ,MAAM,2BACC,6BAA6B,YAElD,CAAC,CAAC,oBAAoB,CAAC,aAAa,CAAC,GAC/B,CACV,IACG,EAEL,OAAO,IAAI,KAAK,IAAI,CACnB,KAAC,KAAK,IAAC,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,IAAI,EAAC,OAAO,EAAC,QAAQ,QAAC,OAAO,EAAE,KAAK,CAAC,OAAO,GAAI,CAClF,EAED,KAAC,KAAK,IACJ,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,EACxB,IAAI,EAAC,OAAO,EACZ,OAAO,EAAE,SAAS,EAClB,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,EAC7B,UAAU,EAAE,OAAmB,EAC/B,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,KAAK,GACjB,EAEF,KAAC,KAAK,IACJ,KAAK,EAAE,CAAC,CAAC,oBAAoB,CAAC,wBAAwB,CAAC,EACvD,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,EACpC,IAAI,EAAE,YAAY,EAClB,aAAa,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,YAEhF,eAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,aACtC,0BACE,KAAC,UAAU,CAAC,IAAI,cAAE,CAAC,CAAC,oBAAoB,CAAC,wBAAwB,CAAC,GAAmB,EACrF,KAAC,KAAK,IACJ,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAC9C,WAAW,EAAE,CAAC,CAAC,oBAAoB,CAAC,8BAA8B,CAAC,GACnE,IACE,EACN,0BACE,KAAC,UAAU,CAAC,IAAI,cAAE,CAAC,CAAC,oBAAoB,CAAC,sBAAsB,CAAC,GAAmB,EACnF,KAAC,MAAM,IACL,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EACxB,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gDACtD,KAAK,EAAE,CAAC;gDACR,KAAK,EAAE,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;6CAC5C,CAAC,CAAC,GACH,IACE,IACF,GACA,IACH,CACR,CAAC;QACJ,CAAC,GACO,CACX,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ReactElement } from "react";
2
+ export interface WorkspaceSettingsProps {
3
+ workspaceId: string;
4
+ /** Called after a successful delete — the host navigates away / switches
5
+ * to another workspace. Not called on cancel or failure. */
6
+ onDeleted?(): void;
7
+ }
8
+ export declare function WorkspaceSettings(props: WorkspaceSettingsProps): ReactElement;
9
+ //# sourceMappingURL=WorkspaceSettings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkspaceSettings.d.ts","sourceRoot":"","sources":["../../src/default/WorkspaceSettings.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAO1C,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB;gEAC4D;IAC5D,SAAS,CAAC,IAAI,IAAI,CAAC;CACpB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,YAAY,CAwG7E"}
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * `<WorkspaceSettings/>` — default skin for the "workspace" settings screen
4
+ * (owner directive: workspace name/general settings, one of the settings
5
+ * surfaces this liberary pair owns). Built entirely on this pair's EXISTING
6
+ * hooks (`useWorkspace`, `useUpdateWorkspace`, `useDeleteWorkspace`) — no new
7
+ * backend surface. Participants/roles live in the sibling
8
+ * `<MembersManager/>` (a workspace's settings page composes both).
9
+ */
10
+ import { useEffect, useState } from "react";
11
+ import { Alert, Badge, Button, Card, Input, Popconfirm, Spin, Typography } from "antd";
12
+ import { useT } from "@stapel/core";
13
+ import { useWorkspace } from "../model/queries.js";
14
+ import { useUpdateWorkspace, useDeleteWorkspace } from "../model/mutations.js";
15
+ import { WORKSPACES_I18N_KEYS } from "../i18n/keys.js";
16
+ export function WorkspaceSettings(props) {
17
+ const t = useT();
18
+ const query = useWorkspace(props.workspaceId);
19
+ const updateMutation = useUpdateWorkspace(props.workspaceId);
20
+ const deleteMutation = useDeleteWorkspace();
21
+ const workspace = query.data;
22
+ const [name, setName] = useState("");
23
+ useEffect(() => {
24
+ if (workspace)
25
+ setName(workspace.name);
26
+ }, [workspace]);
27
+ function handleSave() {
28
+ updateMutation.mutate({ name: name.trim() });
29
+ }
30
+ function handleDelete() {
31
+ deleteMutation.mutate(props.workspaceId, {
32
+ onSuccess: () => props.onDeleted?.(),
33
+ });
34
+ }
35
+ if (query.isLoading && !workspace) {
36
+ return _jsx(Spin, { "data-testid": "workspace-settings-loading" });
37
+ }
38
+ if (!workspace) {
39
+ return (_jsx(Alert, { "data-testid": "workspace-settings-error", type: "error", showIcon: true, message: query.error?.message ?? t(WORKSPACES_I18N_KEYS.unknownError) }));
40
+ }
41
+ const isOwner = workspace.my_role === "owner";
42
+ return (_jsxs("div", { "data-testid": "workspace-settings", style: { display: "grid", gap: 16 }, children: [_jsxs(Card, { children: [_jsx(Typography.Title, { level: 4, style: { marginTop: 0 }, children: t(WORKSPACES_I18N_KEYS.settingsTitle) }), _jsx(Typography.Text, { type: "secondary", children: t(WORKSPACES_I18N_KEYS.settingsSubtitle) }), _jsxs("div", { style: { display: "grid", gap: 12, maxWidth: 480, marginTop: 16 }, children: [_jsxs("div", { children: [_jsx(Typography.Text, { children: t(WORKSPACES_I18N_KEYS.fieldName) }), _jsx(Input, { value: name, onChange: (e) => setName(e.target.value), disabled: !isOwner })] }), _jsxs("div", { children: [_jsx(Typography.Text, { children: t(WORKSPACES_I18N_KEYS.fieldSlug) }), _jsx("div", { children: _jsx(Typography.Text, { code: true, children: workspace.slug }) })] }), _jsxs("div", { children: [_jsx(Typography.Text, { children: t(WORKSPACES_I18N_KEYS.fieldType) }), _jsx("div", { children: _jsx(Badge, { status: "default", text: workspace.type }) })] })] }), updateMutation.error && (_jsx(Alert, { style: { marginTop: 12 }, type: "error", showIcon: true, message: updateMutation.error.message })), _jsx(Button, { type: "primary", style: { marginTop: 16 }, loading: updateMutation.isPending, disabled: !isOwner || !name.trim(), onClick: handleSave, "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked(); pairs carry no @stapel/analytics runtime dependency by architecture", children: updateMutation.isPending ? t(WORKSPACES_I18N_KEYS.saving) : t(WORKSPACES_I18N_KEYS.save) })] }), isOwner && (_jsxs(Card, { children: [_jsx(Typography.Title, { level: 5, type: "danger", style: { marginTop: 0 }, children: t(WORKSPACES_I18N_KEYS.dangerZoneTitle) }), _jsx(Popconfirm, { title: t(WORKSPACES_I18N_KEYS.deleteWorkspaceConfirm), onConfirm: handleDelete, okButtonProps: { loading: deleteMutation.isPending, danger: true }, children: _jsx(Button, { danger: true, children: t(WORKSPACES_I18N_KEYS.deleteWorkspace) }) })] }))] }));
43
+ }
44
+ //# sourceMappingURL=WorkspaceSettings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkspaceSettings.js","sourceRoot":"","sources":["../../src/default/WorkspaceSettings.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@stapel/workspaces-react/default` — the pilot default skin for this
3
+ * pair's settings surfaces (mirrors auth-react's `/default` split, §54): a
4
+ * separate entry point so consumers who bring their own visuals never pull
5
+ * `antd` into their bundle; importing this subpath is the opt-in.
6
+ *
7
+ * ```tsx
8
+ * import { WorkspaceSettings, MembersManager } from "@stapel/workspaces-react/default";
9
+ * // under this pair's <WorkspacesProvider> + core <I18nProvider>:
10
+ * <WorkspaceSettings workspaceId={id} />
11
+ * <MembersManager workspaceId={id} />
12
+ * ```
13
+ */
14
+ export { WorkspaceSettings } from "./WorkspaceSettings.js";
15
+ export type { WorkspaceSettingsProps } from "./WorkspaceSettings.js";
16
+ export { MembersManager } from "./MembersManager.js";
17
+ export type { MembersManagerProps } from "./MembersManager.js";
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `@stapel/workspaces-react/default` — the pilot default skin for this
3
+ * pair's settings surfaces (mirrors auth-react's `/default` split, §54): a
4
+ * separate entry point so consumers who bring their own visuals never pull
5
+ * `antd` into their bundle; importing this subpath is the opt-in.
6
+ *
7
+ * ```tsx
8
+ * import { WorkspaceSettings, MembersManager } from "@stapel/workspaces-react/default";
9
+ * // under this pair's <WorkspacesProvider> + core <I18nProvider>:
10
+ * <WorkspaceSettings workspaceId={id} />
11
+ * <MembersManager workspaceId={id} />
12
+ * ```
13
+ */
14
+ export { WorkspaceSettings } from "./WorkspaceSettings.js";
15
+ export { MembersManager } from "./MembersManager.js";
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
@@ -22,6 +22,27 @@ export declare const WORKSPACES_I18N_KEYS: {
22
22
  readonly acceptAccept: "workspaces.accept.accept";
23
23
  readonly acceptAccepting: "workspaces.accept.accepting";
24
24
  readonly acceptAccepted: "workspaces.accept.accepted";
25
+ readonly settingsTitle: "workspaces.settings.title";
26
+ readonly settingsSubtitle: "workspaces.settings.subtitle";
27
+ readonly fieldName: "workspaces.settings.field.name";
28
+ readonly fieldSlug: "workspaces.settings.field.slug";
29
+ readonly fieldType: "workspaces.settings.field.type";
30
+ readonly save: "workspaces.settings.save";
31
+ readonly saving: "workspaces.settings.saving";
32
+ readonly dangerZoneTitle: "workspaces.settings.danger_zone.title";
33
+ readonly deleteWorkspace: "workspaces.settings.danger_zone.delete";
34
+ readonly deleteWorkspaceConfirm: "workspaces.settings.danger_zone.delete_confirm";
35
+ readonly membersTitle: "workspaces.members.title";
36
+ readonly membersSubtitle: "workspaces.members.subtitle";
37
+ readonly membersInviteDialogTitle: "workspaces.members.invite_dialog.title";
38
+ readonly membersInviteEmailsLabel: "workspaces.members.invite_dialog.emails_label";
39
+ readonly membersInviteEmailsPlaceholder: "workspaces.members.invite_dialog.emails_placeholder";
40
+ readonly membersInviteRoleLabel: "workspaces.members.invite_dialog.role_label";
41
+ readonly membersRemoveConfirm: "workspaces.members.remove_confirm";
42
+ readonly roleOwner: "workspaces.members.role.owner";
43
+ readonly roleAdmin: "workspaces.members.role.admin";
44
+ readonly roleMember: "workspaces.members.role.member";
45
+ readonly roleViewer: "workspaces.members.role.viewer";
25
46
  };
26
47
  export type WorkspacesI18nKey = (typeof WORKSPACES_I18N_KEYS)[keyof typeof WORKSPACES_I18N_KEYS];
27
48
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;CAiBvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAC3B,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,OAAO,oBAAoB,CAAC,CAAC;AAEnE;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAkBpC,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,SAAO,GAAG,IAAI,CAE9E"}
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAC3B,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,OAAO,oBAAoB,CAAC,CAAC;AAEnE;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAuCpC,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,SAAO,GAAG,IAAI,CAE9E"}
package/dist/i18n/keys.js CHANGED
@@ -25,6 +25,29 @@ export const WORKSPACES_I18N_KEYS = {
25
25
  acceptAccept: "workspaces.accept.accept",
26
26
  acceptAccepting: "workspaces.accept.accepting",
27
27
  acceptAccepted: "workspaces.accept.accepted",
28
+ // Workspace settings (default skin — WorkspaceSettings)
29
+ settingsTitle: "workspaces.settings.title",
30
+ settingsSubtitle: "workspaces.settings.subtitle",
31
+ fieldName: "workspaces.settings.field.name",
32
+ fieldSlug: "workspaces.settings.field.slug",
33
+ fieldType: "workspaces.settings.field.type",
34
+ save: "workspaces.settings.save",
35
+ saving: "workspaces.settings.saving",
36
+ dangerZoneTitle: "workspaces.settings.danger_zone.title",
37
+ deleteWorkspace: "workspaces.settings.danger_zone.delete",
38
+ deleteWorkspaceConfirm: "workspaces.settings.danger_zone.delete_confirm",
39
+ // Members manager (default skin — MembersManager)
40
+ membersTitle: "workspaces.members.title",
41
+ membersSubtitle: "workspaces.members.subtitle",
42
+ membersInviteDialogTitle: "workspaces.members.invite_dialog.title",
43
+ membersInviteEmailsLabel: "workspaces.members.invite_dialog.emails_label",
44
+ membersInviteEmailsPlaceholder: "workspaces.members.invite_dialog.emails_placeholder",
45
+ membersInviteRoleLabel: "workspaces.members.invite_dialog.role_label",
46
+ membersRemoveConfirm: "workspaces.members.remove_confirm",
47
+ roleOwner: "workspaces.members.role.owner",
48
+ roleAdmin: "workspaces.members.role.admin",
49
+ roleMember: "workspaces.members.role.member",
50
+ roleViewer: "workspaces.members.role.viewer",
28
51
  };
29
52
  /**
30
53
  * English fallback bundle for workspaces-react UI keys + backend error codes.
@@ -50,6 +73,27 @@ export const workspacesI18nBundleEn = {
50
73
  "workspaces.accept.accept": "Accept invitation",
51
74
  "workspaces.accept.accepting": "Accepting…",
52
75
  "workspaces.accept.accepted": "You've joined the workspace.",
76
+ "workspaces.settings.title": "Workspace",
77
+ "workspaces.settings.subtitle": "Name and general settings.",
78
+ "workspaces.settings.field.name": "Workspace name",
79
+ "workspaces.settings.field.slug": "URL slug",
80
+ "workspaces.settings.field.type": "Type",
81
+ "workspaces.settings.save": "Save changes",
82
+ "workspaces.settings.saving": "Saving…",
83
+ "workspaces.settings.danger_zone.title": "Danger zone",
84
+ "workspaces.settings.danger_zone.delete": "Delete workspace",
85
+ "workspaces.settings.danger_zone.delete_confirm": "Delete this workspace? This can't be undone.",
86
+ "workspaces.members.title": "Members",
87
+ "workspaces.members.subtitle": "Manage who has access to this workspace.",
88
+ "workspaces.members.invite_dialog.title": "Invite members",
89
+ "workspaces.members.invite_dialog.emails_label": "Emails",
90
+ "workspaces.members.invite_dialog.emails_placeholder": "Type an email and press Enter",
91
+ "workspaces.members.invite_dialog.role_label": "Role",
92
+ "workspaces.members.remove_confirm": "Remove this member?",
93
+ "workspaces.members.role.owner": "Owner",
94
+ "workspaces.members.role.admin": "Admin",
95
+ "workspaces.members.role.member": "Member",
96
+ "workspaces.members.role.viewer": "Viewer",
53
97
  };
54
98
  /**
55
99
  * Register workspaces-react's key bundle into a core i18n engine (call once at
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,YAAY,EAAE,0BAA0B;IACxC,0CAA0C;IAC1C,WAAW,EAAE,yBAAyB;IACtC,SAAS,EAAE,uBAAuB;IAClC,UAAU,EAAE,wBAAwB;IACpC,YAAY,EAAE,0BAA0B;IACxC,6BAA6B;IAC7B,cAAc,EAAE,4BAA4B;IAC5C,aAAa,EAAE,2BAA2B;IAC1C,eAAe,EAAE,6BAA6B;IAC9C,iBAAiB,EAAE,gCAAgC;IACnD,aAAa,EAAE,2BAA2B;IAC1C,gDAAgD;IAChD,YAAY,EAAE,0BAA0B;IACxC,eAAe,EAAE,6BAA6B;IAC9C,cAAc,EAAE,4BAA4B;CACpC,CAAC;AAKX;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAmB;IACpD,2EAA2E;IAC3E,GAAG,uBAAuB;IAE1B,sBAAsB;IACtB,0BAA0B,EAAE,yCAAyC;IACrE,yBAAyB,EAAE,qBAAqB;IAChD,uBAAuB,EAAE,oBAAoB;IAC7C,wBAAwB,EAAE,kBAAkB;IAC5C,0BAA0B,EAAE,WAAW;IACvC,4BAA4B,EAAE,kBAAkB;IAChD,2BAA2B,EAAE,QAAQ;IACrC,6BAA6B,EAAE,WAAW;IAC1C,gCAAgC,EAAE,aAAa;IAC/C,2BAA2B,EAAE,QAAQ;IACrC,0BAA0B,EAAE,mBAAmB;IAC/C,6BAA6B,EAAE,YAAY;IAC3C,4BAA4B,EAAE,8BAA8B;CAC7D,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAkB,EAAE,MAAM,GAAG,IAAI;IACtE,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AACxD,CAAC"}
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,YAAY,EAAE,0BAA0B;IACxC,0CAA0C;IAC1C,WAAW,EAAE,yBAAyB;IACtC,SAAS,EAAE,uBAAuB;IAClC,UAAU,EAAE,wBAAwB;IACpC,YAAY,EAAE,0BAA0B;IACxC,6BAA6B;IAC7B,cAAc,EAAE,4BAA4B;IAC5C,aAAa,EAAE,2BAA2B;IAC1C,eAAe,EAAE,6BAA6B;IAC9C,iBAAiB,EAAE,gCAAgC;IACnD,aAAa,EAAE,2BAA2B;IAC1C,gDAAgD;IAChD,YAAY,EAAE,0BAA0B;IACxC,eAAe,EAAE,6BAA6B;IAC9C,cAAc,EAAE,4BAA4B;IAC5C,wDAAwD;IACxD,aAAa,EAAE,2BAA2B;IAC1C,gBAAgB,EAAE,8BAA8B;IAChD,SAAS,EAAE,gCAAgC;IAC3C,SAAS,EAAE,gCAAgC;IAC3C,SAAS,EAAE,gCAAgC;IAC3C,IAAI,EAAE,0BAA0B;IAChC,MAAM,EAAE,4BAA4B;IACpC,eAAe,EAAE,uCAAuC;IACxD,eAAe,EAAE,wCAAwC;IACzD,sBAAsB,EAAE,gDAAgD;IACxE,kDAAkD;IAClD,YAAY,EAAE,0BAA0B;IACxC,eAAe,EAAE,6BAA6B;IAC9C,wBAAwB,EAAE,wCAAwC;IAClE,wBAAwB,EAAE,+CAA+C;IACzE,8BAA8B,EAAE,qDAAqD;IACrF,sBAAsB,EAAE,6CAA6C;IACrE,oBAAoB,EAAE,mCAAmC;IACzD,SAAS,EAAE,+BAA+B;IAC1C,SAAS,EAAE,+BAA+B;IAC1C,UAAU,EAAE,gCAAgC;IAC5C,UAAU,EAAE,gCAAgC;CACpC,CAAC;AAKX;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAmB;IACpD,2EAA2E;IAC3E,GAAG,uBAAuB;IAE1B,sBAAsB;IACtB,0BAA0B,EAAE,yCAAyC;IACrE,yBAAyB,EAAE,qBAAqB;IAChD,uBAAuB,EAAE,oBAAoB;IAC7C,wBAAwB,EAAE,kBAAkB;IAC5C,0BAA0B,EAAE,WAAW;IACvC,4BAA4B,EAAE,kBAAkB;IAChD,2BAA2B,EAAE,QAAQ;IACrC,6BAA6B,EAAE,WAAW;IAC1C,gCAAgC,EAAE,aAAa;IAC/C,2BAA2B,EAAE,QAAQ;IACrC,0BAA0B,EAAE,mBAAmB;IAC/C,6BAA6B,EAAE,YAAY;IAC3C,4BAA4B,EAAE,8BAA8B;IAC5D,2BAA2B,EAAE,WAAW;IACxC,8BAA8B,EAAE,4BAA4B;IAC5D,gCAAgC,EAAE,gBAAgB;IAClD,gCAAgC,EAAE,UAAU;IAC5C,gCAAgC,EAAE,MAAM;IACxC,0BAA0B,EAAE,cAAc;IAC1C,4BAA4B,EAAE,SAAS;IACvC,uCAAuC,EAAE,aAAa;IACtD,wCAAwC,EAAE,kBAAkB;IAC5D,gDAAgD,EAAE,8CAA8C;IAChG,0BAA0B,EAAE,SAAS;IACrC,6BAA6B,EAAE,0CAA0C;IACzE,wCAAwC,EAAE,gBAAgB;IAC1D,+CAA+C,EAAE,QAAQ;IACzD,qDAAqD,EAAE,+BAA+B;IACtF,6CAA6C,EAAE,MAAM;IACrD,mCAAmC,EAAE,qBAAqB;IAC1D,+BAA+B,EAAE,OAAO;IACxC,+BAA+B,EAAE,OAAO;IACxC,gCAAgC,EAAE,QAAQ;IAC1C,gCAAgC,EAAE,QAAQ;CAC3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAkB,EAAE,MAAM,GAAG,IAAI;IACtE,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AACxD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ru.d.ts","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAkBpC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAGjE"}
1
+ {"version":3,"file":"ru.d.ts","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAuCpC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAGjE"}
package/dist/i18n/ru.js CHANGED
@@ -33,6 +33,27 @@ export const workspacesI18nBundleRu = {
33
33
  "workspaces.accept.accept": "Принять приглашение",
34
34
  "workspaces.accept.accepting": "Принятие…",
35
35
  "workspaces.accept.accepted": "Вы присоединились к рабочему пространству.",
36
+ "workspaces.settings.title": "Рабочее пространство",
37
+ "workspaces.settings.subtitle": "Название и общие настройки.",
38
+ "workspaces.settings.field.name": "Название рабочего пространства",
39
+ "workspaces.settings.field.slug": "URL-идентификатор",
40
+ "workspaces.settings.field.type": "Тип",
41
+ "workspaces.settings.save": "Сохранить изменения",
42
+ "workspaces.settings.saving": "Сохранение…",
43
+ "workspaces.settings.danger_zone.title": "Опасная зона",
44
+ "workspaces.settings.danger_zone.delete": "Удалить рабочее пространство",
45
+ "workspaces.settings.danger_zone.delete_confirm": "Удалить это рабочее пространство? Действие необратимо.",
46
+ "workspaces.members.title": "Участники",
47
+ "workspaces.members.subtitle": "Управляйте доступом к рабочему пространству.",
48
+ "workspaces.members.invite_dialog.title": "Пригласить участников",
49
+ "workspaces.members.invite_dialog.emails_label": "Email-адреса",
50
+ "workspaces.members.invite_dialog.emails_placeholder": "Введите email и нажмите Enter",
51
+ "workspaces.members.invite_dialog.role_label": "Роль",
52
+ "workspaces.members.remove_confirm": "Удалить этого участника?",
53
+ "workspaces.members.role.owner": "Владелец",
54
+ "workspaces.members.role.admin": "Администратор",
55
+ "workspaces.members.role.member": "Участник",
56
+ "workspaces.members.role.viewer": "Наблюдатель",
36
57
  };
37
58
  /**
38
59
  * Register the pair's `ru` locale into a core i18n engine (call once at
@@ -1 +1 @@
1
- {"version":3,"file":"ru.js","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAmB;IACpD,uEAAuE;IACvE,GAAG,uBAAuB;IAE1B,yEAAyE;IACzE,0BAA0B,EAAE,0CAA0C;IACtE,yBAAyB,EAAE,+BAA+B;IAC1D,uBAAuB,EAAE,+BAA+B;IACxD,wBAAwB,EAAE,8BAA8B;IACxD,0BAA0B,EAAE,WAAW;IACvC,4BAA4B,EAAE,sBAAsB;IACpD,2BAA2B,EAAE,YAAY;IACzC,6BAA6B,EAAE,cAAc;IAC7C,gCAAgC,EAAE,eAAe;IACjD,2BAA2B,EAAE,SAAS;IACtC,0BAA0B,EAAE,qBAAqB;IACjD,6BAA6B,EAAE,WAAW;IAC1C,4BAA4B,EAAE,4CAA4C;CAC3E,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAkB;IACzD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;IACpD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;AACtD,CAAC"}
1
+ {"version":3,"file":"ru.js","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAmB;IACpD,uEAAuE;IACvE,GAAG,uBAAuB;IAE1B,yEAAyE;IACzE,0BAA0B,EAAE,0CAA0C;IACtE,yBAAyB,EAAE,+BAA+B;IAC1D,uBAAuB,EAAE,+BAA+B;IACxD,wBAAwB,EAAE,8BAA8B;IACxD,0BAA0B,EAAE,WAAW;IACvC,4BAA4B,EAAE,sBAAsB;IACpD,2BAA2B,EAAE,YAAY;IACzC,6BAA6B,EAAE,cAAc;IAC7C,gCAAgC,EAAE,eAAe;IACjD,2BAA2B,EAAE,SAAS;IACtC,0BAA0B,EAAE,qBAAqB;IACjD,6BAA6B,EAAE,WAAW;IAC1C,4BAA4B,EAAE,4CAA4C;IAC1E,2BAA2B,EAAE,sBAAsB;IACnD,8BAA8B,EAAE,6BAA6B;IAC7D,gCAAgC,EAAE,gCAAgC;IAClE,gCAAgC,EAAE,mBAAmB;IACrD,gCAAgC,EAAE,KAAK;IACvC,0BAA0B,EAAE,qBAAqB;IACjD,4BAA4B,EAAE,aAAa;IAC3C,uCAAuC,EAAE,cAAc;IACvD,wCAAwC,EAAE,8BAA8B;IACxE,gDAAgD,EAAE,wDAAwD;IAC1G,0BAA0B,EAAE,WAAW;IACvC,6BAA6B,EAAE,8CAA8C;IAC7E,wCAAwC,EAAE,uBAAuB;IACjE,+CAA+C,EAAE,cAAc;IAC/D,qDAAqD,EAAE,+BAA+B;IACtF,6CAA6C,EAAE,MAAM;IACrD,mCAAmC,EAAE,0BAA0B;IAC/D,+BAA+B,EAAE,UAAU;IAC3C,+BAA+B,EAAE,eAAe;IAChD,gCAAgC,EAAE,UAAU;IAC5C,gCAAgC,EAAE,aAAa;CAChD,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAkB;IACzD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;IACpD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;AACtD,CAAC"}
@@ -6,18 +6,32 @@ import type { MemberPage, MembersParams, Workspace, WorkspaceList } from "../api
6
6
  * override per call site via a page that needs fresher data. Keys are
7
7
  * namespaced (see `workspacesQueryKeys`).
8
8
  */
9
- /** The caller's workspaces — their accepted memberships (GET /). */
9
+ /**
10
+ * The caller's workspaces — their accepted memberships (GET /).
11
+ *
12
+ * Gated on {@link useActiveSessionReady} (owner-diagnosed live incident,
13
+ * 2026-07-17): unlike {@link useWorkspace}/{@link useMembers} below (both
14
+ * naturally disabled until a `workspaceId` is picked), this is the top-level
15
+ * list hook with nothing else to gate it — it fires the instant a component
16
+ * mounts. Without the session ready-gate this is exactly the shape of hook
17
+ * that raced a session still bootstrapping (e.g. right after a QR
18
+ * `session_share` scan set fresh cookies this JS runtime hadn't caught up
19
+ * to yet) and read a live session as "expired" before it had a chance to
20
+ * resolve — zero manual `enabled` wiring needed at each call site by design.
21
+ */
10
22
  export declare function useWorkspaces(): UseQueryResult<WorkspaceList, StapelApiError>;
11
23
  /**
12
24
  * A single workspace by id (GET /{id}). Disabled until a `workspaceId` is
13
- * given, so a detail hook can mount before a selection exists.
25
+ * given, so a detail hook can mount before a selection exists — AND until
26
+ * the session is ready (a `workspaceId` can be known synchronously, e.g.
27
+ * from a URL param, before the session has finished bootstrapping).
14
28
  */
15
29
  export declare function useWorkspace(workspaceId: string | null): UseQueryResult<Workspace, StapelApiError>;
16
30
  /**
17
31
  * A page of a workspace's members (GET /{id}/members, anchor-paginated).
18
- * Disabled until a `workspaceId` is given. Pass `{ anchor, direction, limit,
19
- * search }` to jump to a specific page or filter; omit for the newest page
20
- * (default limit 100).
32
+ * Disabled until a `workspaceId` is given (and the session is ready — see
33
+ * {@link useWorkspace}). Pass `{ anchor, direction, limit, search }` to jump
34
+ * to a specific page or filter; omit for the newest page (default limit 100).
21
35
  */
22
36
  export declare function useMembers(workspaceId: string | null, params?: MembersParams): UseQueryResult<MemberPage, StapelApiError>;
23
37
  //# sourceMappingURL=queries.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACd,MAAM,iBAAiB,CAAC;AAIzB;;;;GAIG;AAEH,oEAAoE;AACpE,wBAAgB,aAAa,IAAI,cAAc,CAAC,aAAa,EAAE,cAAc,CAAC,CAM7E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,GACzB,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,CAO3C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,MAAM,CAAC,EAAE,aAAa,GACrB,cAAc,CAAC,UAAU,EAAE,cAAc,CAAC,CAQ5C"}
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACd,MAAM,iBAAiB,CAAC;AAIzB;;;;GAIG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,IAAI,cAAc,CAAC,aAAa,EAAE,cAAc,CAAC,CAQ7E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,GACzB,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,CAQ3C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,MAAM,CAAC,EAAE,aAAa,GACrB,cAAc,CAAC,UAAU,EAAE,cAAc,CAAC,CAS5C"}
@@ -1,4 +1,5 @@
1
1
  import { useQuery } from "@tanstack/react-query";
2
+ import { useActiveSessionReady } from "@stapel/core";
2
3
  import { useWorkspacesApi } from "./context.js";
3
4
  import { workspacesQueryKeys } from "./queryKeys.js";
4
5
  /**
@@ -6,39 +7,57 @@ import { workspacesQueryKeys } from "./queryKeys.js";
6
7
  * override per call site via a page that needs fresher data. Keys are
7
8
  * namespaced (see `workspacesQueryKeys`).
8
9
  */
9
- /** The caller's workspaces — their accepted memberships (GET /). */
10
+ /**
11
+ * The caller's workspaces — their accepted memberships (GET /).
12
+ *
13
+ * Gated on {@link useActiveSessionReady} (owner-diagnosed live incident,
14
+ * 2026-07-17): unlike {@link useWorkspace}/{@link useMembers} below (both
15
+ * naturally disabled until a `workspaceId` is picked), this is the top-level
16
+ * list hook with nothing else to gate it — it fires the instant a component
17
+ * mounts. Without the session ready-gate this is exactly the shape of hook
18
+ * that raced a session still bootstrapping (e.g. right after a QR
19
+ * `session_share` scan set fresh cookies this JS runtime hadn't caught up
20
+ * to yet) and read a live session as "expired" before it had a chance to
21
+ * resolve — zero manual `enabled` wiring needed at each call site by design.
22
+ */
10
23
  export function useWorkspaces() {
11
24
  const api = useWorkspacesApi();
25
+ const sessionReady = useActiveSessionReady();
12
26
  return useQuery({
13
27
  queryKey: workspacesQueryKeys.list(),
14
28
  queryFn: () => api.listWorkspaces(),
29
+ enabled: sessionReady,
15
30
  });
16
31
  }
17
32
  /**
18
33
  * A single workspace by id (GET /{id}). Disabled until a `workspaceId` is
19
- * given, so a detail hook can mount before a selection exists.
34
+ * given, so a detail hook can mount before a selection exists — AND until
35
+ * the session is ready (a `workspaceId` can be known synchronously, e.g.
36
+ * from a URL param, before the session has finished bootstrapping).
20
37
  */
21
38
  export function useWorkspace(workspaceId) {
22
39
  const api = useWorkspacesApi();
40
+ const sessionReady = useActiveSessionReady();
23
41
  return useQuery({
24
42
  queryKey: workspacesQueryKeys.detail(workspaceId ?? ""),
25
43
  queryFn: () => api.getWorkspace(workspaceId),
26
- enabled: workspaceId !== null && workspaceId !== "",
44
+ enabled: sessionReady && workspaceId !== null && workspaceId !== "",
27
45
  });
28
46
  }
29
47
  /**
30
48
  * A page of a workspace's members (GET /{id}/members, anchor-paginated).
31
- * Disabled until a `workspaceId` is given. Pass `{ anchor, direction, limit,
32
- * search }` to jump to a specific page or filter; omit for the newest page
33
- * (default limit 100).
49
+ * Disabled until a `workspaceId` is given (and the session is ready — see
50
+ * {@link useWorkspace}). Pass `{ anchor, direction, limit, search }` to jump
51
+ * to a specific page or filter; omit for the newest page (default limit 100).
34
52
  */
35
53
  export function useMembers(workspaceId, params) {
36
54
  const api = useWorkspacesApi();
55
+ const sessionReady = useActiveSessionReady();
37
56
  const p = params ?? {};
38
57
  return useQuery({
39
58
  queryKey: workspacesQueryKeys.membersPage(workspaceId ?? "", p),
40
59
  queryFn: () => api.listMembers(workspaceId, p),
41
- enabled: workspaceId !== null && workspaceId !== "",
60
+ enabled: sessionReady && workspaceId !== null && workspaceId !== "",
42
61
  });
43
62
  }
44
63
  //# sourceMappingURL=queries.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AASjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD;;;;GAIG;AAEH,oEAAoE;AACpE,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,mBAAmB,CAAC,IAAI,EAAE;QACpC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,cAAc,EAAE;KACpC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,WAA0B;IAE1B,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,mBAAmB,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;QACvD,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,WAAqB,CAAC;QACtD,OAAO,EAAE,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,EAAE;KACpD,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,WAA0B,EAC1B,MAAsB;IAEtB,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,MAAM,CAAC,GAAG,MAAM,IAAI,EAAE,CAAC;IACvB,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,mBAAmB,CAAC,WAAW,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC,CAAC;QAC/D,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,WAAqB,EAAE,CAAC,CAAC;QACxD,OAAO,EAAE,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,EAAE;KACpD,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAQrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD;;;;GAIG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,MAAM,YAAY,GAAG,qBAAqB,EAAE,CAAC;IAC7C,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,mBAAmB,CAAC,IAAI,EAAE;QACpC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,cAAc,EAAE;QACnC,OAAO,EAAE,YAAY;KACtB,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,WAA0B;IAE1B,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,MAAM,YAAY,GAAG,qBAAqB,EAAE,CAAC;IAC7C,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,mBAAmB,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;QACvD,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,WAAqB,CAAC;QACtD,OAAO,EAAE,YAAY,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,EAAE;KACpE,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,WAA0B,EAC1B,MAAsB;IAEtB,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,MAAM,YAAY,GAAG,qBAAqB,EAAE,CAAC;IAC7C,MAAM,CAAC,GAAG,MAAM,IAAI,EAAE,CAAC;IACvB,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,mBAAmB,CAAC,WAAW,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC,CAAC;QAC/D,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,WAAqB,EAAE,CAAC,CAAC;QACxD,OAAO,EAAE,YAAY,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,EAAE;KACpE,CAAC,CAAC;AACL,CAAC"}
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @stapel/workspaces-react 0.4.1
1
+ # @stapel/workspaces-react 0.6.0
2
2
 
3
3
  Headless React flow pair for stapel-workspaces (contract >=0.4 <0.5) — business + state, zero visual opinion.
4
4
  Built on @stapel/core: typed client + StapelApiError envelope, auth token refresh,
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-manifest.mjs — do not edit; drift-gated (pnpm gen:manifest:check)",
3
3
  "package": "@stapel/workspaces-react",
4
- "version": "0.4.1",
4
+ "version": "0.6.0",
5
5
  "backend": {
6
6
  "module": "stapel-workspaces",
7
7
  "contract": ">=0.4 <0.5"
@@ -626,10 +626,31 @@
626
626
  "workspaces.list.empty",
627
627
  "workspaces.list.loading",
628
628
  "workspaces.members.invite",
629
+ "workspaces.members.invite_dialog.emails_label",
630
+ "workspaces.members.invite_dialog.emails_placeholder",
631
+ "workspaces.members.invite_dialog.role_label",
632
+ "workspaces.members.invite_dialog.title",
629
633
  "workspaces.members.inviting",
630
634
  "workspaces.members.loading",
631
635
  "workspaces.members.remove",
632
- "workspaces.members.update_role"
636
+ "workspaces.members.remove_confirm",
637
+ "workspaces.members.role.admin",
638
+ "workspaces.members.role.member",
639
+ "workspaces.members.role.owner",
640
+ "workspaces.members.role.viewer",
641
+ "workspaces.members.subtitle",
642
+ "workspaces.members.title",
643
+ "workspaces.members.update_role",
644
+ "workspaces.settings.danger_zone.delete",
645
+ "workspaces.settings.danger_zone.delete_confirm",
646
+ "workspaces.settings.danger_zone.title",
647
+ "workspaces.settings.field.name",
648
+ "workspaces.settings.field.slug",
649
+ "workspaces.settings.field.type",
650
+ "workspaces.settings.save",
651
+ "workspaces.settings.saving",
652
+ "workspaces.settings.subtitle",
653
+ "workspaces.settings.title"
633
654
  ],
634
655
  "exports": {
635
656
  "runtime": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stapel/workspaces-react",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "Headless React flow pair for stapel-workspaces: typed API client, TanStack Query hooks, flow machines, headless components, and i18n keys. Zero visual opinion.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,6 +17,10 @@
17
17
  "types": "./dist/index.d.ts",
18
18
  "default": "./dist/index.js"
19
19
  },
20
+ "./default": {
21
+ "types": "./dist/default/index.d.ts",
22
+ "default": "./dist/default/index.js"
23
+ },
20
24
  "./i18n/ru": {
21
25
  "types": "./dist/i18n/ru.d.ts",
22
26
  "default": "./dist/i18n/ru.js"
@@ -46,19 +50,31 @@
46
50
  "name": "i18n/ru subpath (opt-in locale: en floor + generated ru + ru UI)",
47
51
  "path": "dist/i18n/ru.js",
48
52
  "limit": "3.5 KB"
53
+ },
54
+ {
55
+ "name": "default — the settings skin (antd) must stay out of the main bundle",
56
+ "path": "dist/default/index.js",
57
+ "limit": "12 KB"
49
58
  }
50
59
  ],
51
60
  "peerDependencies": {
52
61
  "@stapel/core": ">=0.3.0 <1.0.0",
53
62
  "@tanstack/react-query": "^5.0.0",
63
+ "antd": ">=5.20.0 <7",
54
64
  "react": ">=19"
55
65
  },
66
+ "peerDependenciesMeta": {
67
+ "antd": {
68
+ "optional": true
69
+ }
70
+ },
56
71
  "devDependencies": {
57
72
  "@size-limit/preset-small-lib": "^11.2.0",
58
73
  "@tanstack/react-query": "^5.81.0",
59
74
  "@testing-library/react": "^16.3.0",
60
75
  "@types/react": "^19.1.0",
61
76
  "@types/react-dom": "^19.1.0",
77
+ "antd": "^6.5.0",
62
78
  "jsdom": "^26.1.0",
63
79
  "msw": "^2.10.2",
64
80
  "react": "^19.1.0",
@@ -66,9 +82,9 @@
66
82
  "size-limit": "^11.2.0",
67
83
  "typescript": "^5.8.3",
68
84
  "vitest": "^3.2.4",
69
- "@stapel/core": "^0.4.0",
70
- "@stapel/showcase": "^0.2.0",
71
- "@stapel/tokens": "^0.4.0"
85
+ "@stapel/core": "^0.6.0",
86
+ "@stapel/tokens": "^0.4.0",
87
+ "@stapel/showcase": "^0.2.0"
72
88
  },
73
89
  "engines": {
74
90
  "node": ">=22"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-events.mjs — do not edit; drift-gated (pnpm gen:events:check)",
3
3
  "package": "@stapel/workspaces-react",
4
- "version": "0.4.1",
4
+ "version": "0.6.0",
5
5
  "defined": [],
6
6
  "flows": []
7
7
  }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * `<MembersManager/>` — default skin for the "members & roles" settings
3
+ * screen (owner directive: "настройки воркспейса (имя/участники/роли/
4
+ * инвайты)"). Built entirely on this pair's EXISTING `Members` headless
5
+ * wrapper (`useMembers`/`useInviteMembers`/`useUpdateMemberRole`/
6
+ * `useRemoveMember`) — no new backend surface.
7
+ */
8
+ import { useState } from "react";
9
+ import type { ReactElement } from "react";
10
+ import { Alert, Button, Card, Input, Modal, Popconfirm, Select, Table, Typography } from "antd";
11
+ import type { TableProps } from "antd";
12
+ import { useT } from "@stapel/core";
13
+ import { Members } from "../headless/Members.js";
14
+ import type { Member, WorkspaceRole } from "../api/types.js";
15
+ import { WORKSPACES_I18N_KEYS } from "../i18n/keys.js";
16
+
17
+ export interface MembersManagerProps {
18
+ workspaceId: string;
19
+ /**
20
+ * Whether the caller may invite, change roles, and remove members. The
21
+ * host already knows the caller's own role in this workspace (e.g. from
22
+ * `useWorkspace(workspaceId).data?.my_role`); this component doesn't
23
+ * re-derive it — pass `false` for a read-only roster (e.g. a `viewer`/
24
+ * `member` looking at their teammates). Default `true`.
25
+ */
26
+ canManage?: boolean;
27
+ }
28
+
29
+ const ROLES: readonly WorkspaceRole[] = ["owner", "admin", "member", "viewer"];
30
+
31
+ const ROLE_KEY: Record<
32
+ WorkspaceRole,
33
+ "roleOwner" | "roleAdmin" | "roleMember" | "roleViewer"
34
+ > = {
35
+ owner: "roleOwner",
36
+ admin: "roleAdmin",
37
+ member: "roleMember",
38
+ viewer: "roleViewer",
39
+ };
40
+
41
+ function isWorkspaceRole(value: string): value is WorkspaceRole {
42
+ return (ROLES as readonly string[]).includes(value);
43
+ }
44
+
45
+ export function MembersManager(props: MembersManagerProps): ReactElement {
46
+ const t = useT();
47
+ const canManage = props.canManage ?? true;
48
+ const [inviteOpen, setInviteOpen] = useState(false);
49
+ const [emailsText, setEmailsText] = useState("");
50
+ const [inviteRole, setInviteRole] = useState<WorkspaceRole>("member");
51
+
52
+ return (
53
+ <Members workspaceId={props.workspaceId}>
54
+ {({ members, isLoading, isError, error, invite, isInviting, updateRole, remove }) => {
55
+ function openInvite(): void {
56
+ setEmailsText("");
57
+ setInviteRole("member");
58
+ setInviteOpen(true);
59
+ }
60
+
61
+ function submitInvite(): void {
62
+ const emails = emailsText
63
+ .split(/[,\s]+/)
64
+ .map((e) => e.trim())
65
+ .filter((e) => e.length > 0);
66
+ if (emails.length === 0) return;
67
+ invite({ emails, role: inviteRole });
68
+ setInviteOpen(false);
69
+ }
70
+
71
+ const columns: TableProps<Member>["columns"] = [
72
+ {
73
+ title: "Email",
74
+ dataIndex: "email",
75
+ key: "email",
76
+ render: (value: string | null) => value ?? "—",
77
+ },
78
+ {
79
+ title: t(WORKSPACES_I18N_KEYS.membersInviteRoleLabel),
80
+ key: "role",
81
+ render: (_: unknown, member: Member) => {
82
+ const role = isWorkspaceRole(member.role) ? member.role : "member";
83
+ return canManage ? (
84
+ <Select<WorkspaceRole>
85
+ value={role}
86
+ style={{ width: 140 }}
87
+ onChange={(next) => updateRole({ userId: member.user_id, role: next })}
88
+ options={ROLES.map((r) => ({ value: r, label: t(WORKSPACES_I18N_KEYS[ROLE_KEY[r]]) }))}
89
+ />
90
+ ) : (
91
+ <span>{t(WORKSPACES_I18N_KEYS[ROLE_KEY[role]])}</span>
92
+ );
93
+ },
94
+ },
95
+ ...(canManage
96
+ ? [
97
+ {
98
+ title: "",
99
+ key: "actions",
100
+ render: (_: unknown, member: Member) => (
101
+ <Popconfirm
102
+ title={t(WORKSPACES_I18N_KEYS.membersRemoveConfirm)}
103
+ onConfirm={() => remove(member.user_id)}
104
+ >
105
+ <Button danger type="link">
106
+ {t(WORKSPACES_I18N_KEYS.membersRemove)}
107
+ </Button>
108
+ </Popconfirm>
109
+ ),
110
+ },
111
+ ]
112
+ : []),
113
+ ];
114
+
115
+ return (
116
+ <Card data-testid="members-manager">
117
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 16 }}>
118
+ <div>
119
+ <Typography.Title level={4} style={{ marginTop: 0 }}>
120
+ {t(WORKSPACES_I18N_KEYS.membersTitle)}
121
+ </Typography.Title>
122
+ <Typography.Text type="secondary">{t(WORKSPACES_I18N_KEYS.membersSubtitle)}</Typography.Text>
123
+ </div>
124
+ {canManage && (
125
+ <Button
126
+ type="primary"
127
+ onClick={openInvite}
128
+ data-analytics="none"
129
+ data-analytics-reason="local-ui-open-invite-dialog"
130
+ >
131
+ {t(WORKSPACES_I18N_KEYS.membersInvite)}
132
+ </Button>
133
+ )}
134
+ </div>
135
+
136
+ {isError && error && (
137
+ <Alert style={{ marginTop: 12 }} type="error" showIcon message={error.message} />
138
+ )}
139
+
140
+ <Table<Member>
141
+ style={{ marginTop: 16 }}
142
+ size="small"
143
+ loading={isLoading}
144
+ rowKey={(member) => member.id}
145
+ dataSource={members as Member[]}
146
+ columns={columns}
147
+ pagination={false}
148
+ />
149
+
150
+ <Modal
151
+ title={t(WORKSPACES_I18N_KEYS.membersInviteDialogTitle)}
152
+ open={inviteOpen}
153
+ onCancel={() => setInviteOpen(false)}
154
+ onOk={submitInvite}
155
+ okButtonProps={{ loading: isInviting, disabled: emailsText.trim().length === 0 }}
156
+ >
157
+ <div style={{ display: "grid", gap: 12 }}>
158
+ <div>
159
+ <Typography.Text>{t(WORKSPACES_I18N_KEYS.membersInviteEmailsLabel)}</Typography.Text>
160
+ <Input
161
+ value={emailsText}
162
+ onChange={(e) => setEmailsText(e.target.value)}
163
+ placeholder={t(WORKSPACES_I18N_KEYS.membersInviteEmailsPlaceholder)}
164
+ />
165
+ </div>
166
+ <div>
167
+ <Typography.Text>{t(WORKSPACES_I18N_KEYS.membersInviteRoleLabel)}</Typography.Text>
168
+ <Select<WorkspaceRole>
169
+ value={inviteRole}
170
+ onChange={setInviteRole}
171
+ style={{ width: "100%" }}
172
+ options={ROLES.filter((r) => r !== "owner").map((r) => ({
173
+ value: r,
174
+ label: t(WORKSPACES_I18N_KEYS[ROLE_KEY[r]]),
175
+ }))}
176
+ />
177
+ </div>
178
+ </div>
179
+ </Modal>
180
+ </Card>
181
+ );
182
+ }}
183
+ </Members>
184
+ );
185
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * `<WorkspaceSettings/>` — default skin for the "workspace" settings screen
3
+ * (owner directive: workspace name/general settings, one of the settings
4
+ * surfaces this liberary pair owns). Built entirely on this pair's EXISTING
5
+ * hooks (`useWorkspace`, `useUpdateWorkspace`, `useDeleteWorkspace`) — no new
6
+ * backend surface. Participants/roles live in the sibling
7
+ * `<MembersManager/>` (a workspace's settings page composes both).
8
+ */
9
+ import { useEffect, useState } from "react";
10
+ import type { ReactElement } from "react";
11
+ import { Alert, Badge, Button, Card, Input, Popconfirm, Spin, Typography } from "antd";
12
+ import { useT } from "@stapel/core";
13
+ import { useWorkspace } from "../model/queries.js";
14
+ import { useUpdateWorkspace, useDeleteWorkspace } from "../model/mutations.js";
15
+ import { WORKSPACES_I18N_KEYS } from "../i18n/keys.js";
16
+
17
+ export interface WorkspaceSettingsProps {
18
+ workspaceId: string;
19
+ /** Called after a successful delete — the host navigates away / switches
20
+ * to another workspace. Not called on cancel or failure. */
21
+ onDeleted?(): void;
22
+ }
23
+
24
+ export function WorkspaceSettings(props: WorkspaceSettingsProps): ReactElement {
25
+ const t = useT();
26
+ const query = useWorkspace(props.workspaceId);
27
+ const updateMutation = useUpdateWorkspace(props.workspaceId);
28
+ const deleteMutation = useDeleteWorkspace();
29
+
30
+ const workspace = query.data;
31
+ const [name, setName] = useState("");
32
+
33
+ useEffect(() => {
34
+ if (workspace) setName(workspace.name);
35
+ }, [workspace]);
36
+
37
+ function handleSave(): void {
38
+ updateMutation.mutate({ name: name.trim() });
39
+ }
40
+
41
+ function handleDelete(): void {
42
+ deleteMutation.mutate(props.workspaceId, {
43
+ onSuccess: () => props.onDeleted?.(),
44
+ });
45
+ }
46
+
47
+ if (query.isLoading && !workspace) {
48
+ return <Spin data-testid="workspace-settings-loading" />;
49
+ }
50
+ if (!workspace) {
51
+ return (
52
+ <Alert
53
+ data-testid="workspace-settings-error"
54
+ type="error"
55
+ showIcon
56
+ message={query.error?.message ?? t(WORKSPACES_I18N_KEYS.unknownError)}
57
+ />
58
+ );
59
+ }
60
+
61
+ const isOwner = workspace.my_role === "owner";
62
+
63
+ return (
64
+ <div data-testid="workspace-settings" style={{ display: "grid", gap: 16 }}>
65
+ <Card>
66
+ <Typography.Title level={4} style={{ marginTop: 0 }}>
67
+ {t(WORKSPACES_I18N_KEYS.settingsTitle)}
68
+ </Typography.Title>
69
+ <Typography.Text type="secondary">{t(WORKSPACES_I18N_KEYS.settingsSubtitle)}</Typography.Text>
70
+
71
+ <div style={{ display: "grid", gap: 12, maxWidth: 480, marginTop: 16 }}>
72
+ <div>
73
+ <Typography.Text>{t(WORKSPACES_I18N_KEYS.fieldName)}</Typography.Text>
74
+ <Input value={name} onChange={(e) => setName(e.target.value)} disabled={!isOwner} />
75
+ </div>
76
+ <div>
77
+ <Typography.Text>{t(WORKSPACES_I18N_KEYS.fieldSlug)}</Typography.Text>
78
+ <div>
79
+ <Typography.Text code>{workspace.slug}</Typography.Text>
80
+ </div>
81
+ </div>
82
+ <div>
83
+ <Typography.Text>{t(WORKSPACES_I18N_KEYS.fieldType)}</Typography.Text>
84
+ <div>
85
+ <Badge status="default" text={workspace.type} />
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ {updateMutation.error && (
91
+ <Alert
92
+ style={{ marginTop: 12 }}
93
+ type="error"
94
+ showIcon
95
+ message={updateMutation.error.message}
96
+ />
97
+ )}
98
+
99
+ <Button
100
+ type="primary"
101
+ style={{ marginTop: 16 }}
102
+ loading={updateMutation.isPending}
103
+ disabled={!isOwner || !name.trim()}
104
+ onClick={handleSave}
105
+ data-analytics="none"
106
+ data-analytics-reason="business action — host app wraps with its own tracked(); pairs carry no @stapel/analytics runtime dependency by architecture"
107
+ >
108
+ {updateMutation.isPending ? t(WORKSPACES_I18N_KEYS.saving) : t(WORKSPACES_I18N_KEYS.save)}
109
+ </Button>
110
+ </Card>
111
+
112
+ {isOwner && (
113
+ <Card>
114
+ <Typography.Title level={5} type="danger" style={{ marginTop: 0 }}>
115
+ {t(WORKSPACES_I18N_KEYS.dangerZoneTitle)}
116
+ </Typography.Title>
117
+ <Popconfirm
118
+ title={t(WORKSPACES_I18N_KEYS.deleteWorkspaceConfirm)}
119
+ onConfirm={handleDelete}
120
+ okButtonProps={{ loading: deleteMutation.isPending, danger: true }}
121
+ >
122
+ <Button danger>{t(WORKSPACES_I18N_KEYS.deleteWorkspace)}</Button>
123
+ </Popconfirm>
124
+ </Card>
125
+ )}
126
+ </div>
127
+ );
128
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `@stapel/workspaces-react/default` — the pilot default skin for this
3
+ * pair's settings surfaces (mirrors auth-react's `/default` split, §54): a
4
+ * separate entry point so consumers who bring their own visuals never pull
5
+ * `antd` into their bundle; importing this subpath is the opt-in.
6
+ *
7
+ * ```tsx
8
+ * import { WorkspaceSettings, MembersManager } from "@stapel/workspaces-react/default";
9
+ * // under this pair's <WorkspacesProvider> + core <I18nProvider>:
10
+ * <WorkspaceSettings workspaceId={id} />
11
+ * <MembersManager workspaceId={id} />
12
+ * ```
13
+ */
14
+ export { WorkspaceSettings } from "./WorkspaceSettings.js";
15
+ export type { WorkspaceSettingsProps } from "./WorkspaceSettings.js";
16
+ export { MembersManager } from "./MembersManager.js";
17
+ export type { MembersManagerProps } from "./MembersManager.js";
package/src/i18n/keys.ts CHANGED
@@ -27,6 +27,29 @@ export const WORKSPACES_I18N_KEYS = {
27
27
  acceptAccept: "workspaces.accept.accept",
28
28
  acceptAccepting: "workspaces.accept.accepting",
29
29
  acceptAccepted: "workspaces.accept.accepted",
30
+ // Workspace settings (default skin — WorkspaceSettings)
31
+ settingsTitle: "workspaces.settings.title",
32
+ settingsSubtitle: "workspaces.settings.subtitle",
33
+ fieldName: "workspaces.settings.field.name",
34
+ fieldSlug: "workspaces.settings.field.slug",
35
+ fieldType: "workspaces.settings.field.type",
36
+ save: "workspaces.settings.save",
37
+ saving: "workspaces.settings.saving",
38
+ dangerZoneTitle: "workspaces.settings.danger_zone.title",
39
+ deleteWorkspace: "workspaces.settings.danger_zone.delete",
40
+ deleteWorkspaceConfirm: "workspaces.settings.danger_zone.delete_confirm",
41
+ // Members manager (default skin — MembersManager)
42
+ membersTitle: "workspaces.members.title",
43
+ membersSubtitle: "workspaces.members.subtitle",
44
+ membersInviteDialogTitle: "workspaces.members.invite_dialog.title",
45
+ membersInviteEmailsLabel: "workspaces.members.invite_dialog.emails_label",
46
+ membersInviteEmailsPlaceholder: "workspaces.members.invite_dialog.emails_placeholder",
47
+ membersInviteRoleLabel: "workspaces.members.invite_dialog.role_label",
48
+ membersRemoveConfirm: "workspaces.members.remove_confirm",
49
+ roleOwner: "workspaces.members.role.owner",
50
+ roleAdmin: "workspaces.members.role.admin",
51
+ roleMember: "workspaces.members.role.member",
52
+ roleViewer: "workspaces.members.role.viewer",
30
53
  } as const;
31
54
 
32
55
  export type WorkspacesI18nKey =
@@ -57,6 +80,27 @@ export const workspacesI18nBundleEn: I18nDictionary = {
57
80
  "workspaces.accept.accept": "Accept invitation",
58
81
  "workspaces.accept.accepting": "Accepting…",
59
82
  "workspaces.accept.accepted": "You've joined the workspace.",
83
+ "workspaces.settings.title": "Workspace",
84
+ "workspaces.settings.subtitle": "Name and general settings.",
85
+ "workspaces.settings.field.name": "Workspace name",
86
+ "workspaces.settings.field.slug": "URL slug",
87
+ "workspaces.settings.field.type": "Type",
88
+ "workspaces.settings.save": "Save changes",
89
+ "workspaces.settings.saving": "Saving…",
90
+ "workspaces.settings.danger_zone.title": "Danger zone",
91
+ "workspaces.settings.danger_zone.delete": "Delete workspace",
92
+ "workspaces.settings.danger_zone.delete_confirm": "Delete this workspace? This can't be undone.",
93
+ "workspaces.members.title": "Members",
94
+ "workspaces.members.subtitle": "Manage who has access to this workspace.",
95
+ "workspaces.members.invite_dialog.title": "Invite members",
96
+ "workspaces.members.invite_dialog.emails_label": "Emails",
97
+ "workspaces.members.invite_dialog.emails_placeholder": "Type an email and press Enter",
98
+ "workspaces.members.invite_dialog.role_label": "Role",
99
+ "workspaces.members.remove_confirm": "Remove this member?",
100
+ "workspaces.members.role.owner": "Owner",
101
+ "workspaces.members.role.admin": "Admin",
102
+ "workspaces.members.role.member": "Member",
103
+ "workspaces.members.role.viewer": "Viewer",
60
104
  };
61
105
 
62
106
  /**
package/src/i18n/ru.ts CHANGED
@@ -37,6 +37,27 @@ export const workspacesI18nBundleRu: I18nDictionary = {
37
37
  "workspaces.accept.accept": "Принять приглашение",
38
38
  "workspaces.accept.accepting": "Принятие…",
39
39
  "workspaces.accept.accepted": "Вы присоединились к рабочему пространству.",
40
+ "workspaces.settings.title": "Рабочее пространство",
41
+ "workspaces.settings.subtitle": "Название и общие настройки.",
42
+ "workspaces.settings.field.name": "Название рабочего пространства",
43
+ "workspaces.settings.field.slug": "URL-идентификатор",
44
+ "workspaces.settings.field.type": "Тип",
45
+ "workspaces.settings.save": "Сохранить изменения",
46
+ "workspaces.settings.saving": "Сохранение…",
47
+ "workspaces.settings.danger_zone.title": "Опасная зона",
48
+ "workspaces.settings.danger_zone.delete": "Удалить рабочее пространство",
49
+ "workspaces.settings.danger_zone.delete_confirm": "Удалить это рабочее пространство? Действие необратимо.",
50
+ "workspaces.members.title": "Участники",
51
+ "workspaces.members.subtitle": "Управляйте доступом к рабочему пространству.",
52
+ "workspaces.members.invite_dialog.title": "Пригласить участников",
53
+ "workspaces.members.invite_dialog.emails_label": "Email-адреса",
54
+ "workspaces.members.invite_dialog.emails_placeholder": "Введите email и нажмите Enter",
55
+ "workspaces.members.invite_dialog.role_label": "Роль",
56
+ "workspaces.members.remove_confirm": "Удалить этого участника?",
57
+ "workspaces.members.role.owner": "Владелец",
58
+ "workspaces.members.role.admin": "Администратор",
59
+ "workspaces.members.role.member": "Участник",
60
+ "workspaces.members.role.viewer": "Наблюдатель",
40
61
  };
41
62
 
42
63
  /**
@@ -1,5 +1,6 @@
1
1
  import { useQuery } from "@tanstack/react-query";
2
2
  import type { UseQueryResult } from "@tanstack/react-query";
3
+ import { useActiveSessionReady } from "@stapel/core";
3
4
  import type { StapelApiError } from "@stapel/core";
4
5
  import type {
5
6
  MemberPage,
@@ -16,45 +17,63 @@ import { workspacesQueryKeys } from "./queryKeys.js";
16
17
  * namespaced (see `workspacesQueryKeys`).
17
18
  */
18
19
 
19
- /** The caller's workspaces — their accepted memberships (GET /). */
20
+ /**
21
+ * The caller's workspaces — their accepted memberships (GET /).
22
+ *
23
+ * Gated on {@link useActiveSessionReady} (owner-diagnosed live incident,
24
+ * 2026-07-17): unlike {@link useWorkspace}/{@link useMembers} below (both
25
+ * naturally disabled until a `workspaceId` is picked), this is the top-level
26
+ * list hook with nothing else to gate it — it fires the instant a component
27
+ * mounts. Without the session ready-gate this is exactly the shape of hook
28
+ * that raced a session still bootstrapping (e.g. right after a QR
29
+ * `session_share` scan set fresh cookies this JS runtime hadn't caught up
30
+ * to yet) and read a live session as "expired" before it had a chance to
31
+ * resolve — zero manual `enabled` wiring needed at each call site by design.
32
+ */
20
33
  export function useWorkspaces(): UseQueryResult<WorkspaceList, StapelApiError> {
21
34
  const api = useWorkspacesApi();
35
+ const sessionReady = useActiveSessionReady();
22
36
  return useQuery({
23
37
  queryKey: workspacesQueryKeys.list(),
24
38
  queryFn: () => api.listWorkspaces(),
39
+ enabled: sessionReady,
25
40
  });
26
41
  }
27
42
 
28
43
  /**
29
44
  * A single workspace by id (GET /{id}). Disabled until a `workspaceId` is
30
- * given, so a detail hook can mount before a selection exists.
45
+ * given, so a detail hook can mount before a selection exists — AND until
46
+ * the session is ready (a `workspaceId` can be known synchronously, e.g.
47
+ * from a URL param, before the session has finished bootstrapping).
31
48
  */
32
49
  export function useWorkspace(
33
50
  workspaceId: string | null
34
51
  ): UseQueryResult<Workspace, StapelApiError> {
35
52
  const api = useWorkspacesApi();
53
+ const sessionReady = useActiveSessionReady();
36
54
  return useQuery({
37
55
  queryKey: workspacesQueryKeys.detail(workspaceId ?? ""),
38
56
  queryFn: () => api.getWorkspace(workspaceId as string),
39
- enabled: workspaceId !== null && workspaceId !== "",
57
+ enabled: sessionReady && workspaceId !== null && workspaceId !== "",
40
58
  });
41
59
  }
42
60
 
43
61
  /**
44
62
  * A page of a workspace's members (GET /{id}/members, anchor-paginated).
45
- * Disabled until a `workspaceId` is given. Pass `{ anchor, direction, limit,
46
- * search }` to jump to a specific page or filter; omit for the newest page
47
- * (default limit 100).
63
+ * Disabled until a `workspaceId` is given (and the session is ready — see
64
+ * {@link useWorkspace}). Pass `{ anchor, direction, limit, search }` to jump
65
+ * to a specific page or filter; omit for the newest page (default limit 100).
48
66
  */
49
67
  export function useMembers(
50
68
  workspaceId: string | null,
51
69
  params?: MembersParams
52
70
  ): UseQueryResult<MemberPage, StapelApiError> {
53
71
  const api = useWorkspacesApi();
72
+ const sessionReady = useActiveSessionReady();
54
73
  const p = params ?? {};
55
74
  return useQuery({
56
75
  queryKey: workspacesQueryKeys.membersPage(workspaceId ?? "", p),
57
76
  queryFn: () => api.listMembers(workspaceId as string, p),
58
- enabled: workspaceId !== null && workspaceId !== "",
77
+ enabled: sessionReady && workspaceId !== null && workspaceId !== "",
59
78
  });
60
79
  }