@stapel/workspaces-react 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -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/llms.txt +1 -1
- package/manifest.json +23 -2
- package/package.json +18 -2
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @stapel/workspaces-react
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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).
|
|
8
|
+
|
|
3
9
|
## 0.4.1
|
|
4
10
|
|
|
5
11
|
### 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/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.5.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.5.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,7 +82,7 @@
|
|
|
66
82
|
"size-limit": "^11.2.0",
|
|
67
83
|
"typescript": "^5.8.3",
|
|
68
84
|
"vitest": "^3.2.4",
|
|
69
|
-
"@stapel/core": "^0.
|
|
85
|
+
"@stapel/core": "^0.5.0",
|
|
70
86
|
"@stapel/showcase": "^0.2.0",
|
|
71
87
|
"@stapel/tokens": "^0.4.0"
|
|
72
88
|
},
|
|
@@ -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
|
/**
|