@jskit-ai/users-web 0.1.52 → 0.1.54

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.
Files changed (73) hide show
  1. package/package.descriptor.mjs +14 -96
  2. package/package.json +16 -11
  3. package/src/client/account-settings/sections.js +74 -0
  4. package/src/client/composables/account-settings/accountSettingsRuntimeHelpers.js +2 -38
  5. package/src/client/composables/crud/crudLookupFieldRuntime.js +2 -2
  6. package/src/client/composables/internal/crudListParentTitleSupport.js +1 -1
  7. package/src/client/composables/internal/useOperationScope.js +12 -12
  8. package/src/client/composables/records/useAddEdit.js +2 -2
  9. package/src/client/composables/records/useList.js +3 -3
  10. package/src/client/composables/records/useView.js +2 -2
  11. package/src/client/composables/support/scopeHelpers.js +19 -19
  12. package/src/client/composables/useAccess.js +3 -3
  13. package/src/client/composables/useAccountSettingsRuntime.js +8 -156
  14. package/src/client/composables/useCommand.js +2 -2
  15. package/src/client/composables/useCrudListParentTitle.js +2 -2
  16. package/src/client/composables/usePaths.js +50 -38
  17. package/src/client/composables/useScopeRuntime.js +55 -27
  18. package/src/client/composables/useSurfaceRouteContext.js +1 -7
  19. package/src/client/index.js +0 -1
  20. package/src/client/lib/bootstrap.js +0 -63
  21. package/src/client/lib/httpClient.js +2 -59
  22. package/src/client/lib/theme.js +12 -189
  23. package/src/client/providers/UsersWebClientProvider.js +2 -25
  24. package/src/client/providers/bootUsersWebClientProvider.js +28 -0
  25. package/src/shared/toolsOutletContracts.js +1 -8
  26. package/templates/src/components/account/settings/AccountSettingsClientElement.vue +33 -21
  27. package/test/accountSettingsSections.test.js +79 -0
  28. package/test/exportsContract.test.js +2 -2
  29. package/test/scopeHelpers.test.js +6 -6
  30. package/test/settingsPlacementContract.test.js +4 -49
  31. package/test/theme.test.js +0 -56
  32. package/src/client/components/ConsoleSettingsClientElement.vue +0 -24
  33. package/src/client/components/MembersAdminClientElement.vue +0 -400
  34. package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +0 -39
  35. package/src/client/components/UsersWorkspaceMembersMenuItem.vue +0 -36
  36. package/src/client/components/UsersWorkspacePermissionMenuItem.vue +0 -90
  37. package/src/client/components/UsersWorkspaceSelector.vue +0 -248
  38. package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +0 -39
  39. package/src/client/components/UsersWorkspaceToolsWidget.vue +0 -12
  40. package/src/client/components/WorkspaceMembersClientElement.vue +0 -655
  41. package/src/client/components/WorkspaceProfileClientElement.vue +0 -116
  42. package/src/client/components/WorkspaceSettingsClientElement.vue +0 -102
  43. package/src/client/components/WorkspaceSettingsFieldsClientElement.vue +0 -265
  44. package/src/client/components/WorkspacesClientElement.vue +0 -509
  45. package/src/client/composables/account-settings/accountSettingsInvitesRuntime.js +0 -88
  46. package/src/client/composables/useBootstrapQuery.js +0 -52
  47. package/src/client/composables/useWorkspaceRouteContext.js +0 -28
  48. package/src/client/composables/useWorkspaceSurfaceId.js +0 -43
  49. package/src/client/lib/menuIcons.js +0 -210
  50. package/src/client/lib/profileSurfaceMenuLinks.js +0 -142
  51. package/src/client/lib/surfaceAccessPolicy.js +0 -350
  52. package/src/client/lib/workspaceLinkResolver.js +0 -207
  53. package/src/client/lib/workspaceSurfaceContext.js +0 -82
  54. package/src/client/lib/workspaceSurfacePaths.js +0 -163
  55. package/src/client/providers/UsersWorkspacesClientProvider.js +0 -24
  56. package/src/client/runtime/bootstrapPlacementRouteGuards.js +0 -371
  57. package/src/client/runtime/bootstrapPlacementRuntime.js +0 -463
  58. package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +0 -28
  59. package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +0 -147
  60. package/src/client/support/menuLinkTarget.js +0 -93
  61. package/src/client/support/realtimeWorkspace.js +0 -21
  62. package/src/client/support/runtimeNormalization.js +0 -27
  63. package/src/client/support/workspaceQueryKeys.js +0 -15
  64. package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +0 -77
  65. package/templates/src/pages/console/settings/index.vue +0 -8
  66. package/templates/src/pages/console/settings.vue +0 -32
  67. package/test/bootstrapPlacementRuntime.test.js +0 -1095
  68. package/test/menuIcons.test.js +0 -35
  69. package/test/menuLinkTarget.test.js +0 -116
  70. package/test/profileSurfaceMenuLinks.test.js +0 -207
  71. package/test/surfaceAccessPolicy.test.js +0 -129
  72. package/test/workspaceLinkResolver.test.js +0 -61
  73. package/test/workspaceSurfacePaths.test.js +0 -39
