@jskit-ai/users-web 0.1.36 → 0.1.38

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 (79) hide show
  1. package/package.descriptor.mjs +8 -22
  2. package/package.json +16 -11
  3. package/src/client/components/MembersAdminClientElement.vue +5 -5
  4. package/src/client/components/UsersSurfaceAwareMenuLinkItem.vue +14 -25
  5. package/src/client/components/WorkspaceMembersClientElement.vue +19 -19
  6. package/src/client/components/WorkspaceProfileClientElement.vue +1 -1
  7. package/src/client/components/WorkspaceSettingsFieldsClientElement.vue +1 -1
  8. package/src/client/components/WorkspacesClientElement.vue +4 -4
  9. package/src/client/composables/account-settings/accountSettingsAvatarUploadRuntime.js +61 -0
  10. package/src/client/composables/{accountSettingsInvitesRuntime.js → account-settings/accountSettingsInvitesRuntime.js} +1 -1
  11. package/src/client/composables/{accountSettingsRuntimeConstants.js → account-settings/accountSettingsRuntimeConstants.js} +0 -4
  12. package/src/client/composables/{accountSettingsRuntimeHelpers.js → account-settings/accountSettingsRuntimeHelpers.js} +2 -2
  13. package/src/client/composables/crud/crudBindingSupport.js +75 -0
  14. package/src/client/composables/{crudLookupFieldLabelSupport.js → crud/crudLookupFieldLabelSupport.js} +37 -5
  15. package/src/client/composables/{crudLookupFieldRuntime.js → crud/crudLookupFieldRuntime.js} +11 -4
  16. package/src/client/composables/{crudSchemaFormHelpers.js → crud/crudSchemaFormHelpers.js} +178 -5
  17. package/src/client/composables/internal/crudListParentTitleSupport.js +168 -0
  18. package/src/client/composables/internal/useOperationScope.js +1 -1
  19. package/src/client/composables/{useAddEdit.js → records/useAddEdit.js} +18 -8
  20. package/src/client/composables/{useCrudSchemaForm.js → records/useCrudAddEdit.js} +32 -15
  21. package/src/client/composables/records/useCrudList.js +83 -0
  22. package/src/client/composables/records/useCrudView.js +35 -0
  23. package/src/client/composables/records/useList.js +482 -0
  24. package/src/client/composables/{useView.js → records/useView.js} +7 -7
  25. package/src/client/composables/{addEditUiRuntime.js → runtime/addEditUiRuntime.js} +13 -4
  26. package/src/client/composables/{listUiRuntime.js → runtime/listUiRuntime.js} +20 -8
  27. package/src/client/composables/{operationAdapters.js → runtime/operationAdapters.js} +1 -1
  28. package/src/client/composables/{useEndpointResource.js → runtime/useEndpointResource.js} +5 -5
  29. package/src/client/composables/{useListCore.js → runtime/useListCore.js} +4 -4
  30. package/src/client/composables/{useUiFeedback.js → runtime/useUiFeedback.js} +1 -1
  31. package/src/client/composables/{viewUiRuntime.js → runtime/viewUiRuntime.js} +13 -4
  32. package/src/client/composables/support/listQueryParamSupport.js +459 -0
  33. package/src/client/composables/{routeTemplateHelpers.js → support/routeTemplateHelpers.js} +122 -0
  34. package/src/client/composables/useAccess.js +2 -2
  35. package/src/client/composables/useAccountSettingsRuntime.js +6 -6
  36. package/src/client/composables/useBootstrapQuery.js +1 -1
  37. package/src/client/composables/useCommand.js +5 -5
  38. package/src/client/composables/useCrudListParentTitle.js +131 -0
  39. package/src/client/composables/usePagedCollection.js +58 -7
  40. package/src/client/composables/useScopeRuntime.js +1 -1
  41. package/src/client/lib/bootstrap.js +1 -1
  42. package/src/client/lib/menuIcons.js +27 -6
  43. package/src/client/support/menuLinkTarget.js +93 -0
  44. package/templates/src/components/WorkspaceNotFoundCard.vue +2 -1
  45. package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +1 -1
  46. package/test/addEditUiRuntime.test.js +19 -1
  47. package/test/crudBindingSupport.test.js +110 -0
  48. package/test/crudLookupFieldRuntime.test.js +52 -2
  49. package/test/errorMessageHelpers.test.js +1 -1
  50. package/test/exportsContract.test.js +10 -1
  51. package/test/listQueryParamSupport.test.js +190 -0
  52. package/test/listUiRuntime.test.js +22 -1
  53. package/test/menuIcons.test.js +2 -0
  54. package/test/menuLinkTarget.test.js +116 -0
  55. package/test/permissions.test.js +2 -2
  56. package/test/refValueHelpers.test.js +1 -1
  57. package/test/resourceLoadStateHelpers.test.js +1 -1
  58. package/test/routeTemplateHelpers.test.js +57 -1
  59. package/test/scopeHelpers.test.js +1 -1
  60. package/test/{useCrudSchemaForm.test.js → useCrudAddEdit.test.js} +81 -1
  61. package/test/useCrudListParentTitle.test.js +143 -0
  62. package/test/useListSearchSupport.test.js +1 -1
  63. package/test/usePagedCollection.test.js +53 -0
  64. package/test/viewCoreLoading.test.js +1 -1
  65. package/test/viewUiRuntime.test.js +36 -1
  66. package/src/client/composables/accountSettingsAvatarUploadRuntime.js +0 -241
  67. package/src/client/composables/useList.js +0 -268
  68. /package/src/client/composables/{modelStateHelpers.js → runtime/modelStateHelpers.js} +0 -0
  69. /package/src/client/composables/{operationUiHelpers.js → runtime/operationUiHelpers.js} +0 -0
  70. /package/src/client/composables/{operationValidationHelpers.js → runtime/operationValidationHelpers.js} +0 -0
  71. /package/src/client/composables/{useAddEditCore.js → runtime/useAddEditCore.js} +0 -0
  72. /package/src/client/composables/{useCommandCore.js → runtime/useCommandCore.js} +0 -0
  73. /package/src/client/composables/{useFieldErrorBag.js → runtime/useFieldErrorBag.js} +0 -0
  74. /package/src/client/composables/{useViewCore.js → runtime/useViewCore.js} +0 -0
  75. /package/src/client/composables/{errorMessageHelpers.js → support/errorMessageHelpers.js} +0 -0
  76. /package/src/client/composables/{listSearchSupport.js → support/listSearchSupport.js} +0 -0
  77. /package/src/client/composables/{refValueHelpers.js → support/refValueHelpers.js} +0 -0
  78. /package/src/client/composables/{resourceLoadStateHelpers.js → support/resourceLoadStateHelpers.js} +0 -0
  79. /package/src/client/composables/{scopeHelpers.js → support/scopeHelpers.js} +0 -0
