@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 +32 -0
- package/dist/default/MembersManager.d.ts +14 -0
- package/dist/default/MembersManager.d.ts.map +1 -0
- package/dist/default/MembersManager.js +77 -0
- package/dist/default/MembersManager.js.map +1 -0
- package/dist/default/WorkspaceSettings.d.ts +9 -0
- package/dist/default/WorkspaceSettings.d.ts.map +1 -0
- package/dist/default/WorkspaceSettings.js +44 -0
- package/dist/default/WorkspaceSettings.js.map +1 -0
- package/dist/default/index.d.ts +18 -0
- package/dist/default/index.d.ts.map +1 -0
- package/dist/default/index.js +16 -0
- package/dist/default/index.js.map +1 -0
- package/dist/i18n/keys.d.ts +21 -0
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +44 -0
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +21 -0
- package/dist/i18n/ru.js.map +1 -1
- package/dist/model/queries.d.ts +19 -5
- package/dist/model/queries.d.ts.map +1 -1
- package/dist/model/queries.js +26 -7
- package/dist/model/queries.js.map +1 -1
- package/llms.txt +1 -1
- package/manifest.json +23 -2
- package/package.json +20 -4
- package/src/analytics/generated/events.json +1 -1
- package/src/default/MembersManager.tsx +185 -0
- package/src/default/WorkspaceSettings.tsx +128 -0
- package/src/default/index.ts +17 -0
- package/src/i18n/keys.ts +44 -0
- package/src/i18n/ru.ts +21 -0
- package/src/model/queries.ts +26 -7
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":"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"}
|
|
@@ -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"}
|
package/dist/i18n/keys.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/i18n/keys.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
package/dist/i18n/keys.js.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"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"}
|
package/dist/i18n/ru.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
package/dist/i18n/ru.js.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/model/queries.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
|
19
|
-
*
|
|
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;
|
|
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"}
|
package/dist/model/queries.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
|
32
|
-
*
|
|
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;
|
|
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
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
|
+
"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.
|
|
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.
|
|
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.
|
|
70
|
-
"@stapel/
|
|
71
|
-
"@stapel/
|
|
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"
|
|
@@ -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
|
/**
|
package/src/model/queries.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
|
46
|
-
*
|
|
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
|
}
|