@@ -71,44 +71,6 @@ function expectTextMutation(id, { reason = "", category = "", skipIfContains = "
71
71
  }
72
72
  }
73
73
 
74
- test("users-web console settings template exposes surface-derived settings outlets", async () => {
75
- const source = await readFile(path.join(PACKAGE_DIR, "templates", "src", "pages", "console", "settings.vue"), "utf8");
76
-
77
- assert.match(source, /target="console-settings:primary-menu"/);
78
- assert.match(source, /default-link-component-token="local\.main\.ui\.surface-aware-menu-link-item"/);
79
- assert.match(source, /<RouterView \/>/);
80
- });
81
-
82
- test("users-web console settings index template is a simple developer-owned stub", async () => {
83
- const source = await readFile(path.join(PACKAGE_DIR, "templates", "src", "pages", "console", "settings", "index.vue"), "utf8");
84
-
85
- assert.match(source, /definePage/);
86
- assert.match(source, /your_child_segment/);
87
- });
88
-
89
- test("users-web descriptor metadata advertises console settings outlets with standard positions", () => {
90
- const outlets = readOutlets("console-settings:primary-menu");
91
- assert.deepEqual(
92
- outlets,
93
- [
94
- {
95
- target: "console-settings:primary-menu",
96
- defaultLinkComponentToken: "local.main.ui.surface-aware-menu-link-item",
97
- surfaces: ["console"],
98
- source: "templates/src/pages/console/settings.vue"
99
- }
100
- ]
101
- );
102
- assert.deepEqual(findFileMutation("users-web-page-console-settings"), {
103
- from: "templates/src/pages/console/settings/index.vue",
104
- toSurface: "console",
105
- toSurfacePath: "settings/index.vue",
106
- reason: "Install console settings index stub scaffold for app-owned landing or redirect behavior.",
107
- category: "users-web",
108
- id: "users-web-page-console-settings"
109
- });
110
- });
111
-
112
74
  test("users-web home tools widget exposes home-tools outlet", async () => {
113
75
  const source = await readFile(path.join(PACKAGE_DIR, "src", "client", "components", "UsersHomeToolsWidget.vue"), "utf8");
114
76
 
@@ -118,15 +80,6 @@ test("users-web home tools widget exposes home-tools outlet", async () => {
118
80
  assert.match(source, /:default-link-component-token="HOME_TOOLS_OUTLET\.defaultLinkComponentToken"/);
119
81
  });
120
82
 
121
- test("users-web workspace tools widget exposes workspace-tools outlet", async () => {
122
- const source = await readFile(path.join(PACKAGE_DIR, "src", "client", "components", "UsersWorkspaceToolsWidget.vue"), "utf8");
123
-
124
- assert.match(source, /import \{ WORKSPACE_TOOLS_OUTLET \} from "\.\.\/\.\.\/shared\/toolsOutletContracts\.js";/);
125
- assert.match(source, /<ShellOutletMenuWidget/);
126
- assert.match(source, /:target="WORKSPACE_TOOLS_OUTLET\.target"/);
127
- assert.match(source, /:default-link-component-token="WORKSPACE_TOOLS_OUTLET\.defaultLinkComponentToken"/);
128
- });
129
-
130
83
  test("users-web descriptor metadata advertises home tools outlet and standard home settings placements", () => {
131
84
  assert.deepEqual(
132
85
  readOutlets("home-tools:primary-menu"),
@@ -178,8 +131,8 @@ test("users-web descriptor metadata advertises home tools outlet and standard ho
178
131
  'id: "users.home.menu.settings"',
179
132
  'target: "home-tools:primary-menu"',
180
133
  'componentToken: "local.main.ui.surface-aware-menu-link-item"',
181
- 'workspaceSuffix: "/settings"',
182
- 'nonWorkspaceSuffix: "/settings"'
134
+ 'scopedSuffix: "/settings"',
135
+ 'unscopedSuffix: "/settings"'
183
136
  ]
184
137
  });
185
138
 
@@ -196,4 +149,6 @@ test("users-web descriptor metadata advertises home tools outlet and standard ho
196
149
  ]
197
150
  });
198
151
 
152
+ assert.equal(findFileMutation("users-web-component-account-settings-invites"), null);
153
+
199
154
  });
@@ -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,24 +0,0 @@
1
- <template>
2
- <v-container fluid class="console-home pa-0">
3
- <v-row class="ma-0" justify="center">
4
- <v-col cols="12" sm="10" md="9" lg="7">
5
- <v-sheet class="pa-6 pa-sm-8" rounded="lg" border>
6
- <h1 class="text-h5 text-sm-h4 mb-3">Console settings</h1>
7
- <p class="text-body-2 text-sm-body-1 mb-0">
8
- No console settings sections are available yet. Install a module that adds a page and menu entry to
9
- <code>console-settings:primary-menu</code>.
10
- </p>
11
- </v-sheet>
12
- </v-col>
13
- </v-row>
14
- </v-container>
15
- </template>
16
-
17
- <style scoped>
18
- .console-home {
19
- min-height: min(100vh, 700px);
20
- display: grid;
21
- align-content: start;
22
- padding-top: clamp(16px, 4vh, 48px);
23
- }
24
- </style>
@@ -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>