@@ -1,12 +1,13 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/users-web",
4
- version: "0.1.36",
4
+ version: "0.1.38",
5
5
  kind: "runtime",
6
6
  description: "Users web module: workspace selector shell element plus workspace/profile/members UI elements.",
7
7
  dependsOn: [
8
8
  "@jskit-ai/http-runtime",
9
9
  "@jskit-ai/shell-web",
10
+ "@jskit-ai/uploads-image-web",
10
11
  "@jskit-ai/users-core"
11
12
  ],
12
13
  capabilities: {
@@ -32,17 +33,6 @@ export default Object.freeze({
32
33
  }
33
34
  },
34
35
  metadata: {
35
- client: {
36
- optimizeDeps: {
37
- include: [
38
- "@uppy/core",
39
- "@uppy/dashboard",
40
- "@uppy/image-editor",
41
- "@uppy/compressor",
42
- "@uppy/xhr-upload"
43
- ]
44
- }
45
- },
46
36
  apiSummary: {
47
37
  surfaces: [
48
38
  {
@@ -241,16 +231,12 @@ export default Object.freeze({
241
231
  runtime: {
242
232
  "@tanstack/vue-query": "5.92.12",
243
233
  "@mdi/js": "^7.4.47",
244
- "@uppy/compressor": "^3.1.0",
245
- "@uppy/core": "^5.2.0",
246
- "@uppy/dashboard": "^5.1.1",
247
- "@uppy/image-editor": "^4.2.0",
248
- "@uppy/xhr-upload": "^5.1.1",
249
- "@jskit-ai/http-runtime": "0.1.21",
250
- "@jskit-ai/realtime": "0.1.21",
251
- "@jskit-ai/kernel": "0.1.22",
252
- "@jskit-ai/shell-web": "0.1.21",
253
- "@jskit-ai/users-core": "0.1.31",
234
+ "@jskit-ai/http-runtime": "0.1.23",
235
+ "@jskit-ai/realtime": "0.1.23",
236
+ "@jskit-ai/kernel": "0.1.24",
237
+ "@jskit-ai/shell-web": "0.1.23",
238
+ "@jskit-ai/uploads-image-web": "0.1.2",
239
+ "@jskit-ai/users-core": "0.1.33",
254
240
  "vuetify": "^4.0.0"
255
241
  },
256
242
  dev: {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/users-web",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -8,25 +8,30 @@
8
8
  "exports": {
9
9
  "./client": "./src/client/index.js",
10
10
  "./client/components/WorkspaceMembersClientElement": "./src/client/components/WorkspaceMembersClientElement.vue",
11
- "./client/composables/useAddEdit": "./src/client/composables/useAddEdit.js",
12
- "./client/composables/useCrudSchemaForm": "./src/client/composables/useCrudSchemaForm.js",
13
- "./client/composables/crudLookupFieldRuntime": "./src/client/composables/crudLookupFieldRuntime.js",
14
- "./client/composables/useList": "./src/client/composables/useList.js",
15
- "./client/composables/useView": "./src/client/composables/useView.js",
11
+ "./client/composables/useAddEdit": "./src/client/composables/records/useAddEdit.js",
12
+ "./client/composables/useCrudAddEdit": "./src/client/composables/records/useCrudAddEdit.js",
13
+ "./client/composables/crudLookupFieldRuntime": "./src/client/composables/crud/crudLookupFieldRuntime.js",
14
+ "./client/composables/useList": "./src/client/composables/records/useList.js",
15
+ "./client/composables/useCrudList": "./src/client/composables/records/useCrudList.js",
16
+ "./client/composables/useCrudListParentTitle": "./src/client/composables/useCrudListParentTitle.js",
17
+ "./client/composables/useView": "./src/client/composables/records/useView.js",
18
+ "./client/composables/useCrudView": "./src/client/composables/records/useCrudView.js",
16
19
  "./client/composables/usePagedCollection": "./src/client/composables/usePagedCollection.js",
17
20
  "./client/composables/useAccountSettingsRuntime": "./src/client/composables/useAccountSettingsRuntime.js",
18
21
  "./client/composables/usePaths": "./src/client/composables/usePaths.js",
19
22
  "./client/composables/useWorkspaceRouteContext": "./src/client/composables/useWorkspaceRouteContext.js",
23
+ "./client/support/menuLinkTarget": "./src/client/support/menuLinkTarget.js",
20
24
  "./client/support/realtimeWorkspace": "./src/client/support/realtimeWorkspace.js"
21
25
  },
22
26
  "dependencies": {
23
27
  "@tanstack/vue-query": "5.92.12",
24
28
  "@mdi/js": "^7.4.47",
25
- "@jskit-ai/users-core": "0.1.31",
26
- "@jskit-ai/realtime": "0.1.21",
27
- "@jskit-ai/http-runtime": "0.1.21",
28
- "@jskit-ai/kernel": "0.1.22",
29
- "@jskit-ai/shell-web": "0.1.21",
29
+ "@jskit-ai/http-runtime": "0.1.23",
30
+ "@jskit-ai/kernel": "0.1.24",
31
+ "@jskit-ai/realtime": "0.1.23",
32
+ "@jskit-ai/shell-web": "0.1.23",
33
+ "@jskit-ai/uploads-image-web": "0.1.2",
34
+ "@jskit-ai/users-core": "0.1.33",
30
35
  "vuetify": "^4.0.0"
31
36
  }
32
37
  }
@@ -44,7 +44,7 @@
44
44
  class="mb-3"
45
45
  />
46
46
  <v-select
47
- v-model="inviteForm.roleId"
47
+ v-model="inviteForm.roleSid"
48
48
  label="Role"
49
49
  :items="inviteRoleOptions"
50
50
  item-title="title"
@@ -105,7 +105,7 @@
105
105
  <template #append>
106
106
  <div class="d-flex align-center ga-2">
107
107
  <v-select
108
- v-model="member.roleId"
108
+ v-model="member.roleSid"
109
109
  :items="memberRoleOptions"
110
110
  item-title="title"
111
111
  item-value="value"
@@ -139,7 +139,7 @@
139
139
  {{ invite.email }}
140
140
  </template>
141
141
  <template #subtitle>
142
- Role: {{ invite.roleId }} • expires {{ formatDateTime(invite.expiresAt) }}
142
+ Role: {{ invite.roleSid }} • expires {{ formatDateTime(invite.expiresAt) }}
143
143
  </template>
144
144
  <template #append>
145
145
  <v-btn
@@ -375,12 +375,12 @@ async function onRevokeInvite(inviteId) {
375
375
  await actionHandlers.submitRevokeInvite(inviteId);
376
376
  }
377
377
 
378
- async function onMemberRoleUpdate(member, roleId) {
378
+ async function onMemberRoleUpdate(member, roleSid) {
379
379
  if (isMemberRoleLocked(member)) {
380
380
  return;
381
381
  }
382
382
 
383
- await actionHandlers.submitMemberRoleUpdate(member, roleId);
383
+ await actionHandlers.submitMemberRoleUpdate(member, roleSid);
384
384
  }
385
385
 
386
386
  async function onRemoveMember(member) {
@@ -1,9 +1,10 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
+ import { useRoute } from "vue-router";
3
4
  import { useWebPlacementContext } from "@jskit-ai/shell-web/client/placement";
4
5
  import { usePaths } from "../composables/usePaths.js";
5
- import { surfaceRequiresWorkspaceFromPlacementContext } from "../lib/workspaceSurfaceContext.js";
6
6
  import { resolveMenuLinkIcon } from "../lib/menuIcons.js";
7
+ import { resolveMenuLinkTarget } from "../support/menuLinkTarget.js";
7
8
 
8
9
  const props = defineProps({
9
10
  label: {
@@ -36,34 +37,22 @@ const props = defineProps({
36
37
  }
37
38
  });
38
39
 
40
+ const route = useRoute();
39
41
  const paths = usePaths();
40
42
  const { context: placementContext } = useWebPlacementContext();
41
43
 
42
- const targetSurfaceId = computed(() => {
43
- const explicitSurface = String(props.surface || "").trim().toLowerCase();
44
- if (explicitSurface && explicitSurface !== "*") {
45
- return explicitSurface;
46
- }
47
-
48
- return String(paths.currentSurfaceId.value || "").trim().toLowerCase();
49
- });
50
-
51
44
  const resolvedTo = computed(() => {
52
- const explicitTo = String(props.to || "").trim();
53
- if (explicitTo) {
54
- return explicitTo;
55
- }
56
-
57
- const workspaceRequired = surfaceRequiresWorkspaceFromPlacementContext(
58
- placementContext.value,
59
- targetSurfaceId.value
60
- );
61
- const suffix = workspaceRequired ? props.workspaceSuffix : props.nonWorkspaceSuffix;
62
- const normalizedSuffix = String(suffix || "/").trim() || "/";
63
-
64
- return paths.page(normalizedSuffix, {
65
- surface: targetSurfaceId.value,
66
- mode: "auto"
45
+ return resolveMenuLinkTarget({
46
+ to: props.to,
47
+ surface: props.surface,
48
+ currentSurfaceId: paths.currentSurfaceId.value,
49
+ placementContext: placementContext.value,
50
+ workspaceSuffix: props.workspaceSuffix,
51
+ nonWorkspaceSuffix: props.nonWorkspaceSuffix,
52
+ routeParams: route.params || {},
53
+ resolvePagePath(relativePath, options = {}) {
54
+ return paths.page(relativePath, options);
55
+ }
67
56
  });
68
57
  });
69
58
 
@@ -23,11 +23,11 @@ import { computed, reactive, ref, watch } from "vue";
23
23
  import { formatDateTime } from "@jskit-ai/kernel/shared/support";
24
24
  import MembersAdminClientElement from "./MembersAdminClientElement.vue";
25
25
  import { useCommand } from "../composables/useCommand.js";
26
- import { useList } from "../composables/useList.js";
27
- import { useView } from "../composables/useView.js";
26
+ import { useList } from "../composables/records/useList.js";
27
+ import { useView } from "../composables/records/useView.js";
28
28
  import { usePaths } from "../composables/usePaths.js";
29
29
  import { useAccess } from "../composables/useAccess.js";
30
- import { useUiFeedback } from "../composables/useUiFeedback.js";
30
+ import { useUiFeedback } from "../composables/runtime/useUiFeedback.js";
31
31
  import { useWorkspaceRouteContext } from "../composables/useWorkspaceRouteContext.js";
32
32
  import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
33
33
  import { createWorkspaceRealtimeMatcher } from "../support/realtimeWorkspace.js";
@@ -37,7 +37,7 @@ import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/su
37
37
  const forms = reactive({
38
38
  invite: {
39
39
  email: "",
40
- roleId: "member"
40
+ roleSid: "member"
41
41
  },
42
42
  workspace: {
43
43
  invitesEnabled: false,
@@ -139,7 +139,7 @@ function clearRoleOptions() {
139
139
  function resetViewState() {
140
140
  resetMessages();
141
141
  forms.invite.email = "";
142
- forms.invite.roleId = "member";
142
+ forms.invite.roleSid = "member";
143
143
  forms.workspace.invitesEnabled = false;
144
144
  forms.workspace.invitesAvailable = false;
145
145
  collections.members = [];
@@ -149,8 +149,8 @@ function resetViewState() {
149
149
  removeMemberUserId.value = 0;
150
150
  }
151
151
 
152
- function toRoleTitle(roleId) {
153
- const normalizedRoleId = String(roleId || "").trim();
152
+ function toRoleTitle(roleSid) {
153
+ const normalizedRoleId = String(roleSid || "").trim();
154
154
  if (!normalizedRoleId) {
155
155
  return "";
156
156
  }
@@ -182,9 +182,9 @@ function normalizeRoleCatalog(payload = {}) {
182
182
  }
183
183
 
184
184
  const uniqueRoleIds = Array.from(new Set(assignableRoleIds));
185
- const roleOptions = uniqueRoleIds.map((roleId) => ({
186
- title: toRoleTitle(roleId),
187
- value: roleId
185
+ const roleOptions = uniqueRoleIds.map((roleSid) => ({
186
+ title: toRoleTitle(roleSid),
187
+ value: roleSid
188
188
  }));
189
189
 
190
190
  const defaultInviteRole = String(source.defaultInviteRole || "")
@@ -202,19 +202,19 @@ function applyRoleCatalog(payload = {}) {
202
202
  options.inviteRoleOptions = [...normalizedCatalog.roleOptions];
203
203
  options.memberRoleOptions = [...normalizedCatalog.roleOptions];
204
204
 
205
- const selectedInviteRole = String(forms.invite.roleId || "").trim().toLowerCase();
205
+ const selectedInviteRole = String(forms.invite.roleSid || "").trim().toLowerCase();
206
206
  const hasSelectedInviteRole = normalizedCatalog.roleOptions.some((entry) => entry.value === selectedInviteRole);
207
207
 
208
208
  if (
209
209
  normalizedCatalog.defaultInviteRole &&
210
210
  normalizedCatalog.roleOptions.some((entry) => entry.value === normalizedCatalog.defaultInviteRole)
211
211
  ) {
212
- forms.invite.roleId = normalizedCatalog.defaultInviteRole;
212
+ forms.invite.roleSid = normalizedCatalog.defaultInviteRole;
213
213
  return;
214
214
  }
215
215
 
216
216
  if (!hasSelectedInviteRole && normalizedCatalog.roleOptions.length > 0) {
217
- forms.invite.roleId = normalizedCatalog.roleOptions[0].value;
217
+ forms.invite.roleSid = normalizedCatalog.roleOptions[0].value;
218
218
  }
219
219
  }
220
220
 
@@ -224,7 +224,7 @@ function normalizeMembers(entries) {
224
224
  const value = entry && typeof entry === "object" ? entry : {};
225
225
  return {
226
226
  userId: Number(value.userId || 0),
227
- roleId: String(value.roleId || "").trim().toLowerCase(),
227
+ roleSid: String(value.roleSid || "").trim().toLowerCase(),
228
228
  status: String(value.status || "").trim().toLowerCase(),
229
229
  displayName: String(value.displayName || "").trim(),
230
230
  email: String(value.email || "").trim().toLowerCase(),
@@ -240,7 +240,7 @@ function normalizeInvites(entries) {
240
240
  return {
241
241
  id: Number(value.id || 0),
242
242
  email: String(value.email || "").trim().toLowerCase(),
243
- roleId: String(value.roleId || "").trim().toLowerCase(),
243
+ roleSid: String(value.roleSid || "").trim().toLowerCase(),
244
244
  status: String(value.status || "").trim().toLowerCase(),
245
245
  expiresAt: value.expiresAt || "",
246
246
  invitedByUserId: value.invitedByUserId == null ? null : Number(value.invitedByUserId)
@@ -319,7 +319,7 @@ const inviteCreateCommand = useCommand({
319
319
  fallbackRunError: "Unable to send invite.",
320
320
  buildRawPayload: () => ({
321
321
  email: forms.invite.email,
322
- roleId: forms.invite.roleId
322
+ roleSid: forms.invite.roleSid
323
323
  }),
324
324
  messages: {
325
325
  success: "Invite sent.",
@@ -352,7 +352,7 @@ const memberRoleCommand = useCommand({
352
352
  writeMethod: "PATCH",
353
353
  fallbackRunError: "Unable to update member role.",
354
354
  buildRawPayload: (_model, { context }) => ({
355
- roleId: String(context?.roleId || "").trim().toLowerCase()
355
+ roleSid: String(context?.roleSid || "").trim().toLowerCase()
356
356
  }),
357
357
  buildCommandOptions: (_parsed, { context }) => {
358
358
  return {
@@ -595,7 +595,7 @@ async function submitRevokeInvite(inviteId) {
595
595
  }
596
596
  }
597
597
 
598
- async function submitMemberRoleUpdate(member, roleId) {
598
+ async function submitMemberRoleUpdate(member, roleSid) {
599
599
  if (!canManageMembers.value) {
600
600
  return;
601
601
  }
@@ -610,7 +610,7 @@ async function submitMemberRoleUpdate(member, roleId) {
610
610
 
611
611
  await memberRoleCommand.run({
612
612
  memberUserId,
613
- roleId
613
+ roleSid
614
614
  });
615
615
  await Promise.all([
616
616
  workspaceMembersList.reload(),
@@ -71,7 +71,7 @@ import { computed, reactive } from "vue";
71
71
  import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
72
72
  import { workspaceResource } from "@jskit-ai/users-core/shared/resources/workspaceResource";
73
73
  import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
74
- import { useAddEdit } from "../composables/useAddEdit.js";
74
+ import { useAddEdit } from "../composables/records/useAddEdit.js";
75
75
  import { buildWorkspaceQueryKey } from "../support/workspaceQueryKeys.js";
76
76
 
77
77
  const emit = defineEmits(["saved"]);
@@ -176,7 +176,7 @@ import {
176
176
  DEFAULT_WORKSPACE_LIGHT_PALETTE,
177
177
  resolveWorkspaceThemePalettes
178
178
  } from "@jskit-ai/users-core/shared/settings";
179
- import { useAddEdit } from "../composables/useAddEdit.js";
179
+ import { useAddEdit } from "../composables/records/useAddEdit.js";
180
180
  import { useWorkspaceRouteContext } from "../composables/useWorkspaceRouteContext.js";
181
181
  import { createWorkspaceRealtimeMatcher } from "../support/realtimeWorkspace.js";
182
182
  import { buildWorkspaceQueryKey } from "../support/workspaceQueryKeys.js";
@@ -8,12 +8,12 @@ import {
8
8
  import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
9
9
  import { normalizeWorkspaceList } from "../lib/bootstrap.js";
10
10
  import { useCommand } from "../composables/useCommand.js";
11
- import { useView } from "../composables/useView.js";
11
+ import { useView } from "../composables/records/useView.js";
12
12
  import { usePaths } from "../composables/usePaths.js";
13
13
  import { useRealtimeQueryInvalidation } from "../composables/useRealtimeQueryInvalidation.js";
14
14
  import { useWorkspaceSurfaceId } from "../composables/useWorkspaceSurfaceId.js";
15
15
  import { USERS_ROUTE_VISIBILITY_PUBLIC } from "@jskit-ai/users-core/shared/support/usersVisibility";
16
- import { normalizePendingInvite } from "../composables/accountSettingsRuntimeHelpers.js";
16
+ import { normalizePendingInvite } from "../composables/account-settings/accountSettingsRuntimeHelpers.js";
17
17
 
18
18
  const route = useRoute();
19
19
  const router = useRouter();
@@ -421,7 +421,7 @@ watch(
421
421
  :title="workspace.name"
422
422
  :subtitle="
423
423
  workspace.isAccessible
424
- ? `/${workspace.slug} • role: ${workspace.roleId || 'member'}`
424
+ ? `/${workspace.slug} • role: ${workspace.roleSid || 'member'}`
425
425
  : `/${workspace.slug} • unavailable on this surface`
426
426
  "
427
427
  class="px-0"
@@ -466,7 +466,7 @@ watch(
466
466
  v-for="invite in pendingInvites"
467
467
  :key="invite.id"
468
468
  :title="invite.workspaceName"
469
- :subtitle="`Role: ${invite.roleId}`"
469
+ :subtitle="`Role: ${invite.roleSid}`"
470
470
  class="px-0"
471
471
  >
472
472
  <template #prepend>
@@ -0,0 +1,61 @@
1
+ import "@jskit-ai/uploads-image-web/client/styles";
2
+ import { createManagedImageAssetRuntime } from "@jskit-ai/uploads-image-web/client/composables/createManagedImageAssetRuntime";
3
+ import { resolveFieldErrors } from "@jskit-ai/http-runtime/client";
4
+ import { usersWebHttpClient } from "../../lib/httpClient.js";
5
+
6
+ function createAccountSettingsAvatarUploadRuntime({
7
+ queryClient,
8
+ sessionQueryKey,
9
+ accountSettingsQueryKey,
10
+ selectedAvatarFileName,
11
+ applySettingsData,
12
+ reportAccountFeedback
13
+ } = {}) {
14
+ async function resolveCsrfToken() {
15
+ const sessionPayload = await queryClient.fetchQuery({
16
+ queryKey: sessionQueryKey,
17
+ queryFn: () =>
18
+ usersWebHttpClient.request("/api/session", {
19
+ method: "GET"
20
+ }),
21
+ staleTime: 60_000
22
+ });
23
+
24
+ const csrfToken = String(sessionPayload?.csrfToken || "");
25
+ if (!csrfToken) {
26
+ throw new Error("Unable to prepare secure avatar upload request.");
27
+ }
28
+
29
+ return csrfToken;
30
+ }
31
+
32
+ return createManagedImageAssetRuntime({
33
+ uploadEndpoint: "/api/settings/profile/avatar",
34
+ fieldName: "avatar",
35
+ selectedFileName: selectedAvatarFileName,
36
+ resolveRequestHeaders: async () => ({
37
+ "csrf-token": await resolveCsrfToken()
38
+ }),
39
+ onUploadSuccess: ({ data }) => {
40
+ applySettingsData(data);
41
+ queryClient.setQueryData(accountSettingsQueryKey, data);
42
+ },
43
+ reportFeedback: reportAccountFeedback,
44
+ resolveUploadErrorMessage: ({ error, response, defaultMessage }) => {
45
+ const body = response?.body && typeof response.body === "object" ? response.body : {};
46
+ const fieldErrors = resolveFieldErrors(body);
47
+ return String(fieldErrors.avatar || body?.error || error?.message || defaultMessage);
48
+ },
49
+ messages: {
50
+ uploadSuccess: "Avatar uploaded.",
51
+ uploadInvalidResponse: "Avatar uploaded, but the response payload was invalid.",
52
+ uploadError: "Unable to upload avatar.",
53
+ uploadRestriction: "Selected avatar file does not meet upload restrictions.",
54
+ editorUnavailable: "Avatar editor is unavailable in this environment.",
55
+ changeError: "Avatar uploaded, but the settings view could not refresh."
56
+ },
57
+ source: "users-web.account-settings-runtime:avatar"
58
+ });
59
+ }
60
+
61
+ export { createAccountSettingsAvatarUploadRuntime };
@@ -1,4 +1,4 @@
1
- import { resolveErrorStatusCode } from "../support/runtimeNormalization.js";
1
+ import { resolveErrorStatusCode } from "../../support/runtimeNormalization.js";
2
2
 
3
3
  function createAccountSettingsInvitesRuntime({
4
4
  invitesAvailable,
@@ -1,5 +1,3 @@
1
- const AVATAR_ALLOWED_MIME_TYPES = Object.freeze(["image/jpeg", "image/png", "image/webp"]);
2
- const AVATAR_MAX_UPLOAD_BYTES = 5 * 1024 * 1024;
3
1
  const AVATAR_DEFAULT_SIZE = 64;
4
2
 
5
3
  const THEME_OPTIONS = Object.freeze([
@@ -64,9 +62,7 @@ const ACCOUNT_SETTINGS_DEFAULTS = Object.freeze({
64
62
 
65
63
  export {
66
64
  ACCOUNT_SETTINGS_DEFAULTS,
67
- AVATAR_ALLOWED_MIME_TYPES,
68
65
  AVATAR_DEFAULT_SIZE,
69
- AVATAR_MAX_UPLOAD_BYTES,
70
66
  AVATAR_SIZE_OPTIONS,
71
67
  CURRENCY_OPTIONS,
72
68
  DATE_FORMAT_OPTIONS,
@@ -3,7 +3,7 @@ import {
3
3
  normalizeReturnToPath as normalizeSharedReturnToPath,
4
4
  resolveAllowedOriginsFromPlacementContext
5
5
  } from "@jskit-ai/kernel/shared/support";
6
- import { normalizeRecord } from "../support/runtimeNormalization.js";
6
+ import { normalizeRecord } from "../../support/runtimeNormalization.js";
7
7
 
8
8
  function normalizeReturnToPath(value, { fallback = "/", accountSettingsPath = "/account", allowedOrigins = [] } = {}) {
9
9
  return normalizeSharedReturnToPath(value, {
@@ -50,7 +50,7 @@ function normalizePendingInvite(entry) {
50
50
  workspaceSlug,
51
51
  workspaceName: String(entry.workspaceName || workspaceSlug).trim() || workspaceSlug,
52
52
  workspaceAvatarUrl: String(entry.workspaceAvatarUrl || "").trim(),
53
- roleId: String(entry.roleId || "member").trim().toLowerCase() || "member",
53
+ roleSid: String(entry.roleSid || "member").trim().toLowerCase() || "member",
54
54
  status: String(entry.status || "pending").trim().toLowerCase() || "pending",
55
55
  expiresAt: String(entry.expiresAt || "").trim()
56
56
  };
@@ -0,0 +1,75 @@
1
+ import { unref } from "vue";
2
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { asPlainObject } from "../support/scopeHelpers.js";
4
+
5
+ const CRUD_BINDING_MODE_ROUTE = "route";
6
+ const CRUD_BINDING_MODE_MERGE = "merge";
7
+ const CRUD_BINDING_MODE_EXPLICIT = "explicit";
8
+ const CRUD_BINDING_MODE_NONE = "none";
9
+
10
+ function normalizeCrudBindingMode(value = "") {
11
+ const normalizedValue = normalizeText(value).toLowerCase();
12
+ if (normalizedValue === CRUD_BINDING_MODE_MERGE) {
13
+ return CRUD_BINDING_MODE_MERGE;
14
+ }
15
+ if (normalizedValue === CRUD_BINDING_MODE_EXPLICIT) {
16
+ return CRUD_BINDING_MODE_EXPLICIT;
17
+ }
18
+ if (normalizedValue === CRUD_BINDING_MODE_NONE) {
19
+ return CRUD_BINDING_MODE_NONE;
20
+ }
21
+
22
+ return CRUD_BINDING_MODE_ROUTE;
23
+ }
24
+
25
+ function normalizeCrudBindingConfig(binding = {}) {
26
+ const source = asPlainObject(unref(binding));
27
+ return Object.freeze({
28
+ mode: normalizeCrudBindingMode(source.mode),
29
+ values: source.values ?? null
30
+ });
31
+ }
32
+
33
+ function resolveCrudBindingValues(values, context = {}) {
34
+ if (typeof values === "function") {
35
+ return asPlainObject(values(context));
36
+ }
37
+
38
+ return asPlainObject(unref(values));
39
+ }
40
+
41
+ function resolveCrudBoundValues({
42
+ binding = {},
43
+ routeValues = {},
44
+ context = {}
45
+ } = {}) {
46
+ const normalizedBinding = normalizeCrudBindingConfig(binding);
47
+ const normalizedRouteValues = asPlainObject(routeValues);
48
+ const explicitValues = resolveCrudBindingValues(normalizedBinding.values, context);
49
+
50
+ if (normalizedBinding.mode === CRUD_BINDING_MODE_NONE) {
51
+ return {};
52
+ }
53
+ if (normalizedBinding.mode === CRUD_BINDING_MODE_EXPLICIT) {
54
+ return explicitValues;
55
+ }
56
+ if (normalizedBinding.mode === CRUD_BINDING_MODE_MERGE) {
57
+ return {
58
+ ...normalizedRouteValues,
59
+ ...explicitValues
60
+ };
61
+ }
62
+
63
+ return normalizedRouteValues;
64
+ }
65
+
66
+ export {
67
+ CRUD_BINDING_MODE_ROUTE,
68
+ CRUD_BINDING_MODE_MERGE,
69
+ CRUD_BINDING_MODE_EXPLICIT,
70
+ CRUD_BINDING_MODE_NONE,
71
+ normalizeCrudBindingMode,
72
+ normalizeCrudBindingConfig,
73
+ resolveCrudBindingValues,
74
+ resolveCrudBoundValues
75
+ };
@@ -1,13 +1,16 @@
1
1
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
2
  import { normalizeCrudLookupContainerKey } from "@jskit-ai/kernel/shared/support/crudLookup";
3
- import { asPlainObject } from "./scopeHelpers.js";
3
+ import { asPlainObject } from "../support/scopeHelpers.js";
4
4
 
5
5
  const LOOKUP_LABEL_COMPOSITION_CANDIDATES = Object.freeze([
6
6
  Object.freeze(["name", "surname"]),
7
+ Object.freeze(["name", "lastName"]),
7
8
  Object.freeze(["firstName", "surname"]),
9
+ Object.freeze(["firstName", "lastName"]),
8
10
  Object.freeze(["name"]),
9
11
  Object.freeze(["firstName"])
10
12
  ]);
13
+ const DEFAULT_RECORD_TITLE = "-";
11
14
 
12
15
  function hasDisplayValue(value) {
13
16
  if (value == null) {
@@ -20,9 +23,8 @@ function hasDisplayValue(value) {
20
23
  return true;
21
24
  }
22
25
 
23
- function resolveLookupItemLabel(item = {}, labelKey = "") {
24
- const source = asPlainObject(item);
25
- for (const candidate of LOOKUP_LABEL_COMPOSITION_CANDIDATES) {
26
+ function resolveComposedLabel(source = {}, candidates = LOOKUP_LABEL_COMPOSITION_CANDIDATES) {
27
+ for (const candidate of candidates) {
26
28
  const parts = [];
27
29
  for (const key of candidate) {
28
30
  const part = normalizeText(source[key]);
@@ -37,6 +39,16 @@ function resolveLookupItemLabel(item = {}, labelKey = "") {
37
39
  }
38
40
  }
39
41
 
42
+ return "";
43
+ }
44
+
45
+ function resolveLookupItemLabel(item = {}, labelKey = "") {
46
+ const source = asPlainObject(item);
47
+ const composedLabel = resolveComposedLabel(source);
48
+ if (composedLabel) {
49
+ return composedLabel;
50
+ }
51
+
40
52
  const normalizedLabelKey = normalizeText(labelKey);
41
53
  if (!normalizedLabelKey) {
42
54
  return "";
@@ -45,6 +57,25 @@ function resolveLookupItemLabel(item = {}, labelKey = "") {
45
57
  return normalizeText(source[normalizedLabelKey]);
46
58
  }
47
59
 
60
+ function resolveRecordTitle(record = {}, { fallbackKey = "", defaultValue = DEFAULT_RECORD_TITLE } = {}) {
61
+ const source = asPlainObject(record);
62
+ const composedLabel = resolveComposedLabel(source);
63
+ if (composedLabel) {
64
+ return composedLabel;
65
+ }
66
+
67
+ const normalizedFallbackKey = normalizeText(fallbackKey);
68
+ if (normalizedFallbackKey) {
69
+ const fallbackValue = normalizeText(source[normalizedFallbackKey]);
70
+ if (fallbackValue) {
71
+ return fallbackValue;
72
+ }
73
+ }
74
+
75
+ const normalizedDefaultValue = normalizeText(defaultValue);
76
+ return normalizedDefaultValue || DEFAULT_RECORD_TITLE;
77
+ }
78
+
48
79
  function resolveLookupFieldDescriptor(field = {}, relationKind = "", valueKey = "", labelKey = "") {
49
80
  if (typeof field === "string") {
50
81
  return {
@@ -103,5 +134,6 @@ function resolveLookupFieldDisplayValue(record = {}, field = {}, relationKind =
103
134
 
104
135
  export {
105
136
  resolveLookupItemLabel,
106
- resolveLookupFieldDisplayValue
137
+ resolveLookupFieldDisplayValue,
138
+ resolveRecordTitle
107
139
  };