@jskit-ai/users-web 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/package.descriptor.mjs +507 -0
  2. package/package.json +31 -0
  3. package/src/client/components/ConsoleSettingsClientElement.vue +24 -0
  4. package/src/client/components/MembersAdminClientElement.vue +404 -0
  5. package/src/client/components/ProfileClientElement.vue +242 -0
  6. package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +39 -0
  7. package/src/client/components/UsersShellMenuLinkItem.vue +140 -0
  8. package/src/client/components/UsersSurfaceAwareMenuLinkItem.vue +87 -0
  9. package/src/client/components/UsersWorkspaceMembersMenuItem.vue +36 -0
  10. package/src/client/components/UsersWorkspacePermissionMenuItem.vue +90 -0
  11. package/src/client/components/UsersWorkspaceSelector.vue +237 -0
  12. package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +39 -0
  13. package/src/client/components/UsersWorkspaceToolsWidget.vue +23 -0
  14. package/src/client/components/WorkspaceMembersClientElement.vue +663 -0
  15. package/src/client/components/WorkspaceSettingsClientElement.vue +230 -0
  16. package/src/client/components/WorkspacesClientElement.vue +514 -0
  17. package/src/client/composables/accountSettingsAvatarUploadRuntime.js +241 -0
  18. package/src/client/composables/accountSettingsInvitesRuntime.js +88 -0
  19. package/src/client/composables/accountSettingsRuntimeConstants.js +77 -0
  20. package/src/client/composables/accountSettingsRuntimeHelpers.js +75 -0
  21. package/src/client/composables/errorMessageHelpers.js +66 -0
  22. package/src/client/composables/internal/useOperationScope.js +144 -0
  23. package/src/client/composables/modelStateHelpers.js +49 -0
  24. package/src/client/composables/operationUiHelpers.js +121 -0
  25. package/src/client/composables/operationValidationHelpers.js +52 -0
  26. package/src/client/composables/refValueHelpers.js +19 -0
  27. package/src/client/composables/scopeHelpers.js +145 -0
  28. package/src/client/composables/useAccess.js +109 -0
  29. package/src/client/composables/useAccountSettingsRuntime.js +533 -0
  30. package/src/client/composables/useAddEdit.js +135 -0
  31. package/src/client/composables/useAddEditCore.js +137 -0
  32. package/src/client/composables/useBootstrapQuery.js +52 -0
  33. package/src/client/composables/useCommand.js +112 -0
  34. package/src/client/composables/useCommandCore.js +130 -0
  35. package/src/client/composables/useEndpointResource.js +104 -0
  36. package/src/client/composables/useFieldErrorBag.js +61 -0
  37. package/src/client/composables/useList.js +85 -0
  38. package/src/client/composables/useListCore.js +65 -0
  39. package/src/client/composables/usePagedCollection.js +125 -0
  40. package/src/client/composables/usePaths.js +108 -0
  41. package/src/client/composables/useRealtimeQueryInvalidation.js +105 -0
  42. package/src/client/composables/useScopeRuntime.js +107 -0
  43. package/src/client/composables/useSurfaceRouteContext.js +31 -0
  44. package/src/client/composables/useUiFeedback.js +96 -0
  45. package/src/client/composables/useView.js +89 -0
  46. package/src/client/composables/useViewCore.js +104 -0
  47. package/src/client/composables/useWorkspaceRouteContext.js +28 -0
  48. package/src/client/composables/useWorkspaceSurfaceId.js +43 -0
  49. package/src/client/index.js +7 -0
  50. package/src/client/lib/bootstrap.js +95 -0
  51. package/src/client/lib/httpClient.js +67 -0
  52. package/src/client/lib/menuIcons.js +192 -0
  53. package/src/client/lib/permissions.js +34 -0
  54. package/src/client/lib/profileSurfaceMenuLinks.js +142 -0
  55. package/src/client/lib/surfaceAccessPolicy.js +350 -0
  56. package/src/client/lib/theme.js +99 -0
  57. package/src/client/lib/workspaceLinkResolver.js +207 -0
  58. package/src/client/lib/workspaceSurfaceContext.js +82 -0
  59. package/src/client/lib/workspaceSurfacePaths.js +163 -0
  60. package/src/client/providers/UsersWebClientProvider.js +85 -0
  61. package/src/client/runtime/bootstrapPlacementRouteGuards.js +371 -0
  62. package/src/client/runtime/bootstrapPlacementRuntime.js +413 -0
  63. package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +32 -0
  64. package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +157 -0
  65. package/src/client/support/contractGuards.js +34 -0
  66. package/src/client/support/realtimeWorkspace.js +12 -0
  67. package/src/client/support/runtimeNormalization.js +27 -0
  68. package/src/client/support/workspaceQueryKeys.js +15 -0
  69. package/templates/packages/main/src/client/components/AccountPendingInvitesCue.vue +162 -0
  70. package/templates/src/components/WorkspaceNotFoundCard.vue +33 -0
  71. package/templates/src/components/account/settings/AccountSettingsClientElement.vue +153 -0
  72. package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +77 -0
  73. package/templates/src/components/account/settings/AccountSettingsNotificationsSection.vue +55 -0
  74. package/templates/src/components/account/settings/AccountSettingsPreferencesSection.vue +125 -0
  75. package/templates/src/components/account/settings/AccountSettingsProfileSection.vue +94 -0
  76. package/templates/src/composables/useWorkspaceNotFoundState.js +48 -0
  77. package/templates/src/pages/account/index.vue +17 -0
  78. package/templates/src/pages/admin/members/index.vue +7 -0
  79. package/templates/src/pages/admin/workspace/settings/index.vue +16 -0
  80. package/templates/src/pages/console/settings/index.vue +16 -0
  81. package/templates/src/surfaces/admin/index.vue +29 -0
  82. package/templates/src/surfaces/admin/root.vue +20 -0
  83. package/templates/src/surfaces/app/index.vue +27 -0
  84. package/templates/src/surfaces/app/root.vue +20 -0
  85. package/test/bootstrap.test.js +38 -0
  86. package/test/bootstrapPlacementRuntime.test.js +991 -0
  87. package/test/errorMessageHelpers.test.js +28 -0
  88. package/test/exportsContract.test.js +39 -0
  89. package/test/menuIcons.test.js +33 -0
  90. package/test/permissions.test.js +35 -0
  91. package/test/profileSurfaceMenuLinks.test.js +207 -0
  92. package/test/refValueHelpers.test.js +14 -0
  93. package/test/scopeHelpers.test.js +57 -0
  94. package/test/surfaceAccessPolicy.test.js +129 -0
  95. package/test/theme.test.js +95 -0
  96. package/test/workspaceLinkResolver.test.js +61 -0
  97. package/test/workspaceSurfacePaths.test.js +39 -0
