@jskit-ai/users-web 0.1.4
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/package.descriptor.mjs +507 -0
- package/package.json +31 -0
- package/src/client/components/ConsoleSettingsClientElement.vue +24 -0
- package/src/client/components/MembersAdminClientElement.vue +404 -0
- package/src/client/components/ProfileClientElement.vue +242 -0
- package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +39 -0
- package/src/client/components/UsersShellMenuLinkItem.vue +140 -0
- package/src/client/components/UsersSurfaceAwareMenuLinkItem.vue +87 -0
- package/src/client/components/UsersWorkspaceMembersMenuItem.vue +36 -0
- package/src/client/components/UsersWorkspacePermissionMenuItem.vue +90 -0
- package/src/client/components/UsersWorkspaceSelector.vue +237 -0
- package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +39 -0
- package/src/client/components/UsersWorkspaceToolsWidget.vue +23 -0
- package/src/client/components/WorkspaceMembersClientElement.vue +663 -0
- package/src/client/components/WorkspaceSettingsClientElement.vue +230 -0
- package/src/client/components/WorkspacesClientElement.vue +514 -0
- package/src/client/composables/accountSettingsAvatarUploadRuntime.js +241 -0
- package/src/client/composables/accountSettingsInvitesRuntime.js +88 -0
- package/src/client/composables/accountSettingsRuntimeConstants.js +77 -0
- package/src/client/composables/accountSettingsRuntimeHelpers.js +75 -0
- package/src/client/composables/errorMessageHelpers.js +66 -0
- package/src/client/composables/internal/useOperationScope.js +144 -0
- package/src/client/composables/modelStateHelpers.js +49 -0
- package/src/client/composables/operationUiHelpers.js +121 -0
- package/src/client/composables/operationValidationHelpers.js +52 -0
- package/src/client/composables/refValueHelpers.js +19 -0
- package/src/client/composables/scopeHelpers.js +145 -0
- package/src/client/composables/useAccess.js +109 -0
- package/src/client/composables/useAccountSettingsRuntime.js +533 -0
- package/src/client/composables/useAddEdit.js +135 -0
- package/src/client/composables/useAddEditCore.js +137 -0
- package/src/client/composables/useBootstrapQuery.js +52 -0
- package/src/client/composables/useCommand.js +112 -0
- package/src/client/composables/useCommandCore.js +130 -0
- package/src/client/composables/useEndpointResource.js +104 -0
- package/src/client/composables/useFieldErrorBag.js +61 -0
- package/src/client/composables/useList.js +85 -0
- package/src/client/composables/useListCore.js +65 -0
- package/src/client/composables/usePagedCollection.js +125 -0
- package/src/client/composables/usePaths.js +108 -0
- package/src/client/composables/useRealtimeQueryInvalidation.js +105 -0
- package/src/client/composables/useScopeRuntime.js +107 -0
- package/src/client/composables/useSurfaceRouteContext.js +31 -0
- package/src/client/composables/useUiFeedback.js +96 -0
- package/src/client/composables/useView.js +89 -0
- package/src/client/composables/useViewCore.js +104 -0
- package/src/client/composables/useWorkspaceRouteContext.js +28 -0
- package/src/client/composables/useWorkspaceSurfaceId.js +43 -0
- package/src/client/index.js +7 -0
- package/src/client/lib/bootstrap.js +95 -0
- package/src/client/lib/httpClient.js +67 -0
- package/src/client/lib/menuIcons.js +192 -0
- package/src/client/lib/permissions.js +34 -0
- package/src/client/lib/profileSurfaceMenuLinks.js +142 -0
- package/src/client/lib/surfaceAccessPolicy.js +350 -0
- package/src/client/lib/theme.js +99 -0
- package/src/client/lib/workspaceLinkResolver.js +207 -0
- package/src/client/lib/workspaceSurfaceContext.js +82 -0
- package/src/client/lib/workspaceSurfacePaths.js +163 -0
- package/src/client/providers/UsersWebClientProvider.js +85 -0
- package/src/client/runtime/bootstrapPlacementRouteGuards.js +371 -0
- package/src/client/runtime/bootstrapPlacementRuntime.js +413 -0
- package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +32 -0
- package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +157 -0
- package/src/client/support/contractGuards.js +34 -0
- package/src/client/support/realtimeWorkspace.js +12 -0
- package/src/client/support/runtimeNormalization.js +27 -0
- package/src/client/support/workspaceQueryKeys.js +15 -0
- package/templates/packages/main/src/client/components/AccountPendingInvitesCue.vue +162 -0
- package/templates/src/components/WorkspaceNotFoundCard.vue +33 -0
- package/templates/src/components/account/settings/AccountSettingsClientElement.vue +153 -0
- package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +77 -0
- package/templates/src/components/account/settings/AccountSettingsNotificationsSection.vue +55 -0
- package/templates/src/components/account/settings/AccountSettingsPreferencesSection.vue +125 -0
- package/templates/src/components/account/settings/AccountSettingsProfileSection.vue +94 -0
- package/templates/src/composables/useWorkspaceNotFoundState.js +48 -0
- package/templates/src/pages/account/index.vue +17 -0
- package/templates/src/pages/admin/members/index.vue +7 -0
- package/templates/src/pages/admin/workspace/settings/index.vue +16 -0
- package/templates/src/pages/console/settings/index.vue +16 -0
- package/templates/src/surfaces/admin/index.vue +29 -0
- package/templates/src/surfaces/admin/root.vue +20 -0
- package/templates/src/surfaces/app/index.vue +27 -0
- package/templates/src/surfaces/app/root.vue +20 -0
- package/test/bootstrap.test.js +38 -0
- package/test/bootstrapPlacementRuntime.test.js +991 -0
- package/test/errorMessageHelpers.test.js +28 -0
- package/test/exportsContract.test.js +39 -0
- package/test/menuIcons.test.js +33 -0
- package/test/permissions.test.js +35 -0
- package/test/profileSurfaceMenuLinks.test.js +207 -0
- package/test/refValueHelpers.test.js +14 -0
- package/test/scopeHelpers.test.js +57 -0
- package/test/surfaceAccessPolicy.test.js +129 -0
- package/test/theme.test.js +95 -0
- package/test/workspaceLinkResolver.test.js +61 -0
- package/test/workspaceSurfacePaths.test.js +39 -0
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="workspace-members-page">
|
|
3
|
+
<p v-if="loadError" class="text-body-2 text-medium-emphasis mb-4">
|
|
4
|
+
{{ loadError }}
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<MembersAdminClientElement
|
|
8
|
+
v-else
|
|
9
|
+
:forms="forms"
|
|
10
|
+
:options="options"
|
|
11
|
+
:collections="collections"
|
|
12
|
+
:permissions="permissionState"
|
|
13
|
+
:revokeInviteId="revokeInviteId"
|
|
14
|
+
:removeMemberUserId="removeMemberUserId"
|
|
15
|
+
:status="status"
|
|
16
|
+
:actions="actions"
|
|
17
|
+
/>
|
|
18
|
+
</section>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup>
|
|
22
|
+
import { computed, reactive, ref, watch } from "vue";
|
|
23
|
+
import MembersAdminClientElement from "./MembersAdminClientElement.vue";
|
|
24
|
+
import { useCommand } from "../composables/useCommand.js";
|
|
25
|
+
import { useList } from "../composables/useList.js";
|
|
26
|
+
import { useView } from "../composables/useView.js";
|
|
27
|
+
import { usePaths } from "../composables/usePaths.js";
|
|
28
|
+
import { useAccess } from "../composables/useAccess.js";
|
|
29
|
+
import { useUiFeedback } from "../composables/useUiFeedback.js";
|
|
30
|
+
import { useWorkspaceRouteContext } from "../composables/useWorkspaceRouteContext.js";
|
|
31
|
+
import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
|
|
32
|
+
import { matchesCurrentWorkspaceEvent } from "../support/realtimeWorkspace.js";
|
|
33
|
+
import { buildWorkspaceQueryKey } from "../support/workspaceQueryKeys.js";
|
|
34
|
+
import {
|
|
35
|
+
WORKSPACE_SETTINGS_CHANGED_EVENT,
|
|
36
|
+
WORKSPACE_MEMBERS_CHANGED_EVENT,
|
|
37
|
+
WORKSPACE_INVITES_CHANGED_EVENT
|
|
38
|
+
} from "@jskit-ai/users-core/shared/events/usersEvents";
|
|
39
|
+
import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
40
|
+
|
|
41
|
+
const forms = reactive({
|
|
42
|
+
invite: {
|
|
43
|
+
email: "",
|
|
44
|
+
roleId: "member"
|
|
45
|
+
},
|
|
46
|
+
workspace: {
|
|
47
|
+
invitesEnabled: false,
|
|
48
|
+
invitesAvailable: false
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const options = reactive({
|
|
53
|
+
inviteRoleOptions: [],
|
|
54
|
+
memberRoleOptions: [],
|
|
55
|
+
formatDateTime(value) {
|
|
56
|
+
const parsedDate = new Date(value);
|
|
57
|
+
if (Number.isNaN(parsedDate.getTime())) {
|
|
58
|
+
return "unknown";
|
|
59
|
+
}
|
|
60
|
+
return parsedDate.toLocaleString();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const collections = reactive({
|
|
65
|
+
members: [],
|
|
66
|
+
invites: []
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const inviteFeedback = useUiFeedback();
|
|
70
|
+
const membersFeedback = useUiFeedback();
|
|
71
|
+
const teamFeedback = useUiFeedback();
|
|
72
|
+
const revokeInviteId = ref(0);
|
|
73
|
+
const removeMemberUserId = ref(0);
|
|
74
|
+
|
|
75
|
+
const { route, currentSurfaceId, workspaceSlugFromRoute, mergePlacementContext } =
|
|
76
|
+
useWorkspaceRouteContext();
|
|
77
|
+
const usersPaths = usePaths();
|
|
78
|
+
const errorRuntime = useShellWebErrorRuntime();
|
|
79
|
+
const OWNERSHIP_WORKSPACE = USERS_ROUTE_VISIBILITY_WORKSPACE;
|
|
80
|
+
|
|
81
|
+
const hasRouteWorkspaceSlug = computed(() => Boolean(workspaceSlugFromRoute.value));
|
|
82
|
+
const workspaceMembersApiPath = computed(() =>
|
|
83
|
+
usersPaths.api("/members", {
|
|
84
|
+
workspaceSlug: workspaceSlugFromRoute.value
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
const workspaceInvitesApiPath = computed(() =>
|
|
88
|
+
usersPaths.api("/invites", {
|
|
89
|
+
workspaceSlug: workspaceSlugFromRoute.value
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
function workspaceMembersPath(memberId) {
|
|
94
|
+
return `${workspaceMembersApiPath.value}/${Number(memberId || 0)}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function workspaceInvitePath(inviteId) {
|
|
98
|
+
const encodedInviteId = encodeURIComponent(String(inviteId || ""));
|
|
99
|
+
return `${workspaceInvitesApiPath.value}/${encodedInviteId}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const access = useAccess({
|
|
103
|
+
workspaceSlug: workspaceSlugFromRoute,
|
|
104
|
+
enabled: hasRouteWorkspaceSlug,
|
|
105
|
+
mergePlacementContext,
|
|
106
|
+
placementSource: "users-web.workspace-members-view"
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function isCurrentWorkspaceRealtimeEvent({ payload = {} } = {}) {
|
|
110
|
+
return matchesCurrentWorkspaceEvent(payload, workspaceSlugFromRoute.value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const canViewMembers = computed(() => {
|
|
114
|
+
return access.canAny(["workspace.members.view", "workspace.members.manage"]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const canInviteMembers = computed(() => {
|
|
118
|
+
return access.can("workspace.members.invite");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const canManageMembers = computed(() => {
|
|
122
|
+
return access.can("workspace.members.manage");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const canRevokeInvites = computed(() => {
|
|
126
|
+
return access.can("workspace.invites.revoke");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const permissionState = computed(() => {
|
|
130
|
+
return {
|
|
131
|
+
canViewMembers: canViewMembers.value,
|
|
132
|
+
canInviteMembers: canInviteMembers.value,
|
|
133
|
+
canManageMembers: canManageMembers.value,
|
|
134
|
+
canRevokeInvites: canRevokeInvites.value
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
function resetMessages() {
|
|
139
|
+
inviteFeedback.clear();
|
|
140
|
+
membersFeedback.clear();
|
|
141
|
+
teamFeedback.clear();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function clearRoleOptions() {
|
|
145
|
+
options.inviteRoleOptions = [];
|
|
146
|
+
options.memberRoleOptions = [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function resetViewState() {
|
|
150
|
+
resetMessages();
|
|
151
|
+
forms.invite.email = "";
|
|
152
|
+
forms.invite.roleId = "member";
|
|
153
|
+
forms.workspace.invitesEnabled = false;
|
|
154
|
+
forms.workspace.invitesAvailable = false;
|
|
155
|
+
collections.members = [];
|
|
156
|
+
collections.invites = [];
|
|
157
|
+
clearRoleOptions();
|
|
158
|
+
revokeInviteId.value = 0;
|
|
159
|
+
removeMemberUserId.value = 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function toRoleTitle(roleId) {
|
|
163
|
+
const normalizedRoleId = String(roleId || "").trim();
|
|
164
|
+
if (!normalizedRoleId) {
|
|
165
|
+
return "";
|
|
166
|
+
}
|
|
167
|
+
return normalizedRoleId.charAt(0).toUpperCase() + normalizedRoleId.slice(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function normalizeRoleCatalog(payload = {}) {
|
|
171
|
+
const source =
|
|
172
|
+
payload?.roleCatalog && typeof payload.roleCatalog === "object"
|
|
173
|
+
? payload.roleCatalog
|
|
174
|
+
: payload && typeof payload === "object"
|
|
175
|
+
? payload
|
|
176
|
+
: {};
|
|
177
|
+
|
|
178
|
+
const roles = Array.isArray(source.roles) ? source.roles : [];
|
|
179
|
+
const assignableRoleIdsFromCatalog = Array.isArray(source.assignableRoleIds)
|
|
180
|
+
? source.assignableRoleIds
|
|
181
|
+
: [];
|
|
182
|
+
|
|
183
|
+
let assignableRoleIds = assignableRoleIdsFromCatalog
|
|
184
|
+
.map((entry) => String(entry || "").trim().toLowerCase())
|
|
185
|
+
.filter(Boolean);
|
|
186
|
+
|
|
187
|
+
if (assignableRoleIds.length < 1) {
|
|
188
|
+
assignableRoleIds = roles
|
|
189
|
+
.filter((entry) => entry?.assignable === true)
|
|
190
|
+
.map((entry) => String(entry?.id || "").trim().toLowerCase())
|
|
191
|
+
.filter(Boolean);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const uniqueRoleIds = Array.from(new Set(assignableRoleIds));
|
|
195
|
+
const roleOptions = uniqueRoleIds.map((roleId) => ({
|
|
196
|
+
title: toRoleTitle(roleId),
|
|
197
|
+
value: roleId
|
|
198
|
+
}));
|
|
199
|
+
|
|
200
|
+
const defaultInviteRole = String(source.defaultInviteRole || "")
|
|
201
|
+
.trim()
|
|
202
|
+
.toLowerCase();
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
roleOptions,
|
|
206
|
+
defaultInviteRole
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function applyRoleCatalog(payload = {}) {
|
|
211
|
+
const normalizedCatalog = normalizeRoleCatalog(payload);
|
|
212
|
+
options.inviteRoleOptions = [...normalizedCatalog.roleOptions];
|
|
213
|
+
options.memberRoleOptions = [...normalizedCatalog.roleOptions];
|
|
214
|
+
|
|
215
|
+
const selectedInviteRole = String(forms.invite.roleId || "").trim().toLowerCase();
|
|
216
|
+
const hasSelectedInviteRole = normalizedCatalog.roleOptions.some((entry) => entry.value === selectedInviteRole);
|
|
217
|
+
|
|
218
|
+
if (
|
|
219
|
+
normalizedCatalog.defaultInviteRole &&
|
|
220
|
+
normalizedCatalog.roleOptions.some((entry) => entry.value === normalizedCatalog.defaultInviteRole)
|
|
221
|
+
) {
|
|
222
|
+
forms.invite.roleId = normalizedCatalog.defaultInviteRole;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!hasSelectedInviteRole && normalizedCatalog.roleOptions.length > 0) {
|
|
227
|
+
forms.invite.roleId = normalizedCatalog.roleOptions[0].value;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function normalizeMembers(entries) {
|
|
232
|
+
const source = Array.isArray(entries) ? entries : [];
|
|
233
|
+
return source.map((entry) => {
|
|
234
|
+
const value = entry && typeof entry === "object" ? entry : {};
|
|
235
|
+
return {
|
|
236
|
+
userId: Number(value.userId || 0),
|
|
237
|
+
roleId: String(value.roleId || "").trim().toLowerCase(),
|
|
238
|
+
status: String(value.status || "").trim().toLowerCase(),
|
|
239
|
+
displayName: String(value.displayName || "").trim(),
|
|
240
|
+
email: String(value.email || "").trim().toLowerCase(),
|
|
241
|
+
isOwner: Boolean(value.isOwner)
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function normalizeInvites(entries) {
|
|
247
|
+
const source = Array.isArray(entries) ? entries : [];
|
|
248
|
+
return source.map((entry) => {
|
|
249
|
+
const value = entry && typeof entry === "object" ? entry : {};
|
|
250
|
+
return {
|
|
251
|
+
id: Number(value.id || 0),
|
|
252
|
+
email: String(value.email || "").trim().toLowerCase(),
|
|
253
|
+
roleId: String(value.roleId || "").trim().toLowerCase(),
|
|
254
|
+
status: String(value.status || "").trim().toLowerCase(),
|
|
255
|
+
expiresAt: value.expiresAt || "",
|
|
256
|
+
invitedByUserId: value.invitedByUserId == null ? null : Number(value.invitedByUserId)
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function latestPage(pages) {
|
|
262
|
+
if (!Array.isArray(pages) || pages.length < 1) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return pages[pages.length - 1];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function applyWorkspaceSettingsPolicy(payload = {}) {
|
|
270
|
+
const settings = payload?.settings && typeof payload.settings === "object" ? payload.settings : {};
|
|
271
|
+
forms.workspace.invitesEnabled = settings.invitesEnabled !== false;
|
|
272
|
+
forms.workspace.invitesAvailable = settings.invitesAvailable !== false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const workspaceSettingsView = useView({
|
|
276
|
+
ownershipFilter: OWNERSHIP_WORKSPACE,
|
|
277
|
+
apiSuffix: "/settings",
|
|
278
|
+
queryKeyFactory: (surfaceId = "", workspaceSlug = "") =>
|
|
279
|
+
buildWorkspaceQueryKey("settings", surfaceId, workspaceSlug),
|
|
280
|
+
viewPermissions: ["workspace.members.invite"],
|
|
281
|
+
realtime: {
|
|
282
|
+
event: WORKSPACE_SETTINGS_CHANGED_EVENT,
|
|
283
|
+
matches: isCurrentWorkspaceRealtimeEvent
|
|
284
|
+
},
|
|
285
|
+
fallbackLoadError: "Unable to load workspace settings."
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const workspaceRolesView = useView({
|
|
289
|
+
ownershipFilter: OWNERSHIP_WORKSPACE,
|
|
290
|
+
apiSuffix: "/roles",
|
|
291
|
+
queryKeyFactory: (surfaceId = "", workspaceSlug = "") => buildWorkspaceQueryKey("roles", surfaceId, workspaceSlug),
|
|
292
|
+
viewPermissions: ["workspace.members.view", "workspace.members.invite", "workspace.members.manage"],
|
|
293
|
+
fallbackLoadError: "Unable to load workspace roles."
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const workspaceMembersList = useList({
|
|
297
|
+
ownershipFilter: OWNERSHIP_WORKSPACE,
|
|
298
|
+
apiSuffix: "/members",
|
|
299
|
+
queryKeyFactory: (surfaceId = "", workspaceSlug = "") =>
|
|
300
|
+
buildWorkspaceQueryKey("members", surfaceId, workspaceSlug),
|
|
301
|
+
viewPermissions: ["workspace.members.view", "workspace.members.manage"],
|
|
302
|
+
realtime: {
|
|
303
|
+
event: WORKSPACE_MEMBERS_CHANGED_EVENT,
|
|
304
|
+
matches: isCurrentWorkspaceRealtimeEvent
|
|
305
|
+
},
|
|
306
|
+
selectItems: (payload) => normalizeMembers(payload?.members),
|
|
307
|
+
fallbackLoadError: "Unable to load workspace members."
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const workspaceInvitesList = useList({
|
|
311
|
+
ownershipFilter: OWNERSHIP_WORKSPACE,
|
|
312
|
+
apiSuffix: "/invites",
|
|
313
|
+
queryKeyFactory: (surfaceId = "", workspaceSlug = "") =>
|
|
314
|
+
buildWorkspaceQueryKey("invites", surfaceId, workspaceSlug),
|
|
315
|
+
viewPermissions: ["workspace.members.view", "workspace.members.manage"],
|
|
316
|
+
realtime: {
|
|
317
|
+
event: WORKSPACE_INVITES_CHANGED_EVENT,
|
|
318
|
+
matches: isCurrentWorkspaceRealtimeEvent
|
|
319
|
+
},
|
|
320
|
+
selectItems: (payload) => normalizeInvites(payload?.invites),
|
|
321
|
+
fallbackLoadError: "Unable to load workspace invites."
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const inviteCreateCommand = useCommand({
|
|
325
|
+
ownershipFilter: OWNERSHIP_WORKSPACE,
|
|
326
|
+
apiSuffix: "/invites",
|
|
327
|
+
runPermissions: ["workspace.members.invite"],
|
|
328
|
+
writeMethod: "POST",
|
|
329
|
+
fallbackRunError: "Unable to send invite.",
|
|
330
|
+
buildRawPayload: () => ({
|
|
331
|
+
email: forms.invite.email,
|
|
332
|
+
roleId: forms.invite.roleId
|
|
333
|
+
}),
|
|
334
|
+
messages: {
|
|
335
|
+
success: "Invite sent.",
|
|
336
|
+
error: "Unable to send invite."
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const revokeInviteCommand = useCommand({
|
|
341
|
+
ownershipFilter: OWNERSHIP_WORKSPACE,
|
|
342
|
+
apiSuffix: "/invites",
|
|
343
|
+
runPermissions: ["workspace.invites.revoke"],
|
|
344
|
+
writeMethod: "DELETE",
|
|
345
|
+
fallbackRunError: "Unable to revoke invite.",
|
|
346
|
+
buildCommandOptions: (_parsed, { context }) => {
|
|
347
|
+
return {
|
|
348
|
+
method: "DELETE",
|
|
349
|
+
path: workspaceInvitePath(context?.inviteId)
|
|
350
|
+
};
|
|
351
|
+
},
|
|
352
|
+
messages: {
|
|
353
|
+
success: "Invite revoked.",
|
|
354
|
+
error: "Unable to revoke invite."
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const memberRoleCommand = useCommand({
|
|
359
|
+
ownershipFilter: OWNERSHIP_WORKSPACE,
|
|
360
|
+
apiSuffix: "/members",
|
|
361
|
+
runPermissions: ["workspace.members.manage"],
|
|
362
|
+
writeMethod: "PATCH",
|
|
363
|
+
fallbackRunError: "Unable to update member role.",
|
|
364
|
+
buildRawPayload: (_model, { context }) => ({
|
|
365
|
+
roleId: String(context?.roleId || "").trim().toLowerCase()
|
|
366
|
+
}),
|
|
367
|
+
buildCommandOptions: (_parsed, { context }) => {
|
|
368
|
+
return {
|
|
369
|
+
method: "PATCH",
|
|
370
|
+
path: `${workspaceMembersPath(context?.memberUserId)}/role`
|
|
371
|
+
};
|
|
372
|
+
},
|
|
373
|
+
messages: {
|
|
374
|
+
success: "Member role updated.",
|
|
375
|
+
error: "Unable to update member role."
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const memberRemoveCommand = useCommand({
|
|
380
|
+
ownershipFilter: OWNERSHIP_WORKSPACE,
|
|
381
|
+
apiSuffix: "/members",
|
|
382
|
+
runPermissions: ["workspace.members.manage"],
|
|
383
|
+
writeMethod: "DELETE",
|
|
384
|
+
fallbackRunError: "Unable to remove member.",
|
|
385
|
+
buildCommandOptions: (_parsed, { context }) => {
|
|
386
|
+
return {
|
|
387
|
+
method: "DELETE",
|
|
388
|
+
path: workspaceMembersPath(context?.memberUserId)
|
|
389
|
+
};
|
|
390
|
+
},
|
|
391
|
+
messages: {
|
|
392
|
+
success: "Member removed.",
|
|
393
|
+
error: "Unable to remove member."
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const status = computed(() => {
|
|
398
|
+
return {
|
|
399
|
+
isCreatingInvite: Boolean(inviteCreateCommand.isRunning.value),
|
|
400
|
+
isRevokingInvite: Boolean(revokeInviteCommand.isRunning.value),
|
|
401
|
+
isRemovingMember: Boolean(memberRemoveCommand.isRunning.value),
|
|
402
|
+
hasLoadedWorkspaceSettings: !canInviteMembers.value || !workspaceSettingsView.isLoading.value,
|
|
403
|
+
hasLoadedMembersList: !canViewMembers.value || !workspaceMembersList.isInitialLoading.value,
|
|
404
|
+
hasLoadedInviteList: !canViewMembers.value || !workspaceInvitesList.isInitialLoading.value,
|
|
405
|
+
isRefreshingWorkspaceSettings: canInviteMembers.value && Boolean(workspaceSettingsView.isRefetching.value),
|
|
406
|
+
isRefreshingMembersList: canViewMembers.value && Boolean(workspaceMembersList.isRefetching.value),
|
|
407
|
+
isRefreshingInviteList: canViewMembers.value && Boolean(workspaceInvitesList.isRefetching.value)
|
|
408
|
+
};
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const loadError = computed(() => {
|
|
412
|
+
if (!hasRouteWorkspaceSlug.value) {
|
|
413
|
+
return "Workspace slug is required in the URL.";
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return access.bootstrapError.value;
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
watch(
|
|
420
|
+
loadError,
|
|
421
|
+
(nextLoadError) => {
|
|
422
|
+
if (!nextLoadError) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
errorRuntime.report({
|
|
426
|
+
source: "users-web.workspace-members-view",
|
|
427
|
+
severity: "error",
|
|
428
|
+
channel: "banner",
|
|
429
|
+
message: String(nextLoadError || "Unable to load workspace members."),
|
|
430
|
+
dedupeKey: `users-web.workspace-members-view:bootstrap:${nextLoadError}`,
|
|
431
|
+
dedupeWindowMs: 3000
|
|
432
|
+
});
|
|
433
|
+
},
|
|
434
|
+
{ immediate: true }
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const actions = Object.freeze({
|
|
438
|
+
submitInvite,
|
|
439
|
+
submitRevokeInvite,
|
|
440
|
+
submitMemberRoleUpdate,
|
|
441
|
+
submitRemoveMember
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
watch(
|
|
445
|
+
() => `${currentSurfaceId.value}:${workspaceSlugFromRoute.value}`,
|
|
446
|
+
() => {
|
|
447
|
+
resetViewState();
|
|
448
|
+
},
|
|
449
|
+
{ immediate: true }
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
watch(
|
|
453
|
+
() => workspaceSettingsView.record.value,
|
|
454
|
+
(payload) => {
|
|
455
|
+
if (!payload) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
applyWorkspaceSettingsPolicy(payload);
|
|
459
|
+
},
|
|
460
|
+
{ immediate: true }
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
watch(
|
|
464
|
+
() => workspaceSettingsView.loadError.value,
|
|
465
|
+
(nextLoadError) => {
|
|
466
|
+
if (!nextLoadError) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
forms.workspace.invitesEnabled = false;
|
|
470
|
+
forms.workspace.invitesAvailable = false;
|
|
471
|
+
}
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
watch(
|
|
475
|
+
() => workspaceRolesView.record.value,
|
|
476
|
+
(payload) => {
|
|
477
|
+
if (!payload) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
applyRoleCatalog(payload);
|
|
481
|
+
},
|
|
482
|
+
{ immediate: true }
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
watch(
|
|
486
|
+
() => workspaceRolesView.loadError.value,
|
|
487
|
+
(nextLoadError) => {
|
|
488
|
+
if (!nextLoadError) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
clearRoleOptions();
|
|
492
|
+
}
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
watch(
|
|
496
|
+
() => workspaceMembersList.items.value,
|
|
497
|
+
(nextMembers) => {
|
|
498
|
+
collections.members = Array.isArray(nextMembers) ? [...nextMembers] : [];
|
|
499
|
+
},
|
|
500
|
+
{ immediate: true }
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
watch(
|
|
504
|
+
() => workspaceMembersList.pages.value,
|
|
505
|
+
(pages) => {
|
|
506
|
+
const payload = latestPage(pages);
|
|
507
|
+
if (!payload) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
applyRoleCatalog(payload);
|
|
511
|
+
},
|
|
512
|
+
{ immediate: true }
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
watch(
|
|
516
|
+
() => workspaceMembersList.loadError.value,
|
|
517
|
+
(nextLoadError) => {
|
|
518
|
+
if (!nextLoadError) {
|
|
519
|
+
membersFeedback.clear();
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
membersFeedback.error(null, nextLoadError);
|
|
523
|
+
}
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
watch(
|
|
527
|
+
() => workspaceInvitesList.items.value,
|
|
528
|
+
(nextInvites) => {
|
|
529
|
+
collections.invites = Array.isArray(nextInvites) ? [...nextInvites] : [];
|
|
530
|
+
},
|
|
531
|
+
{ immediate: true }
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
watch(
|
|
535
|
+
() => workspaceInvitesList.pages.value,
|
|
536
|
+
(pages) => {
|
|
537
|
+
const payload = latestPage(pages);
|
|
538
|
+
if (!payload) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
applyRoleCatalog(payload);
|
|
542
|
+
},
|
|
543
|
+
{ immediate: true }
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
watch(
|
|
547
|
+
() => workspaceInvitesList.loadError.value,
|
|
548
|
+
(nextLoadError) => {
|
|
549
|
+
if (!nextLoadError) {
|
|
550
|
+
teamFeedback.clear();
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
teamFeedback.error(null, nextLoadError);
|
|
554
|
+
}
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
watch(
|
|
558
|
+
() => route.fullPath,
|
|
559
|
+
() => {
|
|
560
|
+
resetMessages();
|
|
561
|
+
}
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
async function submitInvite() {
|
|
565
|
+
if (inviteCreateCommand.isRunning.value || !canInviteMembers.value) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
inviteFeedback.clear();
|
|
570
|
+
|
|
571
|
+
try {
|
|
572
|
+
await inviteCreateCommand.run();
|
|
573
|
+
forms.invite.email = "";
|
|
574
|
+
await Promise.all([
|
|
575
|
+
workspaceInvitesList.reload(),
|
|
576
|
+
workspaceRolesView.refresh()
|
|
577
|
+
]);
|
|
578
|
+
inviteFeedback.success("Invite sent.");
|
|
579
|
+
} catch (error) {
|
|
580
|
+
inviteFeedback.error(error, "Unable to send invite.");
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function submitRevokeInvite(inviteId) {
|
|
585
|
+
if (revokeInviteCommand.isRunning.value || !canRevokeInvites.value) {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
revokeInviteId.value = Number(inviteId || 0);
|
|
590
|
+
teamFeedback.clear();
|
|
591
|
+
|
|
592
|
+
try {
|
|
593
|
+
await revokeInviteCommand.run({
|
|
594
|
+
inviteId
|
|
595
|
+
});
|
|
596
|
+
await Promise.all([
|
|
597
|
+
workspaceInvitesList.reload(),
|
|
598
|
+
workspaceRolesView.refresh()
|
|
599
|
+
]);
|
|
600
|
+
teamFeedback.success("Invite revoked.");
|
|
601
|
+
} catch (error) {
|
|
602
|
+
teamFeedback.error(error, "Unable to revoke invite.");
|
|
603
|
+
} finally {
|
|
604
|
+
revokeInviteId.value = 0;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async function submitMemberRoleUpdate(member, roleId) {
|
|
609
|
+
if (!canManageMembers.value) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
membersFeedback.clear();
|
|
614
|
+
|
|
615
|
+
try {
|
|
616
|
+
const memberUserId = Number(member?.userId || 0);
|
|
617
|
+
if (!Number.isInteger(memberUserId) || memberUserId < 1) {
|
|
618
|
+
throw new Error("Member user id is invalid.");
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
await memberRoleCommand.run({
|
|
622
|
+
memberUserId,
|
|
623
|
+
roleId
|
|
624
|
+
});
|
|
625
|
+
await Promise.all([
|
|
626
|
+
workspaceMembersList.reload(),
|
|
627
|
+
workspaceRolesView.refresh()
|
|
628
|
+
]);
|
|
629
|
+
membersFeedback.success("Member role updated.");
|
|
630
|
+
} catch (error) {
|
|
631
|
+
membersFeedback.error(error, "Unable to update member role.");
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async function submitRemoveMember(member) {
|
|
636
|
+
if (memberRemoveCommand.isRunning.value || !canManageMembers.value) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
membersFeedback.clear();
|
|
641
|
+
|
|
642
|
+
try {
|
|
643
|
+
const memberUserId = Number(member?.userId || 0);
|
|
644
|
+
if (!Number.isInteger(memberUserId) || memberUserId < 1) {
|
|
645
|
+
throw new Error("Member user id is invalid.");
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
removeMemberUserId.value = memberUserId;
|
|
649
|
+
await memberRemoveCommand.run({
|
|
650
|
+
memberUserId
|
|
651
|
+
});
|
|
652
|
+
await Promise.all([
|
|
653
|
+
workspaceMembersList.reload(),
|
|
654
|
+
workspaceRolesView.refresh()
|
|
655
|
+
]);
|
|
656
|
+
membersFeedback.success("Member removed.");
|
|
657
|
+
} catch (error) {
|
|
658
|
+
membersFeedback.error(error, "Unable to remove member.");
|
|
659
|
+
} finally {
|
|
660
|
+
removeMemberUserId.value = 0;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
</script>
|