@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.
- package/package.descriptor.mjs +14 -96
- 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 -49
- package/test/theme.test.js +0 -56
- package/src/client/components/ConsoleSettingsClientElement.vue +0 -24
- 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 -210
- 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/templates/src/pages/console/settings/index.vue +0 -8
- package/templates/src/pages/console/settings.vue +0 -32
- package/test/bootstrapPlacementRuntime.test.js +0 -1095
- package/test/menuIcons.test.js +0 -35
- package/test/menuLinkTarget.test.js +0 -116
- package/test/profileSurfaceMenuLinks.test.js +0 -207
- package/test/surfaceAccessPolicy.test.js +0 -129
- package/test/workspaceLinkResolver.test.js +0 -61
- 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
|
-
'
|
|
182
|
-
'
|
|
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
|
});
|
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,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>
|