@@ -0,0 +1,230 @@
1
+ <template>
2
+ <section class="workspace-settings-client-element">
3
+ <v-card rounded="lg" elevation="1" border>
4
+ <v-card-item>
5
+ <v-card-title class="text-h6">Workspace settings</v-card-title>
6
+ <v-card-subtitle>These values apply to everyone in this workspace.</v-card-subtitle>
7
+ </v-card-item>
8
+ <v-divider />
9
+ <v-card-text class="pt-4">
10
+ <template v-if="showFormSkeleton">
11
+ <v-skeleton-loader type="text@2, list-item-two-line@4, button" />
12
+ </template>
13
+
14
+ <p v-else-if="addEdit.loadError" class="text-body-2 text-medium-emphasis mb-4">
15
+ {{ addEdit.loadError }}
16
+ </p>
17
+
18
+ <p v-else-if="!addEdit.canView" class="text-body-2 text-medium-emphasis mb-4">
19
+ You do not have permission to view workspace settings.
20
+ </p>
21
+
22
+ <template v-else>
23
+ <v-form @submit.prevent="addEdit.submit" novalidate>
24
+ <v-progress-linear v-if="addEdit.isRefetching" indeterminate class="mb-4" />
25
+ <v-row>
26
+ <v-col cols="12" md="5">
27
+ <v-text-field
28
+ v-model="workspaceForm.name"
29
+ label="Workspace name"
30
+ variant="outlined"
31
+ density="comfortable"
32
+ :readonly="!addEdit.canSave || addEdit.isSaving || addEdit.isRefetching"
33
+ :error-messages="addEdit.fieldErrors.name ? [addEdit.fieldErrors.name] : []"
34
+ />
35
+ </v-col>
36
+
37
+ <v-col cols="12" md="2">
38
+ <v-text-field
39
+ v-model="workspaceForm.color"
40
+ label="Workspace color"
41
+ type="color"
42
+ variant="outlined"
43
+ density="comfortable"
44
+ :readonly="!addEdit.canSave || addEdit.isSaving || addEdit.isRefetching"
45
+ :error-messages="addEdit.fieldErrors.color ? [addEdit.fieldErrors.color] : []"
46
+ />
47
+ </v-col>
48
+
49
+ <v-col cols="12" md="5">
50
+ <v-text-field
51
+ v-model="workspaceForm.avatarUrl"
52
+ label="Workspace avatar URL"
53
+ variant="outlined"
54
+ density="comfortable"
55
+ :readonly="!addEdit.canSave || addEdit.isSaving || addEdit.isRefetching"
56
+ placeholder="https://..."
57
+ hint="Optional"
58
+ persistent-hint
59
+ :error-messages="addEdit.fieldErrors.avatarUrl ? [addEdit.fieldErrors.avatarUrl] : []"
60
+ />
61
+ </v-col>
62
+
63
+ <v-col cols="12" md="6" class="d-flex align-center">
64
+ <v-switch
65
+ v-model="workspaceForm.invitesEnabled"
66
+ color="primary"
67
+ hide-details
68
+ label="Enable invites"
69
+ :disabled="!addEdit.canSave || !workspaceForm.invitesAvailable || addEdit.isSaving || addEdit.isRefetching"
70
+ />
71
+ </v-col>
72
+
73
+ <v-col cols="12" class="d-flex align-center justify-end ga-3">
74
+ <v-btn
75
+ v-if="addEdit.canSave"
76
+ type="submit"
77
+ color="primary"
78
+ :loading="addEdit.isSaving"
79
+ :disabled="addEdit.isInitialLoading || addEdit.isRefetching"
80
+ >
81
+ Save workspace settings
82
+ </v-btn>
83
+ <v-chip v-else color="secondary" label>Read-only</v-chip>
84
+ </v-col>
85
+ </v-row>
86
+ </v-form>
87
+ </template>
88
+ </v-card-text>
89
+ </v-card>
90
+ </section>
91
+ </template>
92
+
93
+ <script setup>
94
+ import { computed, reactive, watch } from "vue";
95
+ import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
96
+ import { workspaceSettingsResource } from "@jskit-ai/users-core/shared/resources/workspaceSettingsResource";
97
+ import { WORKSPACE_SETTINGS_CHANGED_EVENT } from "@jskit-ai/users-core/shared/events/usersEvents";
98
+ import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
99
+ import { useWebPlacementContext } from "@jskit-ai/shell-web/client/placement";
100
+ import { useAddEdit } from "../composables/useAddEdit.js";
101
+ import { useBootstrapQuery } from "../composables/useBootstrapQuery.js";
102
+ import { useWorkspaceRouteContext } from "../composables/useWorkspaceRouteContext.js";
103
+ import { findWorkspaceBySlug, normalizeWorkspaceList } from "../lib/bootstrap.js";
104
+ import { arePermissionListsEqual, normalizePermissionList } from "../lib/permissions.js";
105
+ import { matchesCurrentWorkspaceEvent } from "../support/realtimeWorkspace.js";
106
+ import { buildWorkspaceQueryKey } from "../support/workspaceQueryKeys.js";
107
+
108
+ const DEFAULT_WORKSPACE_COLOR = "#0F6B54";
109
+
110
+ const workspaceForm = reactive({
111
+ name: "",
112
+ color: DEFAULT_WORKSPACE_COLOR,
113
+ avatarUrl: "",
114
+ invitesEnabled: false,
115
+ invitesAvailable: false
116
+ });
117
+ const routeContext = useWorkspaceRouteContext();
118
+ const { context: placementContext, mergeContext: mergePlacementContext } = useWebPlacementContext();
119
+ const OWNERSHIP_WORKSPACE = USERS_ROUTE_VISIBILITY_WORKSPACE;
120
+ const bootstrapQuery = useBootstrapQuery({
121
+ workspaceSlug: routeContext.workspaceSlugFromRoute,
122
+ enabled: computed(() => Boolean(routeContext.workspaceSlugFromRoute.value))
123
+ });
124
+
125
+ function toWorkspaceEntrySnapshot(entry = null) {
126
+ if (!entry || typeof entry !== "object") {
127
+ return "";
128
+ }
129
+
130
+ const normalizedEntry = normalizeWorkspaceList([entry])[0] || null;
131
+ if (!normalizedEntry) {
132
+ return "";
133
+ }
134
+ return JSON.stringify(normalizedEntry);
135
+ }
136
+
137
+ function toWorkspaceListSnapshot(list = []) {
138
+ return JSON.stringify(normalizeWorkspaceList(list));
139
+ }
140
+
141
+ function applyShellWorkspaceContext(payload = {}) {
142
+ const availableWorkspaces = normalizeWorkspaceList(payload?.workspaces);
143
+ const currentWorkspace = findWorkspaceBySlug(
144
+ availableWorkspaces,
145
+ routeContext.workspaceSlugFromRoute.value
146
+ );
147
+ const permissions = normalizePermissionList(payload?.permissions);
148
+ const currentContext =
149
+ placementContext.value && typeof placementContext.value === "object"
150
+ ? placementContext.value
151
+ : {};
152
+ const currentPermissions = normalizePermissionList(currentContext.permissions);
153
+ const samePermissions = arePermissionListsEqual(permissions, currentPermissions);
154
+ const sameWorkspace = toWorkspaceEntrySnapshot(currentContext.workspace) === toWorkspaceEntrySnapshot(currentWorkspace);
155
+ const sameWorkspaces =
156
+ toWorkspaceListSnapshot(currentContext.workspaces) === toWorkspaceListSnapshot(availableWorkspaces);
157
+
158
+ if (samePermissions && sameWorkspace && sameWorkspaces) {
159
+ return;
160
+ }
161
+
162
+ mergePlacementContext(
163
+ {
164
+ workspace: currentWorkspace,
165
+ workspaces: availableWorkspaces,
166
+ permissions
167
+ },
168
+ "users-web.workspace-settings-view"
169
+ );
170
+ }
171
+
172
+ watch(
173
+ () => bootstrapQuery.query.data.value,
174
+ (payload) => {
175
+ if (!payload || typeof payload !== "object") {
176
+ return;
177
+ }
178
+ applyShellWorkspaceContext(payload);
179
+ },
180
+ { immediate: true }
181
+ );
182
+
183
+ function isCurrentWorkspaceRealtimeEvent({ payload = {} } = {}) {
184
+ return matchesCurrentWorkspaceEvent(payload, routeContext.workspaceSlugFromRoute.value);
185
+ }
186
+
187
+ const addEdit = useAddEdit({
188
+ ownershipFilter: OWNERSHIP_WORKSPACE,
189
+ resource: workspaceSettingsResource,
190
+ apiSuffix: "/settings",
191
+ queryKeyFactory: (surfaceId = "", workspaceSlug = "") =>
192
+ buildWorkspaceQueryKey("settings", surfaceId, workspaceSlug),
193
+ viewPermissions: ["workspace.settings.view", "workspace.settings.update"],
194
+ savePermissions: ["workspace.settings.update"],
195
+ placementSource: "users-web.workspace-settings-view",
196
+ fallbackLoadError: "Unable to load workspace settings.",
197
+ fieldErrorKeys: ["name", "avatarUrl", "color"],
198
+ realtime: {
199
+ event: WORKSPACE_SETTINGS_CHANGED_EVENT,
200
+ matches: isCurrentWorkspaceRealtimeEvent
201
+ },
202
+ model: workspaceForm,
203
+ parseInput: (rawPayload) =>
204
+ validateOperationSection({
205
+ operation: workspaceSettingsResource.operations.patch,
206
+ section: "bodyValidator",
207
+ value: rawPayload
208
+ }),
209
+ mapLoadedToModel: (model, payload = {}) => {
210
+ const settings = payload?.settings && typeof payload.settings === "object" ? payload.settings : {};
211
+
212
+ model.name = String(settings.name || "");
213
+ model.color = String(settings.color || DEFAULT_WORKSPACE_COLOR);
214
+ model.avatarUrl = String(settings.avatarUrl || "");
215
+ model.invitesEnabled = settings.invitesEnabled !== false;
216
+ model.invitesAvailable = settings.invitesAvailable !== false;
217
+ },
218
+ buildRawPayload: (model) => ({
219
+ name: model.name,
220
+ color: model.color,
221
+ avatarUrl: model.avatarUrl,
222
+ invitesEnabled: model.invitesEnabled
223
+ }),
224
+ onSaveSuccess: async () => {
225
+ await bootstrapQuery.query.refetch();
226
+ }
227
+ });
228
+
229
+ const showFormSkeleton = computed(() => Boolean(addEdit.isInitialLoading));
230
+ </script>