@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
@@ -1,509 +0,0 @@
1
- <script setup>
2
- import { computed, reactive, ref, watch } from "vue";
3
- import { useRoute, useRouter } from "vue-router";
4
- import {
5
- useWebPlacementContext,
6
- resolveSurfaceNavigationTargetFromPlacementContext
7
- } from "@jskit-ai/shell-web/client/placement";
8
- import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
9
- import { normalizeWorkspaceList } from "../lib/bootstrap.js";
10
- import { useCommand } from "../composables/useCommand.js";
11
- import { useView } from "../composables/records/useView.js";
12
- import { usePaths } from "../composables/usePaths.js";
13
- import { useRealtimeQueryInvalidation } from "../composables/useRealtimeQueryInvalidation.js";
14
- import { useWorkspaceSurfaceId } from "../composables/useWorkspaceSurfaceId.js";
15
- import { USERS_ROUTE_VISIBILITY_PUBLIC } from "@jskit-ai/users-core/shared/support/usersVisibility";
16
- import { normalizePendingInvite } from "../composables/account-settings/accountSettingsRuntimeHelpers.js";
17
-
18
- const route = useRoute();
19
- const router = useRouter();
20
- const { context: placementContext } = useWebPlacementContext();
21
- const paths = usePaths();
22
- const errorRuntime = useShellWebErrorRuntime();
23
-
24
- const selectingWorkspaceSlug = ref("");
25
- const bootstrapModel = reactive({
26
- sessionAuthenticated: false,
27
- tenancyMode: "none",
28
- workspaceAllowSelfCreate: false,
29
- workspaceInvitesEnabled: false,
30
- workspaces: [],
31
- pendingInvites: []
32
- });
33
- const inviteAction = ref({
34
- token: "",
35
- decision: ""
36
- });
37
- const createWorkspaceModel = reactive({
38
- name: "",
39
- slug: ""
40
- });
41
- const redeemInviteModel = reactive({
42
- token: "",
43
- decision: ""
44
- });
45
- const bootstrapQueryKey = Object.freeze(["users-web", "bootstrap", "__none__"]);
46
- const OWNERSHIP_PUBLIC = USERS_ROUTE_VISIBILITY_PUBLIC;
47
-
48
- const bootstrapView = useView({
49
- ownershipFilter: OWNERSHIP_PUBLIC,
50
- apiSuffix: "/bootstrap",
51
- queryKeyFactory: () => bootstrapQueryKey,
52
- realtime: {
53
- event: "workspace.settings.changed"
54
- },
55
- fallbackLoadError: "Unable to load workspaces.",
56
- model: bootstrapModel,
57
- mapLoadedToModel: (model, payload = {}) => {
58
- model.sessionAuthenticated = Boolean(payload?.session?.authenticated);
59
- model.tenancyMode = String(payload?.tenancy?.mode || "").trim().toLowerCase() || "none";
60
- model.workspaceAllowSelfCreate = payload?.tenancy?.workspace?.allowSelfCreate === true;
61
- model.workspaceInvitesEnabled = payload?.app?.features?.workspaceInvites === true;
62
- model.workspaces = normalizeWorkspaceList(payload?.workspaces);
63
- model.pendingInvites = model.workspaceInvitesEnabled
64
- ? (Array.isArray(payload?.pendingInvites) ? payload.pendingInvites : [])
65
- .map(normalizePendingInvite)
66
- .filter(Boolean)
67
- : [];
68
- }
69
- });
70
-
71
- const redeemInviteCommand = useCommand({
72
- ownershipFilter: OWNERSHIP_PUBLIC,
73
- apiSuffix: "/workspace/invitations/redeem",
74
- writeMethod: "POST",
75
- fallbackRunError: "Unable to respond to invitation.",
76
- suppressSuccessMessage: true,
77
- model: redeemInviteModel,
78
- buildRawPayload: (model) => ({
79
- token: String(model.token || "").trim(),
80
- decision: String(model.decision || "").trim().toLowerCase()
81
- }),
82
- messages: {
83
- error: "Unable to respond to invitation."
84
- }
85
- });
86
-
87
- const createWorkspaceCommand = useCommand({
88
- ownershipFilter: OWNERSHIP_PUBLIC,
89
- apiSuffix: "/workspaces",
90
- writeMethod: "POST",
91
- fallbackRunError: "Unable to create workspace.",
92
- suppressSuccessMessage: true,
93
- model: createWorkspaceModel,
94
- buildRawPayload: (model) => {
95
- const payload = {
96
- name: String(model.name || "").trim()
97
- };
98
- const slug = String(model.slug || "").trim().toLowerCase();
99
- if (slug) {
100
- payload.slug = slug;
101
- }
102
- return payload;
103
- },
104
- messages: {
105
- error: "Unable to create workspace."
106
- }
107
- });
108
-
109
- useRealtimeQueryInvalidation({
110
- event: "workspaces.changed",
111
- queryKey: bootstrapQueryKey
112
- });
113
-
114
- useRealtimeQueryInvalidation({
115
- event: "workspace.invitations.pending.changed",
116
- queryKey: bootstrapQueryKey
117
- });
118
-
119
- const workspaceItems = computed(() => {
120
- return Array.isArray(bootstrapModel.workspaces) ? bootstrapModel.workspaces : [];
121
- });
122
-
123
- const pendingInvites = computed(() => {
124
- return Array.isArray(bootstrapModel.pendingInvites) ? bootstrapModel.pendingInvites : [];
125
- });
126
- const workspaceInvitesEnabled = computed(() => bootstrapModel.workspaceInvitesEnabled === true);
127
-
128
- const isBootstrapping = computed(() => Boolean(bootstrapView.isLoading));
129
- const isRefreshingBootstrap = computed(() => Boolean(bootstrapView.isRefetching));
130
- const canCreateWorkspace = computed(() => bootstrapModel.workspaceAllowSelfCreate === true);
131
- const isCreatingWorkspace = computed(() => Boolean(createWorkspaceCommand.isRunning.value));
132
-
133
- function reportFeedback({
134
- message,
135
- severity = "error",
136
- channel = "banner",
137
- dedupeKey = ""
138
- } = {}) {
139
- const normalizedMessage = String(message || "").trim();
140
- if (!normalizedMessage) {
141
- return;
142
- }
143
-
144
- errorRuntime.report({
145
- source: "users-web.workspaces-view",
146
- message: normalizedMessage,
147
- severity,
148
- channel,
149
- dedupeKey: dedupeKey || `users-web.workspaces-view:${severity}:${normalizedMessage}`,
150
- dedupeWindowMs: 3000
151
- });
152
- }
153
-
154
- const { workspaceSurfaceId } = useWorkspaceSurfaceId({
155
- route,
156
- placementContext
157
- });
158
-
159
- function workspaceInitials(workspace) {
160
- const source = String(workspace?.name || workspace?.slug || "W").trim();
161
- return source.slice(0, 2).toUpperCase();
162
- }
163
-
164
- function workspaceAvatarStyle(workspace) {
165
- const color = String(workspace?.color || "").trim();
166
- if (!/^#[0-9a-fA-F]{6}$/.test(color)) {
167
- return {};
168
- }
169
-
170
- return {
171
- backgroundColor: color
172
- };
173
- }
174
-
175
- function workspaceHomePath(workspaceSlug) {
176
- const normalizedSlug = String(workspaceSlug || "").trim();
177
- if (!normalizedSlug || !workspaceSurfaceId.value) {
178
- return "";
179
- }
180
-
181
- return paths.page("/", {
182
- surface: workspaceSurfaceId.value,
183
- workspaceSlug: normalizedSlug,
184
- mode: "workspace"
185
- });
186
- }
187
-
188
- async function openWorkspace(workspaceSlug) {
189
- const normalizedSlug = String(workspaceSlug || "").trim();
190
- if (!normalizedSlug) {
191
- return;
192
- }
193
-
194
- const targetPath = workspaceHomePath(normalizedSlug);
195
- if (!targetPath) {
196
- reportFeedback({
197
- message: "Workspace surface is not configured.",
198
- severity: "error",
199
- channel: "banner",
200
- dedupeKey: "users-web.workspaces-view:workspace-surface-missing"
201
- });
202
- return;
203
- }
204
-
205
- selectingWorkspaceSlug.value = normalizedSlug;
206
-
207
- try {
208
- const navigationTarget = resolveSurfaceNavigationTargetFromPlacementContext(placementContext.value, {
209
- path: targetPath,
210
- surfaceId: workspaceSurfaceId.value
211
- });
212
- if (navigationTarget.sameOrigin && router && typeof router.push === "function") {
213
- await router.push(navigationTarget.href);
214
- } else if (typeof window === "object" && window?.location && typeof window.location.assign === "function") {
215
- window.location.assign(navigationTarget.href);
216
- return;
217
- } else {
218
- throw new Error("Workspace navigation is unavailable.");
219
- }
220
- } catch (error) {
221
- reportFeedback({
222
- message: String(error?.message || "Unable to open workspace."),
223
- severity: "error",
224
- channel: "banner",
225
- dedupeKey: `users-web.workspaces-view:open-workspace:${normalizedSlug}`
226
- });
227
- } finally {
228
- selectingWorkspaceSlug.value = "";
229
- }
230
- }
231
-
232
- async function respondToInvite(invite, decision) {
233
- if (!workspaceInvitesEnabled.value) {
234
- return;
235
- }
236
-
237
- const token = String(invite?.token || "").trim();
238
- const normalizedDecision = String(decision || "").trim().toLowerCase();
239
- if (!token || (normalizedDecision !== "accept" && normalizedDecision !== "refuse")) {
240
- return;
241
- }
242
-
243
- inviteAction.value = {
244
- token,
245
- decision: normalizedDecision
246
- };
247
- redeemInviteModel.token = token;
248
- redeemInviteModel.decision = normalizedDecision;
249
-
250
- try {
251
- await redeemInviteCommand.run();
252
-
253
- bootstrapModel.pendingInvites = pendingInvites.value.filter((entry) => entry.token !== token);
254
- await bootstrapView.refresh();
255
-
256
- if (normalizedDecision === "accept") {
257
- const nextWorkspaceSlug = String(invite?.workspaceSlug || "").trim();
258
- if (nextWorkspaceSlug) {
259
- await openWorkspace(nextWorkspaceSlug);
260
- }
261
- return;
262
- }
263
-
264
- reportFeedback({
265
- message: "Invitation refused.",
266
- severity: "success",
267
- channel: "snackbar",
268
- dedupeKey: `users-web.workspaces-view:invite-refused:${token}`
269
- });
270
- } catch (error) {
271
- reportFeedback({
272
- message: String(
273
- error?.message || (normalizedDecision === "accept" ? "Unable to accept invite." : "Unable to refuse invite.")
274
- ),
275
- severity: "error",
276
- channel: "banner",
277
- dedupeKey: `users-web.workspaces-view:invite-${normalizedDecision}:${token}`
278
- });
279
- } finally {
280
- inviteAction.value = {
281
- token: "",
282
- decision: ""
283
- };
284
- redeemInviteModel.token = "";
285
- redeemInviteModel.decision = "";
286
- }
287
- }
288
-
289
- function acceptInvite(invite) {
290
- return respondToInvite(invite, "accept");
291
- }
292
-
293
- function refuseInvite(invite) {
294
- return respondToInvite(invite, "refuse");
295
- }
296
-
297
- async function createWorkspace() {
298
- if (!canCreateWorkspace.value) {
299
- return;
300
- }
301
-
302
- const name = String(createWorkspaceModel.name || "").trim();
303
- if (!name) {
304
- reportFeedback({
305
- message: "Workspace name is required.",
306
- severity: "error",
307
- channel: "banner",
308
- dedupeKey: "users-web.workspaces-view:create-workspace-name-required"
309
- });
310
- return;
311
- }
312
-
313
- try {
314
- const createdWorkspace = await createWorkspaceCommand.run();
315
- await bootstrapView.refresh();
316
- const createdSlug = String(createdWorkspace?.slug || "").trim();
317
- const autoOpenHandledByWatcher = workspaceItems.value.length === 1 && pendingInvites.value.length < 1;
318
- if (createdSlug && !autoOpenHandledByWatcher) {
319
- await openWorkspace(createdSlug);
320
- }
321
- createWorkspaceModel.name = "";
322
- createWorkspaceModel.slug = "";
323
- } catch (error) {
324
- reportFeedback({
325
- message: String(error?.message || "Unable to create workspace."),
326
- severity: "error",
327
- channel: "banner",
328
- dedupeKey: "users-web.workspaces-view:create-workspace-error"
329
- });
330
- }
331
- }
332
-
333
- watch(
334
- () => bootstrapView.resource.data.value,
335
- async (payload) => {
336
- if (!payload) {
337
- return;
338
- }
339
-
340
- if (!bootstrapModel.sessionAuthenticated) {
341
- const loginTarget = resolveSurfaceNavigationTargetFromPlacementContext(placementContext.value, {
342
- path: "/auth/login",
343
- surfaceId: "auth"
344
- });
345
- if (loginTarget.sameOrigin && router && typeof router.replace === "function") {
346
- await router.replace(loginTarget.href);
347
- } else if (typeof window === "object" && window?.location && typeof window.location.assign === "function") {
348
- window.location.assign(loginTarget.href);
349
- }
350
- return;
351
- }
352
-
353
- if (workspaceItems.value.length === 1 && pendingInvites.value.length < 1) {
354
- await openWorkspace(workspaceItems.value[0].slug);
355
- }
356
- },
357
- {
358
- immediate: true
359
- }
360
- );
361
-
362
- </script>
363
-
364
- <template>
365
- <section class="workspaces-view py-6">
366
- <v-container class="mx-auto" max-width="860">
367
- <v-card rounded="lg" border elevation="1">
368
- <v-card-item>
369
- <v-card-title class="text-h6">You are logged in</v-card-title>
370
- <v-card-subtitle>Select a workspace or respond to invitations.</v-card-subtitle>
371
- </v-card-item>
372
- <v-divider />
373
-
374
- <v-card-text class="pt-4">
375
- <v-progress-linear v-if="!isBootstrapping && isRefreshingBootstrap" indeterminate class="mb-4" />
376
- <v-row>
377
- <v-col cols="12" :md="workspaceInvitesEnabled ? 6 : 12">
378
- <template v-if="isBootstrapping">
379
- <v-skeleton-loader type="text, list-item-avatar-two-line@3" />
380
- </template>
381
- <template v-else>
382
- <div class="text-subtitle-2 mb-2">Your workspaces</div>
383
- <template v-if="workspaceItems.length === 0">
384
- <p class="text-body-1 mb-2">You do not have a workspace yet.</p>
385
- <template v-if="canCreateWorkspace">
386
- <v-text-field
387
- v-model="createWorkspaceModel.name"
388
- density="comfortable"
389
- label="Workspace name"
390
- variant="outlined"
391
- hide-details
392
- class="mb-2"
393
- />
394
- <v-text-field
395
- v-model="createWorkspaceModel.slug"
396
- density="comfortable"
397
- label="Slug (optional)"
398
- variant="outlined"
399
- hide-details
400
- class="mb-3"
401
- />
402
- <v-btn
403
- color="primary"
404
- variant="tonal"
405
- :loading="isCreatingWorkspace"
406
- @click="createWorkspace"
407
- >
408
- Create Workspace
409
- </v-btn>
410
- </template>
411
- <p v-else class="text-body-2 text-medium-emphasis mb-0">
412
- Ask an administrator for an invite, or create one after policy is enabled.
413
- </p>
414
- </template>
415
-
416
- <template v-else>
417
- <v-list density="comfortable" class="pa-0">
418
- <v-list-item
419
- v-for="workspace in workspaceItems"
420
- :key="workspace.id"
421
- :title="workspace.name"
422
- :subtitle="
423
- workspace.isAccessible
424
- ? `/${workspace.slug} • role: ${workspace.roleSid || 'member'}`
425
- : `/${workspace.slug} • unavailable on this surface`
426
- "
427
- class="px-0"
428
- >
429
- <template #prepend>
430
- <v-avatar :style="workspaceAvatarStyle(workspace)" size="28">
431
- <v-img v-if="workspace.avatarUrl" :src="workspace.avatarUrl" cover />
432
- <span v-else class="text-caption">{{ workspaceInitials(workspace) }}</span>
433
- </v-avatar>
434
- </template>
435
- <template #append>
436
- <v-btn
437
- color="primary"
438
- size="small"
439
- variant="tonal"
440
- :disabled="!workspace.isAccessible"
441
- :loading="selectingWorkspaceSlug === workspace.slug"
442
- @click="openWorkspace(workspace.slug)"
443
- >
444
- {{ workspace.isAccessible ? "Open" : "Unavailable" }}
445
- </v-btn>
446
- </template>
447
- </v-list-item>
448
- </v-list>
449
- </template>
450
- </template>
451
- </v-col>
452
-
453
- <v-col v-if="workspaceInvitesEnabled" cols="12" md="6">
454
- <template v-if="isBootstrapping">
455
- <v-skeleton-loader type="text, list-item-two-line@3" />
456
- </template>
457
- <template v-else>
458
- <div class="text-subtitle-2 mb-2">Invitations</div>
459
- <template v-if="pendingInvites.length === 0">
460
- <p class="text-body-2 text-medium-emphasis mb-0">No pending invitations.</p>
461
- </template>
462
-
463
- <template v-else>
464
- <v-list density="comfortable" class="pa-0">
465
- <v-list-item
466
- v-for="invite in pendingInvites"
467
- :key="invite.id"
468
- :title="invite.workspaceName"
469
- :subtitle="`Role: ${invite.roleSid}`"
470
- class="px-0"
471
- >
472
- <template #prepend>
473
- <v-avatar color="warning" size="28">
474
- <span class="text-caption font-weight-bold">?</span>
475
- </v-avatar>
476
- </template>
477
- <template #append>
478
- <div class="d-flex ga-2">
479
- <v-btn
480
- size="small"
481
- variant="text"
482
- color="error"
483
- :loading="inviteAction.token === invite.token && inviteAction.decision === 'refuse'"
484
- @click="refuseInvite(invite)"
485
- >
486
- Refuse
487
- </v-btn>
488
- <v-btn
489
- size="small"
490
- variant="tonal"
491
- color="primary"
492
- :loading="inviteAction.token === invite.token && inviteAction.decision === 'accept'"
493
- @click="acceptInvite(invite)"
494
- >
495
- Join
496
- </v-btn>
497
- </div>
498
- </template>
499
- </v-list-item>
500
- </v-list>
501
- </template>
502
- </template>
503
- </v-col>
504
- </v-row>
505
- </v-card-text>
506
- </v-card>
507
- </v-container>
508
- </section>
509
- </template>
@@ -1,88 +0,0 @@
1
- import { resolveErrorStatusCode } from "../../support/runtimeNormalization.js";
2
-
3
- function createAccountSettingsInvitesRuntime({
4
- invitesAvailable,
5
- isResolvingInvite,
6
- inviteAction,
7
- redeemInviteModel,
8
- redeemInviteCommand,
9
- pendingInvites,
10
- pendingInvitesModel,
11
- pendingInvitesView,
12
- openWorkspace,
13
- reportAccountFeedback
14
- } = {}) {
15
- async function respondToInvite(invite, decision) {
16
- if (!invitesAvailable.value) {
17
- return;
18
- }
19
-
20
- const token = String(invite?.token || "").trim();
21
- const normalizedDecision = String(decision || "").trim().toLowerCase();
22
- if (!token || (normalizedDecision !== "accept" && normalizedDecision !== "refuse")) {
23
- return;
24
- }
25
- if (isResolvingInvite.value) {
26
- return;
27
- }
28
-
29
- inviteAction.value = {
30
- token,
31
- decision: normalizedDecision
32
- };
33
- redeemInviteModel.token = token;
34
- redeemInviteModel.decision = normalizedDecision;
35
-
36
- try {
37
- await redeemInviteCommand.run();
38
- pendingInvitesModel.pendingInvites = pendingInvites.value.filter((entry) => entry.token !== token);
39
- await pendingInvitesView.refresh();
40
-
41
- if (normalizedDecision === "accept") {
42
- const nextWorkspaceSlug = String(invite?.workspaceSlug || "").trim();
43
- if (nextWorkspaceSlug) {
44
- await openWorkspace(nextWorkspaceSlug);
45
- return;
46
- }
47
- }
48
-
49
- reportAccountFeedback({
50
- message: normalizedDecision === "accept" ? "Invitation accepted." : "Invitation refused.",
51
- severity: "success",
52
- channel: "snackbar",
53
- dedupeKey: `users-web.account-settings-runtime:invite-${normalizedDecision}:${token}`
54
- });
55
- } catch (error) {
56
- const statusCode = resolveErrorStatusCode(error);
57
- const fallbackMessage = normalizedDecision === "accept"
58
- ? "Unable to accept invitation."
59
- : "Unable to refuse invitation.";
60
- reportAccountFeedback({
61
- message: statusCode === 404
62
- ? "Invitation no longer exists."
63
- : String(error?.message || fallbackMessage),
64
- severity: "error",
65
- channel: "banner",
66
- dedupeKey: `users-web.account-settings-runtime:invite-${normalizedDecision}-error:${token}`
67
- });
68
- } finally {
69
- inviteAction.value = {
70
- token: "",
71
- decision: ""
72
- };
73
- redeemInviteModel.token = "";
74
- redeemInviteModel.decision = "";
75
- }
76
- }
77
-
78
- return Object.freeze({
79
- accept(invite) {
80
- return respondToInvite(invite, "accept");
81
- },
82
- refuse(invite) {
83
- return respondToInvite(invite, "refuse");
84
- }
85
- });
86
- }
87
-
88
- export { createAccountSettingsInvitesRuntime };
@@ -1,52 +0,0 @@
1
- import {
2
- computed
3
- } from "vue";
4
- import { useQuery } from "@tanstack/vue-query";
5
- import { normalizeQueryToken } from "@jskit-ai/kernel/shared/support/normalize";
6
- import { usersWebHttpClient } from "../lib/httpClient.js";
7
- import { buildBootstrapApiPath } from "../lib/bootstrap.js";
8
- import { resolveEnabledRef, resolveTextRef } from "./support/refValueHelpers.js";
9
-
10
- const DEFAULT_BOOTSTRAP_STALE_TIME_MS = 60_000;
11
-
12
- function normalizeStaleTime(value, fallback = DEFAULT_BOOTSTRAP_STALE_TIME_MS) {
13
- const parsed = Number(value);
14
- if (!Number.isFinite(parsed) || parsed < 0) {
15
- return fallback;
16
- }
17
- return parsed;
18
- }
19
-
20
- function useBootstrapQuery({
21
- workspaceSlug = "",
22
- enabled = true,
23
- staleTime = DEFAULT_BOOTSTRAP_STALE_TIME_MS,
24
- refetchOnMount = false,
25
- refetchOnWindowFocus = false
26
- } = {}) {
27
- const normalizedWorkspaceSlug = computed(() => resolveTextRef(workspaceSlug));
28
- const queryKey = computed(() => ["users-web", "bootstrap", normalizeQueryToken(normalizedWorkspaceSlug.value)]);
29
- const bootstrapPath = computed(() => buildBootstrapApiPath(normalizedWorkspaceSlug.value));
30
- const queryEnabled = computed(() => resolveEnabledRef(enabled));
31
- const queryStaleTime = normalizeStaleTime(staleTime, DEFAULT_BOOTSTRAP_STALE_TIME_MS);
32
-
33
- const query = useQuery({
34
- queryKey,
35
- queryFn: () =>
36
- usersWebHttpClient.request(bootstrapPath.value, {
37
- method: "GET"
38
- }),
39
- enabled: queryEnabled,
40
- staleTime: queryStaleTime,
41
- refetchOnMount,
42
- refetchOnWindowFocus
43
- });
44
-
45
- return Object.freeze({
46
- query,
47
- queryKey,
48
- workspaceSlug: normalizedWorkspaceSlug
49
- });
50
- }
51
-
52
- export { useBootstrapQuery };
@@ -1,28 +0,0 @@
1
- import { computed } from "vue";
2
- import {
3
- extractWorkspaceSlugFromSurfacePathname
4
- } from "../lib/workspaceSurfacePaths.js";
5
- import { useSurfaceRouteContext } from "./useSurfaceRouteContext.js";
6
-
7
- function useWorkspaceRouteContext() {
8
- const { route, routePath, placementContext, mergePlacementContext, currentSurfaceId } = useSurfaceRouteContext();
9
- const workspaceSlugFromRoute = computed(() => {
10
- const workspaceSlug = extractWorkspaceSlugFromSurfacePathname(
11
- placementContext.value,
12
- currentSurfaceId.value,
13
- routePath.value
14
- );
15
- return String(workspaceSlug || "").trim();
16
- });
17
-
18
- return Object.freeze({
19
- route,
20
- routePath,
21
- placementContext,
22
- mergePlacementContext,
23
- currentSurfaceId,
24
- workspaceSlugFromRoute
25
- });
26
- }
27
-
28
- export { useWorkspaceRouteContext };