@jskit-ai/users-web 0.1.53 → 0.1.55
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 +15 -53
- package/package.json +16 -11
- package/src/client/account-settings/sections.js +74 -0
- package/src/client/composables/account-settings/accountSettingsRuntimeHelpers.js +2 -38
- package/src/client/composables/crud/crudLookupFieldRuntime.js +2 -2
- package/src/client/composables/internal/crudListParentTitleSupport.js +1 -1
- package/src/client/composables/internal/useOperationScope.js +12 -12
- package/src/client/composables/records/useAddEdit.js +2 -2
- package/src/client/composables/records/useList.js +3 -3
- package/src/client/composables/records/useView.js +2 -2
- package/src/client/composables/support/scopeHelpers.js +19 -19
- package/src/client/composables/useAccess.js +3 -3
- package/src/client/composables/useAccountSettingsRuntime.js +8 -156
- package/src/client/composables/useCommand.js +2 -2
- package/src/client/composables/useCrudListParentTitle.js +2 -2
- package/src/client/composables/usePaths.js +50 -38
- package/src/client/composables/useScopeRuntime.js +55 -27
- package/src/client/composables/useSurfaceRouteContext.js +1 -7
- package/src/client/index.js +0 -1
- package/src/client/lib/bootstrap.js +0 -63
- package/src/client/lib/httpClient.js +2 -59
- package/src/client/lib/theme.js +12 -189
- package/src/client/providers/UsersWebClientProvider.js +2 -25
- package/src/client/providers/bootUsersWebClientProvider.js +28 -0
- package/src/shared/toolsOutletContracts.js +1 -8
- package/templates/src/components/account/settings/AccountSettingsClientElement.vue +33 -21
- package/test/accountSettingsSections.test.js +79 -0
- package/test/exportsContract.test.js +2 -2
- package/test/scopeHelpers.test.js +6 -6
- package/test/settingsPlacementContract.test.js +4 -11
- package/test/theme.test.js +0 -56
- package/src/client/components/MembersAdminClientElement.vue +0 -400
- package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +0 -39
- package/src/client/components/UsersWorkspaceMembersMenuItem.vue +0 -36
- package/src/client/components/UsersWorkspacePermissionMenuItem.vue +0 -90
- package/src/client/components/UsersWorkspaceSelector.vue +0 -248
- package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +0 -39
- package/src/client/components/UsersWorkspaceToolsWidget.vue +0 -12
- package/src/client/components/WorkspaceMembersClientElement.vue +0 -655
- package/src/client/components/WorkspaceProfileClientElement.vue +0 -116
- package/src/client/components/WorkspaceSettingsClientElement.vue +0 -102
- package/src/client/components/WorkspaceSettingsFieldsClientElement.vue +0 -265
- package/src/client/components/WorkspacesClientElement.vue +0 -509
- package/src/client/composables/account-settings/accountSettingsInvitesRuntime.js +0 -88
- package/src/client/composables/useBootstrapQuery.js +0 -52
- package/src/client/composables/useWorkspaceRouteContext.js +0 -28
- package/src/client/composables/useWorkspaceSurfaceId.js +0 -43
- package/src/client/lib/menuIcons.js +0 -201
- package/src/client/lib/profileSurfaceMenuLinks.js +0 -142
- package/src/client/lib/surfaceAccessPolicy.js +0 -350
- package/src/client/lib/workspaceLinkResolver.js +0 -207
- package/src/client/lib/workspaceSurfaceContext.js +0 -82
- package/src/client/lib/workspaceSurfacePaths.js +0 -163
- package/src/client/providers/UsersWorkspacesClientProvider.js +0 -24
- package/src/client/runtime/bootstrapPlacementRouteGuards.js +0 -371
- package/src/client/runtime/bootstrapPlacementRuntime.js +0 -463
- package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +0 -28
- package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +0 -147
- package/src/client/support/menuLinkTarget.js +0 -93
- package/src/client/support/realtimeWorkspace.js +0 -21
- package/src/client/support/runtimeNormalization.js +0 -27
- package/src/client/support/workspaceQueryKeys.js +0 -15
- package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +0 -77
- package/test/bootstrapPlacementRuntime.test.js +0 -1095
- package/test/menuIcons.test.js +0 -34
- package/test/menuLinkTarget.test.js +0 -116
- package/test/profileSurfaceMenuLinks.test.js +0 -208
- package/test/surfaceAccessPolicy.test.js +0 -129
- package/test/workspaceLinkResolver.test.js +0 -61
- package/test/workspaceSurfacePaths.test.js +0 -39
package/test/theme.test.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { ThemeSymbol } from "vuetify/lib/composables/theme.js";
|
|
4
|
-
import { resolveWorkspaceThemePalette } from "@jskit-ai/users-core/shared/settings";
|
|
5
4
|
import {
|
|
6
|
-
hexColorToRgb,
|
|
7
5
|
normalizeThemePreference,
|
|
8
6
|
persistBootstrapThemePreference,
|
|
9
7
|
persistThemePreference,
|
|
@@ -12,7 +10,6 @@ import {
|
|
|
12
10
|
resolveThemeNameForPreference,
|
|
13
11
|
resolveBootstrapThemeName,
|
|
14
12
|
resolveVuetifyThemeController,
|
|
15
|
-
setVuetifyPrimaryColorOverride,
|
|
16
13
|
setVuetifyThemeName
|
|
17
14
|
} from "../src/client/lib/theme.js";
|
|
18
15
|
|
|
@@ -167,56 +164,3 @@ test("setVuetifyThemeName updates only when the value changes", () => {
|
|
|
167
164
|
assert.equal(setVuetifyThemeName(themeController, "dark"), true);
|
|
168
165
|
assert.equal(themeController.global.name.value, "dark");
|
|
169
166
|
});
|
|
170
|
-
|
|
171
|
-
test("hexColorToRgb returns Vuetify rgb tuple and rejects invalid values", () => {
|
|
172
|
-
assert.equal(hexColorToRgb("#0f6b54"), "15,107,84");
|
|
173
|
-
assert.equal(hexColorToRgb("#CC3344"), "204,51,68");
|
|
174
|
-
assert.equal(hexColorToRgb("invalid"), "");
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("setVuetifyPrimaryColorOverride mutates workspace themes and restores base theme names", () => {
|
|
178
|
-
const themeController = createVuetifyThemeController("light");
|
|
179
|
-
const themeInput = {
|
|
180
|
-
lightPrimaryColor: "#CC3344",
|
|
181
|
-
lightSecondaryColor: "#884455",
|
|
182
|
-
lightSurfaceColor: "#F4F4F4",
|
|
183
|
-
lightSurfaceVariantColor: "#444444",
|
|
184
|
-
darkPrimaryColor: "#BB2233",
|
|
185
|
-
darkSecondaryColor: "#557799",
|
|
186
|
-
darkSurfaceColor: "#202020",
|
|
187
|
-
darkSurfaceVariantColor: "#A0A0A0"
|
|
188
|
-
};
|
|
189
|
-
const expectedLightPalette = resolveWorkspaceThemePalette(themeInput, {
|
|
190
|
-
mode: "light"
|
|
191
|
-
});
|
|
192
|
-
const expectedDarkPalette = resolveWorkspaceThemePalette(themeInput, {
|
|
193
|
-
mode: "dark"
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
assert.equal(setVuetifyPrimaryColorOverride(themeController, themeInput), true);
|
|
197
|
-
assert.equal(themeController.global.name.value, "workspace-light");
|
|
198
|
-
assert.equal(themeController.themes.value["workspace-light"].colors.primary, expectedLightPalette.color);
|
|
199
|
-
assert.equal(themeController.themes.value["workspace-light"].colors.secondary, expectedLightPalette.secondaryColor);
|
|
200
|
-
assert.equal(themeController.themes.value["workspace-light"].colors.surface, expectedLightPalette.surfaceColor);
|
|
201
|
-
assert.equal(
|
|
202
|
-
themeController.themes.value["workspace-light"].colors["surface-variant"],
|
|
203
|
-
expectedLightPalette.surfaceVariantColor
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
assert.equal(setVuetifyPrimaryColorOverride(themeController, themeInput), false);
|
|
207
|
-
|
|
208
|
-
assert.equal(setVuetifyThemeName(themeController, "dark"), true);
|
|
209
|
-
assert.equal(setVuetifyPrimaryColorOverride(themeController, themeInput), true);
|
|
210
|
-
assert.equal(themeController.global.name.value, "workspace-dark");
|
|
211
|
-
assert.equal(themeController.themes.value["workspace-dark"].colors.primary, expectedDarkPalette.color);
|
|
212
|
-
assert.equal(themeController.themes.value["workspace-dark"].colors.secondary, expectedDarkPalette.secondaryColor);
|
|
213
|
-
assert.equal(themeController.themes.value["workspace-dark"].colors.surface, expectedDarkPalette.surfaceColor);
|
|
214
|
-
assert.equal(
|
|
215
|
-
themeController.themes.value["workspace-dark"].colors["surface-variant"],
|
|
216
|
-
expectedDarkPalette.surfaceVariantColor
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
assert.equal(setVuetifyPrimaryColorOverride(themeController, null), true);
|
|
220
|
-
assert.equal(themeController.global.name.value, "dark");
|
|
221
|
-
assert.equal(setVuetifyPrimaryColorOverride(themeController, null), false);
|
|
222
|
-
});
|
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<section class="members-admin-client-element">
|
|
3
|
-
<v-row>
|
|
4
|
-
<v-col cols="12" lg="5">
|
|
5
|
-
<v-card rounded="lg" elevation="1" border data-testid="members-admin-invite-card">
|
|
6
|
-
<v-card-item>
|
|
7
|
-
<v-card-title class="text-subtitle-1">Invite people</v-card-title>
|
|
8
|
-
<v-card-subtitle>Send workspace invites with a role.</v-card-subtitle>
|
|
9
|
-
</v-card-item>
|
|
10
|
-
<v-divider />
|
|
11
|
-
<v-card-text>
|
|
12
|
-
<template v-if="showWorkspaceInviteLoadingSkeleton">
|
|
13
|
-
<v-skeleton-loader type="text@2, paragraph, button" class="mb-3" />
|
|
14
|
-
</template>
|
|
15
|
-
<template v-else>
|
|
16
|
-
<v-progress-linear v-if="showWorkspaceInviteRefreshingIndicator" indeterminate class="mb-3" />
|
|
17
|
-
<p
|
|
18
|
-
v-if="workspaceInvitePolicyLoaded && !workspaceInvitesAvailable"
|
|
19
|
-
class="text-body-2 text-medium-emphasis mb-3"
|
|
20
|
-
>
|
|
21
|
-
Invites are disabled by app policy or role manifest.
|
|
22
|
-
</p>
|
|
23
|
-
<p
|
|
24
|
-
v-else-if="workspaceInvitePolicyLoaded && !workspaceInvitesEnabled"
|
|
25
|
-
class="text-body-2 text-medium-emphasis mb-3"
|
|
26
|
-
>
|
|
27
|
-
Invites are currently off for this workspace.
|
|
28
|
-
</p>
|
|
29
|
-
|
|
30
|
-
<p v-if="!canInviteMembers" class="text-body-2 text-medium-emphasis mb-3">
|
|
31
|
-
You do not have permission to send invites.
|
|
32
|
-
</p>
|
|
33
|
-
|
|
34
|
-
<template v-else-if="canShowInviteForm">
|
|
35
|
-
<v-form @submit.prevent="onSubmitInvite" novalidate>
|
|
36
|
-
<v-text-field
|
|
37
|
-
v-model="inviteForm.email"
|
|
38
|
-
label="Email"
|
|
39
|
-
variant="outlined"
|
|
40
|
-
density="comfortable"
|
|
41
|
-
type="email"
|
|
42
|
-
autocomplete="email"
|
|
43
|
-
:disabled="isCreatingInvite || showWorkspaceInviteRefreshingIndicator"
|
|
44
|
-
class="mb-3"
|
|
45
|
-
/>
|
|
46
|
-
<v-select
|
|
47
|
-
v-model="inviteForm.roleSid"
|
|
48
|
-
label="Role"
|
|
49
|
-
:items="inviteRoleOptions"
|
|
50
|
-
item-title="title"
|
|
51
|
-
item-value="value"
|
|
52
|
-
variant="outlined"
|
|
53
|
-
density="comfortable"
|
|
54
|
-
:disabled="isCreatingInvite || showWorkspaceInviteRefreshingIndicator"
|
|
55
|
-
class="mb-3"
|
|
56
|
-
/>
|
|
57
|
-
<v-btn
|
|
58
|
-
type="submit"
|
|
59
|
-
color="primary"
|
|
60
|
-
:loading="isCreatingInvite"
|
|
61
|
-
:disabled="showWorkspaceInviteRefreshingIndicator"
|
|
62
|
-
>
|
|
63
|
-
Send invite
|
|
64
|
-
</v-btn>
|
|
65
|
-
</v-form>
|
|
66
|
-
</template>
|
|
67
|
-
</template>
|
|
68
|
-
</v-card-text>
|
|
69
|
-
</v-card>
|
|
70
|
-
</v-col>
|
|
71
|
-
|
|
72
|
-
<v-col cols="12" lg="7">
|
|
73
|
-
<v-card rounded="lg" elevation="1" border data-testid="members-admin-members-card">
|
|
74
|
-
<v-card-item>
|
|
75
|
-
<v-card-title class="text-subtitle-1">Team</v-card-title>
|
|
76
|
-
<v-card-subtitle>Members and pending invites.</v-card-subtitle>
|
|
77
|
-
</v-card-item>
|
|
78
|
-
<v-divider />
|
|
79
|
-
<v-card-text>
|
|
80
|
-
<template v-if="showMembersLoadingSkeleton">
|
|
81
|
-
<v-skeleton-loader type="text@2, list-item-avatar-two-line@3" class="mb-3" />
|
|
82
|
-
<v-divider class="mb-3" />
|
|
83
|
-
<v-skeleton-loader type="text, list-item-two-line@2" />
|
|
84
|
-
</template>
|
|
85
|
-
<template v-else>
|
|
86
|
-
<v-progress-linear v-if="showMembersRefreshingIndicator" indeterminate class="mb-3" />
|
|
87
|
-
<p v-if="!canViewMembers" class="text-body-2 text-medium-emphasis mb-0">
|
|
88
|
-
You do not have permission to view members.
|
|
89
|
-
</p>
|
|
90
|
-
|
|
91
|
-
<template v-else>
|
|
92
|
-
<div class="text-caption text-medium-emphasis mb-2">Members</div>
|
|
93
|
-
<v-list density="comfortable" class="pa-0 mb-3">
|
|
94
|
-
<v-list-item v-for="member in memberRows" :key="member.userId" class="px-0">
|
|
95
|
-
<template #title>
|
|
96
|
-
<div class="d-flex align-center ga-2">
|
|
97
|
-
<span>{{ member.displayName || member.email }}</span>
|
|
98
|
-
<v-chip v-if="showOwnerChip(member)" size="x-small" label color="secondary">Owner</v-chip>
|
|
99
|
-
</div>
|
|
100
|
-
</template>
|
|
101
|
-
<template #subtitle>
|
|
102
|
-
{{ member.email }}
|
|
103
|
-
</template>
|
|
104
|
-
|
|
105
|
-
<template #append>
|
|
106
|
-
<div class="d-flex align-center ga-2">
|
|
107
|
-
<v-select
|
|
108
|
-
v-model="member.roleSid"
|
|
109
|
-
:items="memberRoleOptions"
|
|
110
|
-
item-title="title"
|
|
111
|
-
item-value="value"
|
|
112
|
-
density="compact"
|
|
113
|
-
variant="outlined"
|
|
114
|
-
hide-details
|
|
115
|
-
class="member-role-select"
|
|
116
|
-
:disabled="showMembersRefreshingIndicator || isMemberRoleLocked(member)"
|
|
117
|
-
@update:model-value="(value) => onMemberRoleUpdate(member, value)"
|
|
118
|
-
/>
|
|
119
|
-
<v-btn
|
|
120
|
-
variant="text"
|
|
121
|
-
color="error"
|
|
122
|
-
:disabled="showMembersRefreshingIndicator || isMemberRemoveLocked(member)"
|
|
123
|
-
:loading="isRemoveMemberLoading(member.userId)"
|
|
124
|
-
@click="onRemoveMember(member)"
|
|
125
|
-
>
|
|
126
|
-
Remove
|
|
127
|
-
</v-btn>
|
|
128
|
-
</div>
|
|
129
|
-
</template>
|
|
130
|
-
</v-list-item>
|
|
131
|
-
</v-list>
|
|
132
|
-
|
|
133
|
-
<v-divider class="mb-3" />
|
|
134
|
-
|
|
135
|
-
<div class="text-caption text-medium-emphasis mb-2">Pending invites</div>
|
|
136
|
-
<v-list density="comfortable" class="pa-0">
|
|
137
|
-
<v-list-item v-for="invite in inviteRows" :key="invite.id" class="px-0">
|
|
138
|
-
<template #title>
|
|
139
|
-
{{ invite.email }}
|
|
140
|
-
</template>
|
|
141
|
-
<template #subtitle>
|
|
142
|
-
Role: {{ invite.roleSid }} • expires {{ formatDateTime(invite.expiresAt) }}
|
|
143
|
-
</template>
|
|
144
|
-
<template #append>
|
|
145
|
-
<v-btn
|
|
146
|
-
v-if="canRevokeInvites"
|
|
147
|
-
variant="text"
|
|
148
|
-
color="error"
|
|
149
|
-
:disabled="showMembersRefreshingIndicator"
|
|
150
|
-
:loading="isRevokeInviteLoading(invite.id)"
|
|
151
|
-
@click="onRevokeInvite(invite.id)"
|
|
152
|
-
>
|
|
153
|
-
Revoke
|
|
154
|
-
</v-btn>
|
|
155
|
-
</template>
|
|
156
|
-
</v-list-item>
|
|
157
|
-
<p v-if="inviteRows.length < 1" class="text-body-2 text-medium-emphasis mb-0">No pending invites.</p>
|
|
158
|
-
</v-list>
|
|
159
|
-
</template>
|
|
160
|
-
</template>
|
|
161
|
-
</v-card-text>
|
|
162
|
-
</v-card>
|
|
163
|
-
</v-col>
|
|
164
|
-
</v-row>
|
|
165
|
-
</section>
|
|
166
|
-
</template>
|
|
167
|
-
|
|
168
|
-
<script setup>
|
|
169
|
-
import { computed, toRefs, unref } from "vue";
|
|
170
|
-
import { formatDateTime as formatKernelDateTime } from "@jskit-ai/kernel/shared/support";
|
|
171
|
-
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
172
|
-
import { requireBoolean, requireFunction, requireRecord } from "../support/contractGuards.js";
|
|
173
|
-
|
|
174
|
-
const props = defineProps({
|
|
175
|
-
forms: {
|
|
176
|
-
type: Object,
|
|
177
|
-
required: true
|
|
178
|
-
},
|
|
179
|
-
options: {
|
|
180
|
-
type: Object,
|
|
181
|
-
required: true
|
|
182
|
-
},
|
|
183
|
-
collections: {
|
|
184
|
-
type: Object,
|
|
185
|
-
required: true
|
|
186
|
-
},
|
|
187
|
-
permissions: {
|
|
188
|
-
type: Object,
|
|
189
|
-
required: true
|
|
190
|
-
},
|
|
191
|
-
revokeInviteId: {
|
|
192
|
-
type: String,
|
|
193
|
-
required: true
|
|
194
|
-
},
|
|
195
|
-
removeMemberUserId: {
|
|
196
|
-
type: String,
|
|
197
|
-
required: true
|
|
198
|
-
},
|
|
199
|
-
status: {
|
|
200
|
-
type: Object,
|
|
201
|
-
required: true
|
|
202
|
-
},
|
|
203
|
-
actions: {
|
|
204
|
-
type: Object,
|
|
205
|
-
required: true
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
requireRecord(props.forms, "forms", "MembersAdminClientElement");
|
|
210
|
-
requireRecord(props.options, "options", "MembersAdminClientElement");
|
|
211
|
-
requireRecord(props.collections, "collections", "MembersAdminClientElement");
|
|
212
|
-
requireRecord(props.permissions, "permissions", "MembersAdminClientElement");
|
|
213
|
-
requireRecord(props.status, "status", "MembersAdminClientElement");
|
|
214
|
-
requireRecord(props.actions, "actions", "MembersAdminClientElement");
|
|
215
|
-
|
|
216
|
-
const {
|
|
217
|
-
forms,
|
|
218
|
-
options,
|
|
219
|
-
collections,
|
|
220
|
-
permissions,
|
|
221
|
-
revokeInviteId,
|
|
222
|
-
removeMemberUserId,
|
|
223
|
-
status,
|
|
224
|
-
actions
|
|
225
|
-
} = toRefs(props);
|
|
226
|
-
|
|
227
|
-
const actionHandlers = Object.freeze({
|
|
228
|
-
submitInvite: requireFunction(actions.value.submitInvite, "actions.submitInvite", "MembersAdminClientElement"),
|
|
229
|
-
submitRevokeInvite: requireFunction(
|
|
230
|
-
actions.value.submitRevokeInvite,
|
|
231
|
-
"actions.submitRevokeInvite",
|
|
232
|
-
"MembersAdminClientElement"
|
|
233
|
-
),
|
|
234
|
-
submitMemberRoleUpdate: requireFunction(
|
|
235
|
-
actions.value.submitMemberRoleUpdate,
|
|
236
|
-
"actions.submitMemberRoleUpdate",
|
|
237
|
-
"MembersAdminClientElement"
|
|
238
|
-
),
|
|
239
|
-
submitRemoveMember: requireFunction(
|
|
240
|
-
actions.value.submitRemoveMember,
|
|
241
|
-
"actions.submitRemoveMember",
|
|
242
|
-
"MembersAdminClientElement"
|
|
243
|
-
)
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const inviteForm = computed(() => requireRecord(forms.value.invite, "forms.invite", "MembersAdminClientElement"));
|
|
247
|
-
const workspaceForm = computed(() =>
|
|
248
|
-
requireRecord(forms.value.workspace, "forms.workspace", "MembersAdminClientElement")
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
const memberRows = computed(() => {
|
|
252
|
-
const source = collections.value.members;
|
|
253
|
-
return Array.isArray(unref(source)) ? unref(source) : [];
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
const inviteRows = computed(() => {
|
|
257
|
-
const source = collections.value.invites;
|
|
258
|
-
return Array.isArray(unref(source)) ? unref(source) : [];
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
const inviteRoleOptions = computed(() => {
|
|
262
|
-
const source = options.value.inviteRoleOptions;
|
|
263
|
-
return Array.isArray(unref(source)) ? unref(source) : [];
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const memberRoleOptions = computed(() => {
|
|
267
|
-
const source = options.value.memberRoleOptions;
|
|
268
|
-
return Array.isArray(unref(source)) ? unref(source) : [];
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
const canViewMembers = computed(() => Boolean(unref(permissions.value.canViewMembers)));
|
|
272
|
-
const canInviteMembers = computed(() => Boolean(unref(permissions.value.canInviteMembers)));
|
|
273
|
-
const canManageMembers = computed(() => Boolean(unref(permissions.value.canManageMembers)));
|
|
274
|
-
const canRevokeInvites = computed(() => Boolean(unref(permissions.value.canRevokeInvites)));
|
|
275
|
-
const isCreatingInvite = computed(() => Boolean(unref(status.value.isCreatingInvite)));
|
|
276
|
-
const isRevokingInvite = computed(() => Boolean(unref(status.value.isRevokingInvite)));
|
|
277
|
-
const isRemovingMember = computed(() => Boolean(unref(status.value.isRemovingMember)));
|
|
278
|
-
const workspaceInvitePolicyLoaded = computed(() =>
|
|
279
|
-
requireBoolean(status.value.hasLoadedWorkspaceSettings, "status.hasLoadedWorkspaceSettings", "MembersAdminClientElement")
|
|
280
|
-
);
|
|
281
|
-
const workspaceInvitePolicyRefreshing = computed(() =>
|
|
282
|
-
requireBoolean(
|
|
283
|
-
status.value.isRefreshingWorkspaceSettings,
|
|
284
|
-
"status.isRefreshingWorkspaceSettings",
|
|
285
|
-
"MembersAdminClientElement"
|
|
286
|
-
)
|
|
287
|
-
);
|
|
288
|
-
const membersListLoaded = computed(() =>
|
|
289
|
-
requireBoolean(status.value.hasLoadedMembersList, "status.hasLoadedMembersList", "MembersAdminClientElement")
|
|
290
|
-
);
|
|
291
|
-
const membersListRefreshing = computed(() =>
|
|
292
|
-
requireBoolean(status.value.isRefreshingMembersList, "status.isRefreshingMembersList", "MembersAdminClientElement")
|
|
293
|
-
);
|
|
294
|
-
const inviteListLoaded = computed(() =>
|
|
295
|
-
requireBoolean(status.value.hasLoadedInviteList, "status.hasLoadedInviteList", "MembersAdminClientElement")
|
|
296
|
-
);
|
|
297
|
-
const inviteListRefreshing = computed(() =>
|
|
298
|
-
requireBoolean(status.value.isRefreshingInviteList, "status.isRefreshingInviteList", "MembersAdminClientElement")
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
const showWorkspaceInviteLoadingSkeleton = computed(
|
|
302
|
-
() => canInviteMembers.value && !workspaceInvitePolicyLoaded.value
|
|
303
|
-
);
|
|
304
|
-
const showWorkspaceInviteRefreshingIndicator = computed(
|
|
305
|
-
() => canInviteMembers.value && workspaceInvitePolicyLoaded.value && workspaceInvitePolicyRefreshing.value
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
const showMembersLoadingSkeleton = computed(
|
|
309
|
-
() =>
|
|
310
|
-
canViewMembers.value &&
|
|
311
|
-
(!membersListLoaded.value || !inviteListLoaded.value)
|
|
312
|
-
);
|
|
313
|
-
const showMembersRefreshingIndicator = computed(
|
|
314
|
-
() =>
|
|
315
|
-
canViewMembers.value &&
|
|
316
|
-
membersListLoaded.value &&
|
|
317
|
-
inviteListLoaded.value &&
|
|
318
|
-
(membersListRefreshing.value || inviteListRefreshing.value)
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
const workspaceInvitesAvailable = computed(() => Boolean(unref(workspaceForm.value.invitesAvailable)));
|
|
322
|
-
const workspaceInvitesEnabled = computed(() => Boolean(unref(workspaceForm.value.invitesEnabled)));
|
|
323
|
-
|
|
324
|
-
const canShowInviteForm = computed(
|
|
325
|
-
() => canInviteMembers.value && workspaceInvitesAvailable.value && workspaceInvitesEnabled.value
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
function formatDateTime(value) {
|
|
329
|
-
if (typeof options.value.formatDateTime === "function") {
|
|
330
|
-
return options.value.formatDateTime(value);
|
|
331
|
-
}
|
|
332
|
-
return formatKernelDateTime(value);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function showOwnerChip(member) {
|
|
336
|
-
return Boolean(member?.isOwner);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function isMemberRoleLocked(member) {
|
|
340
|
-
if (!canManageMembers.value) {
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return Boolean(member?.isOwner);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function isMemberRemoveLocked(member) {
|
|
348
|
-
if (!canManageMembers.value) {
|
|
349
|
-
return true;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return Boolean(member?.isOwner);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function isRevokeInviteLoading(inviteId) {
|
|
356
|
-
return isRevokingInvite.value && revokeInviteId.value === normalizeRecordId(inviteId, { fallback: "" });
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function isRemoveMemberLoading(memberUserId) {
|
|
360
|
-
return isRemovingMember.value && removeMemberUserId.value === normalizeRecordId(memberUserId, { fallback: "" });
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
async function onSubmitInvite() {
|
|
364
|
-
if (!canShowInviteForm.value) {
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
await actionHandlers.submitInvite();
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async function onRevokeInvite(inviteId) {
|
|
372
|
-
if (!canRevokeInvites.value) {
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
await actionHandlers.submitRevokeInvite(inviteId);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async function onMemberRoleUpdate(member, roleSid) {
|
|
380
|
-
if (isMemberRoleLocked(member)) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
await actionHandlers.submitMemberRoleUpdate(member, roleSid);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
async function onRemoveMember(member) {
|
|
388
|
-
if (isMemberRemoveLocked(member)) {
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
await actionHandlers.submitRemoveMember(member);
|
|
393
|
-
}
|
|
394
|
-
</script>
|
|
395
|
-
|
|
396
|
-
<style scoped>
|
|
397
|
-
.member-role-select {
|
|
398
|
-
width: 160px;
|
|
399
|
-
}
|
|
400
|
-
</style>
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { computed } from "vue";
|
|
3
|
-
import { useSurfaceRouteContext } from "../composables/useSurfaceRouteContext.js";
|
|
4
|
-
import { resolveProfileSurfaceMenuLinks } from "../lib/profileSurfaceMenuLinks.js";
|
|
5
|
-
|
|
6
|
-
const props = defineProps({
|
|
7
|
-
surface: {
|
|
8
|
-
type: String,
|
|
9
|
-
default: "*"
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const { placementContext, currentSurfaceId } = useSurfaceRouteContext();
|
|
14
|
-
|
|
15
|
-
const resolvedSurfaceId = computed(() => {
|
|
16
|
-
const explicitSurface = String(props.surface || "").trim().toLowerCase();
|
|
17
|
-
if (explicitSurface && explicitSurface !== "*") {
|
|
18
|
-
return explicitSurface;
|
|
19
|
-
}
|
|
20
|
-
return String(currentSurfaceId.value || "").trim().toLowerCase() || "*";
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const resolvedLinks = computed(() => {
|
|
24
|
-
return resolveProfileSurfaceMenuLinks({
|
|
25
|
-
context: placementContext.value,
|
|
26
|
-
surface: resolvedSurfaceId.value
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
</script>
|
|
30
|
-
|
|
31
|
-
<template>
|
|
32
|
-
<v-list-item
|
|
33
|
-
v-for="link in resolvedLinks"
|
|
34
|
-
:key="link.id"
|
|
35
|
-
:title="link.label"
|
|
36
|
-
:to="link.to"
|
|
37
|
-
:prepend-icon="link.icon"
|
|
38
|
-
/>
|
|
39
|
-
</template>
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { mdiAccountGroupOutline } from "@mdi/js";
|
|
3
|
-
import UsersWorkspacePermissionMenuItem from "./UsersWorkspacePermissionMenuItem.vue";
|
|
4
|
-
|
|
5
|
-
const props = defineProps({
|
|
6
|
-
label: {
|
|
7
|
-
type: String,
|
|
8
|
-
default: "Members"
|
|
9
|
-
},
|
|
10
|
-
to: {
|
|
11
|
-
type: String,
|
|
12
|
-
default: ""
|
|
13
|
-
},
|
|
14
|
-
icon: {
|
|
15
|
-
type: String,
|
|
16
|
-
default: mdiAccountGroupOutline
|
|
17
|
-
},
|
|
18
|
-
surface: {
|
|
19
|
-
type: String,
|
|
20
|
-
default: "*"
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const MEMBERS_MENU_PERMISSIONS = Object.freeze(["workspace.members.view", "workspace.members.manage"]);
|
|
25
|
-
</script>
|
|
26
|
-
|
|
27
|
-
<template>
|
|
28
|
-
<UsersWorkspacePermissionMenuItem
|
|
29
|
-
:label="props.label"
|
|
30
|
-
:to="props.to"
|
|
31
|
-
:icon="props.icon"
|
|
32
|
-
:surface="props.surface"
|
|
33
|
-
path="/members"
|
|
34
|
-
:permissions="MEMBERS_MENU_PERMISSIONS"
|
|
35
|
-
/>
|
|
36
|
-
</template>
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { computed } from "vue";
|
|
3
|
-
import { useSurfaceRouteContext } from "../composables/useSurfaceRouteContext.js";
|
|
4
|
-
import { hasPermission, normalizePermissionList } from "../lib/permissions.js";
|
|
5
|
-
import { usePaths } from "../composables/usePaths.js";
|
|
6
|
-
|
|
7
|
-
const props = defineProps({
|
|
8
|
-
label: {
|
|
9
|
-
type: String,
|
|
10
|
-
default: ""
|
|
11
|
-
},
|
|
12
|
-
to: {
|
|
13
|
-
type: String,
|
|
14
|
-
default: ""
|
|
15
|
-
},
|
|
16
|
-
icon: {
|
|
17
|
-
type: String,
|
|
18
|
-
default: ""
|
|
19
|
-
},
|
|
20
|
-
surface: {
|
|
21
|
-
type: String,
|
|
22
|
-
default: "*"
|
|
23
|
-
},
|
|
24
|
-
path: {
|
|
25
|
-
type: String,
|
|
26
|
-
default: "/"
|
|
27
|
-
},
|
|
28
|
-
permissions: {
|
|
29
|
-
type: [Array, String],
|
|
30
|
-
default: () => []
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const { placementContext, currentSurfaceId } = useSurfaceRouteContext();
|
|
35
|
-
const paths = usePaths();
|
|
36
|
-
|
|
37
|
-
function normalizeRequiredPermissions(value) {
|
|
38
|
-
if (Array.isArray(value)) {
|
|
39
|
-
return value
|
|
40
|
-
.map((entry) => String(entry || "").trim())
|
|
41
|
-
.filter(Boolean);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const normalized = String(value || "").trim();
|
|
45
|
-
if (!normalized) {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return [normalized];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const requiredPermissions = computed(() => normalizeRequiredPermissions(props.permissions));
|
|
53
|
-
|
|
54
|
-
const canView = computed(() => {
|
|
55
|
-
if (requiredPermissions.value.length < 1) {
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const permissions = normalizePermissionList(placementContext.value?.permissions);
|
|
60
|
-
return requiredPermissions.value.some((permission) => hasPermission(permissions, permission));
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const resolvedTo = computed(() => {
|
|
64
|
-
const explicitTo = String(props.to || "").trim();
|
|
65
|
-
if (explicitTo) {
|
|
66
|
-
return explicitTo;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const explicitSurface = String(props.surface || "").trim().toLowerCase();
|
|
70
|
-
const targetSurfaceId =
|
|
71
|
-
explicitSurface && explicitSurface !== "*"
|
|
72
|
-
? explicitSurface
|
|
73
|
-
: String(currentSurfaceId.value || "").trim().toLowerCase();
|
|
74
|
-
const targetPath = String(props.path || "/").trim() || "/";
|
|
75
|
-
|
|
76
|
-
return paths.page(targetPath, {
|
|
77
|
-
surface: targetSurfaceId,
|
|
78
|
-
mode: "auto"
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
</script>
|
|
82
|
-
|
|
83
|
-
<template>
|
|
84
|
-
<v-list-item
|
|
85
|
-
v-if="canView && resolvedTo"
|
|
86
|
-
:title="props.label"
|
|
87
|
-
:to="resolvedTo"
|
|
88
|
-
:prepend-icon="props.icon || undefined"
|
|
89
|
-
/>
|
|
90
|
-
</template>
|