@jskit-ai/users-web 0.1.36 → 0.1.37
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 +8 -22
- package/package.json +7 -6
- package/src/client/components/MembersAdminClientElement.vue +5 -5
- package/src/client/components/WorkspaceMembersClientElement.vue +16 -16
- package/src/client/components/WorkspacesClientElement.vue +2 -2
- package/src/client/composables/accountSettingsAvatarUploadRuntime.js +26 -172
- package/src/client/composables/accountSettingsRuntimeConstants.js +0 -4
- package/src/client/composables/accountSettingsRuntimeHelpers.js +1 -1
- package/src/client/composables/addEditUiRuntime.js +11 -2
- package/src/client/composables/crudLookupFieldLabelSupport.js +36 -4
- package/src/client/composables/crudLookupFieldRuntime.js +5 -2
- package/src/client/composables/crudSchemaFormHelpers.js +23 -3
- package/src/client/composables/listQueryParamSupport.js +459 -0
- package/src/client/composables/listUiRuntime.js +18 -6
- package/src/client/composables/routeTemplateHelpers.js +122 -0
- package/src/client/composables/useAddEdit.js +10 -0
- package/src/client/composables/useList.js +242 -2
- package/src/client/composables/usePagedCollection.js +55 -4
- package/src/client/composables/useView.js +4 -1
- package/src/client/composables/viewUiRuntime.js +11 -2
- package/src/client/lib/bootstrap.js +1 -1
- package/src/client/lib/menuIcons.js +27 -6
- package/templates/src/components/WorkspaceNotFoundCard.vue +2 -1
- package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +1 -1
- package/test/addEditUiRuntime.test.js +18 -0
- package/test/crudLookupFieldRuntime.test.js +51 -1
- package/test/listQueryParamSupport.test.js +190 -0
- package/test/listUiRuntime.test.js +21 -0
- package/test/menuIcons.test.js +2 -0
- package/test/routeTemplateHelpers.test.js +56 -0
- package/test/usePagedCollection.test.js +53 -0
- package/test/viewUiRuntime.test.js +35 -0
package/package.descriptor.mjs
CHANGED
|
@@ -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.
|
|
4
|
+
version: "0.1.37",
|
|
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
|
-
"@
|
|
245
|
-
"@
|
|
246
|
-
"@
|
|
247
|
-
"@
|
|
248
|
-
"@
|
|
249
|
-
"@jskit-ai/
|
|
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.22",
|
|
235
|
+
"@jskit-ai/realtime": "0.1.22",
|
|
236
|
+
"@jskit-ai/kernel": "0.1.23",
|
|
237
|
+
"@jskit-ai/shell-web": "0.1.22",
|
|
238
|
+
"@jskit-ai/uploads-image-web": "0.1.1",
|
|
239
|
+
"@jskit-ai/users-core": "0.1.32",
|
|
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.
|
|
3
|
+
"version": "0.1.37",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@tanstack/vue-query": "5.92.12",
|
|
24
24
|
"@mdi/js": "^7.4.47",
|
|
25
|
-
"@jskit-ai/
|
|
26
|
-
"@jskit-ai/
|
|
27
|
-
"@jskit-ai/
|
|
28
|
-
"@jskit-ai/
|
|
29
|
-
"@jskit-ai/
|
|
25
|
+
"@jskit-ai/http-runtime": "0.1.22",
|
|
26
|
+
"@jskit-ai/kernel": "0.1.23",
|
|
27
|
+
"@jskit-ai/realtime": "0.1.22",
|
|
28
|
+
"@jskit-ai/shell-web": "0.1.22",
|
|
29
|
+
"@jskit-ai/uploads-image-web": "0.1.1",
|
|
30
|
+
"@jskit-ai/users-core": "0.1.32",
|
|
30
31
|
"vuetify": "^4.0.0"
|
|
31
32
|
}
|
|
32
33
|
}
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
class="mb-3"
|
|
45
45
|
/>
|
|
46
46
|
<v-select
|
|
47
|
-
v-model="inviteForm.
|
|
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.
|
|
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.
|
|
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,
|
|
378
|
+
async function onMemberRoleUpdate(member, roleSid) {
|
|
379
379
|
if (isMemberRoleLocked(member)) {
|
|
380
380
|
return;
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
-
await actionHandlers.submitMemberRoleUpdate(member,
|
|
383
|
+
await actionHandlers.submitMemberRoleUpdate(member, roleSid);
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
async function onRemoveMember(member) {
|
|
@@ -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
|
-
|
|
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.
|
|
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(
|
|
153
|
-
const normalizedRoleId = String(
|
|
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((
|
|
186
|
-
title: toRoleTitle(
|
|
187
|
-
value:
|
|
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.
|
|
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.
|
|
212
|
+
forms.invite.roleSid = normalizedCatalog.defaultInviteRole;
|
|
213
213
|
return;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
if (!hasSelectedInviteRole && normalizedCatalog.roleOptions.length > 0) {
|
|
217
|
-
forms.invite.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
613
|
+
roleSid
|
|
614
614
|
});
|
|
615
615
|
await Promise.all([
|
|
616
616
|
workspaceMembersList.reload(),
|
|
@@ -421,7 +421,7 @@ watch(
|
|
|
421
421
|
:title="workspace.name"
|
|
422
422
|
:subtitle="
|
|
423
423
|
workspace.isAccessible
|
|
424
|
-
? `/${workspace.slug} • role: ${workspace.
|
|
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.
|
|
469
|
+
:subtitle="`Role: ${invite.roleSid}`"
|
|
470
470
|
class="px-0"
|
|
471
471
|
>
|
|
472
472
|
<template #prepend>
|
|
@@ -1,36 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import ImageEditor from "@uppy/image-editor";
|
|
4
|
-
import Compressor from "@uppy/compressor";
|
|
5
|
-
import XHRUpload from "@uppy/xhr-upload";
|
|
6
|
-
import "@uppy/core/css/style.min.css";
|
|
7
|
-
import "@uppy/dashboard/css/style.min.css";
|
|
8
|
-
import "@uppy/image-editor/css/style.min.css";
|
|
1
|
+
import "@jskit-ai/uploads-image-web/client/styles";
|
|
2
|
+
import { createImageUploadRuntime } from "@jskit-ai/uploads-image-web/client/composables/createImageUploadRuntime";
|
|
9
3
|
import { resolveFieldErrors } from "@jskit-ai/http-runtime/client";
|
|
10
4
|
import { usersWebHttpClient } from "../lib/httpClient.js";
|
|
11
|
-
import {
|
|
12
|
-
AVATAR_ALLOWED_MIME_TYPES,
|
|
13
|
-
AVATAR_MAX_UPLOAD_BYTES
|
|
14
|
-
} from "./accountSettingsRuntimeConstants.js";
|
|
15
|
-
|
|
16
|
-
function parseUploadResponse(xhr) {
|
|
17
|
-
if (!xhr.responseText) {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
return JSON.parse(xhr.responseText);
|
|
23
|
-
} catch {
|
|
24
|
-
return {};
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function stopImageEditor(uppy) {
|
|
29
|
-
const imageEditor = uppy.getPlugin("ImageEditor");
|
|
30
|
-
if (imageEditor && typeof imageEditor.stop === "function") {
|
|
31
|
-
imageEditor.stop();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
5
|
|
|
35
6
|
function createAccountSettingsAvatarUploadRuntime({
|
|
36
7
|
queryClient,
|
|
@@ -40,8 +11,6 @@ function createAccountSettingsAvatarUploadRuntime({
|
|
|
40
11
|
applySettingsData,
|
|
41
12
|
reportAccountFeedback
|
|
42
13
|
} = {}) {
|
|
43
|
-
let avatarUppy = null;
|
|
44
|
-
|
|
45
14
|
async function resolveCsrfToken() {
|
|
46
15
|
const sessionPayload = await queryClient.fetchQuery({
|
|
47
16
|
queryKey: sessionQueryKey,
|
|
@@ -60,94 +29,16 @@ function createAccountSettingsAvatarUploadRuntime({
|
|
|
60
29
|
return csrfToken;
|
|
61
30
|
}
|
|
62
31
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
autoProceed: false,
|
|
74
|
-
restrictions: {
|
|
75
|
-
maxNumberOfFiles: 1,
|
|
76
|
-
allowedFileTypes: [...AVATAR_ALLOWED_MIME_TYPES],
|
|
77
|
-
maxFileSize: AVATAR_MAX_UPLOAD_BYTES
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
uppy.use(Dashboard, {
|
|
82
|
-
inline: false,
|
|
83
|
-
closeAfterFinish: false,
|
|
84
|
-
showProgressDetails: true,
|
|
85
|
-
proudlyDisplayPoweredByUppy: false,
|
|
86
|
-
hideUploadButton: false,
|
|
87
|
-
doneButtonHandler: () => {
|
|
88
|
-
const dashboard = uppy.getPlugin("Dashboard");
|
|
89
|
-
if (dashboard && typeof dashboard.closeModal === "function") {
|
|
90
|
-
dashboard.closeModal();
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
note: `Accepted: ${AVATAR_ALLOWED_MIME_TYPES.join(", ")}, max ${Math.floor(AVATAR_MAX_UPLOAD_BYTES / (1024 * 1024))}MB`
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
uppy.use(ImageEditor, {
|
|
97
|
-
quality: 0.9
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
uppy.use(Compressor, {
|
|
101
|
-
quality: 0.84,
|
|
102
|
-
limit: 1
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
uppy.use(XHRUpload, {
|
|
106
|
-
endpoint: "/api/settings/profile/avatar",
|
|
107
|
-
method: "POST",
|
|
108
|
-
formData: true,
|
|
109
|
-
fieldName: "avatar",
|
|
110
|
-
withCredentials: true,
|
|
111
|
-
onBeforeRequest: async (xhr) => {
|
|
112
|
-
const csrfToken = await resolveCsrfToken();
|
|
113
|
-
xhr.setRequestHeader("csrf-token", csrfToken);
|
|
114
|
-
},
|
|
115
|
-
getResponseData: parseUploadResponse
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
uppy.on("file-added", (file) => {
|
|
119
|
-
selectedAvatarFileName.value = String(file?.name || "");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
uppy.on("file-removed", () => {
|
|
123
|
-
selectedAvatarFileName.value = "";
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
uppy.on("file-editor:complete", (file) => {
|
|
127
|
-
selectedAvatarFileName.value = String(file?.name || selectedAvatarFileName.value || "");
|
|
128
|
-
stopImageEditor(uppy);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
uppy.on("file-editor:cancel", () => {
|
|
132
|
-
stopImageEditor(uppy);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
uppy.on("dashboard:modal-closed", () => {
|
|
136
|
-
stopImageEditor(uppy);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
uppy.on("upload-success", (_file, response) => {
|
|
140
|
-
const data = response?.body;
|
|
141
|
-
if (!data || typeof data !== "object") {
|
|
142
|
-
reportAccountFeedback({
|
|
143
|
-
message: "Avatar uploaded, but the response payload was invalid.",
|
|
144
|
-
severity: "error",
|
|
145
|
-
channel: "banner",
|
|
146
|
-
dedupeKey: "users-web.account-settings-runtime:avatar-upload-invalid-response"
|
|
147
|
-
});
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
32
|
+
return createImageUploadRuntime({
|
|
33
|
+
endpoint: "/api/settings/profile/avatar",
|
|
34
|
+
fieldName: "avatar",
|
|
35
|
+
resolveRequestHeaders: async () => ({
|
|
36
|
+
"csrf-token": await resolveCsrfToken()
|
|
37
|
+
}),
|
|
38
|
+
onSelectedFileNameChanged: (fileName) => {
|
|
39
|
+
selectedAvatarFileName.value = String(fileName || "");
|
|
40
|
+
},
|
|
41
|
+
onUploadSuccess: ({ data, uppy }) => {
|
|
151
42
|
applySettingsData(data);
|
|
152
43
|
queryClient.setQueryData(accountSettingsQueryKey, data);
|
|
153
44
|
|
|
@@ -162,10 +53,16 @@ function createAccountSettingsAvatarUploadRuntime({
|
|
|
162
53
|
channel: "snackbar",
|
|
163
54
|
dedupeKey: "users-web.account-settings-runtime:avatar-uploaded"
|
|
164
55
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
56
|
+
},
|
|
57
|
+
onInvalidResponse: () => {
|
|
58
|
+
reportAccountFeedback({
|
|
59
|
+
message: "Avatar uploaded, but the response payload was invalid.",
|
|
60
|
+
severity: "error",
|
|
61
|
+
channel: "banner",
|
|
62
|
+
dedupeKey: "users-web.account-settings-runtime:avatar-upload-invalid-response"
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
onUploadError: ({ error, response }) => {
|
|
169
66
|
const body = response?.body && typeof response.body === "object" ? response.body : {};
|
|
170
67
|
const fieldErrors = resolveFieldErrors(body);
|
|
171
68
|
|
|
@@ -175,66 +72,23 @@ function createAccountSettingsAvatarUploadRuntime({
|
|
|
175
72
|
channel: "banner",
|
|
176
73
|
dedupeKey: "users-web.account-settings-runtime:avatar-upload-error"
|
|
177
74
|
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
uppy.on("restriction-failed", (_file, error) => {
|
|
75
|
+
},
|
|
76
|
+
onRestrictionFailed: ({ error }) => {
|
|
181
77
|
reportAccountFeedback({
|
|
182
78
|
message: String(error?.message || "Selected avatar file does not meet upload restrictions."),
|
|
183
79
|
severity: "error",
|
|
184
80
|
channel: "banner",
|
|
185
81
|
dedupeKey: "users-web.account-settings-runtime:avatar-upload-restriction"
|
|
186
82
|
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
uppy.on("complete", (result) => {
|
|
190
|
-
const successfulCount = Array.isArray(result?.successful) ? result.successful.length : 0;
|
|
191
|
-
if (successfulCount <= 0) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
uppy.clear();
|
|
197
|
-
} catch {
|
|
198
|
-
// Upload succeeded; ignore clear timing issues.
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
avatarUppy = uppy;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function openEditor() {
|
|
206
|
-
setup();
|
|
207
|
-
|
|
208
|
-
const uppy = avatarUppy;
|
|
209
|
-
if (!uppy) {
|
|
83
|
+
},
|
|
84
|
+
onUnavailable: () => {
|
|
210
85
|
reportAccountFeedback({
|
|
211
86
|
message: "Avatar editor is unavailable in this environment.",
|
|
212
87
|
severity: "error",
|
|
213
88
|
channel: "banner",
|
|
214
89
|
dedupeKey: "users-web.account-settings-runtime:avatar-editor-unavailable"
|
|
215
90
|
});
|
|
216
|
-
return;
|
|
217
91
|
}
|
|
218
|
-
|
|
219
|
-
const dashboard = uppy.getPlugin("Dashboard");
|
|
220
|
-
if (dashboard && typeof dashboard.openModal === "function") {
|
|
221
|
-
dashboard.openModal();
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function destroy() {
|
|
226
|
-
if (!avatarUppy) {
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
avatarUppy.destroy();
|
|
231
|
-
avatarUppy = null;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return Object.freeze({
|
|
235
|
-
destroy,
|
|
236
|
-
openEditor,
|
|
237
|
-
setup
|
|
238
92
|
});
|
|
239
93
|
}
|
|
240
94
|
|
|
@@ -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,
|
|
@@ -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
|
-
|
|
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
|
};
|
|
@@ -3,7 +3,7 @@ import { asPlainObject } from "./scopeHelpers.js";
|
|
|
3
3
|
import {
|
|
4
4
|
normalizeRouteParamName,
|
|
5
5
|
resolveRouteParamsSource,
|
|
6
|
-
|
|
6
|
+
resolveScopedRoutePathname,
|
|
7
7
|
resolveRouteTemplateLocation,
|
|
8
8
|
toRouteParamValue
|
|
9
9
|
} from "./routeTemplateHelpers.js";
|
|
@@ -31,6 +31,7 @@ function resolveSavedRecordId(payload, saveRecordIdSelector) {
|
|
|
31
31
|
function createAddEditUiRuntime({
|
|
32
32
|
recordIdParam = "recordId",
|
|
33
33
|
routeParams = null,
|
|
34
|
+
routeParamNames = null,
|
|
34
35
|
routePath = "",
|
|
35
36
|
routeRecordId = null,
|
|
36
37
|
apiUrlTemplate = "",
|
|
@@ -63,10 +64,18 @@ function createAddEditUiRuntime({
|
|
|
63
64
|
routeRecordId
|
|
64
65
|
});
|
|
65
66
|
sourceParams[normalizedRecordIdParam] = resolvedRecordId;
|
|
67
|
+
const currentPathname = resolveScopedRoutePathname({
|
|
68
|
+
currentPathname: routePath,
|
|
69
|
+
params: currentRouteParams,
|
|
70
|
+
orderedParamNames: routeParamNames,
|
|
71
|
+
anchorParamName: normalizedRecordIdParam,
|
|
72
|
+
anchorParamValue: resolvedRecordId,
|
|
73
|
+
anchorMode: "after"
|
|
74
|
+
});
|
|
66
75
|
|
|
67
76
|
return resolveRouteTemplateLocation(normalizedTemplate, {
|
|
68
77
|
params: sourceParams,
|
|
69
|
-
currentPathname
|
|
78
|
+
currentPathname
|
|
70
79
|
});
|
|
71
80
|
}
|
|
72
81
|
|
|
@@ -4,10 +4,13 @@ import { asPlainObject } from "./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
|
|
24
|
-
const
|
|
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
|
};
|
|
@@ -66,7 +66,8 @@ function createSelectedLookupItem(selectedValue, selectedRecord = {}, entry = {}
|
|
|
66
66
|
const label = displayValue == null || displayValue === "" ? value : displayValue;
|
|
67
67
|
return {
|
|
68
68
|
value,
|
|
69
|
-
label: String(label ?? "")
|
|
69
|
+
label: String(label ?? ""),
|
|
70
|
+
record: hydratedLookup
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
73
|
|
|
@@ -161,12 +162,14 @@ function createCrudLookupFieldRuntime({
|
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
const items = (Array.isArray(entry.runtime.items) ? entry.runtime.items : []).map((item = {}) => {
|
|
165
|
+
const sourceRecord = asPlainObject(item);
|
|
164
166
|
const value = normalizeLookupValue(item?.[entry.valueKey]);
|
|
165
167
|
const resolvedLabel = resolveLookupItemLabel(item, entry.labelKey);
|
|
166
168
|
const label = resolvedLabel || value;
|
|
167
169
|
return {
|
|
168
170
|
value,
|
|
169
|
-
label: String(label ?? "")
|
|
171
|
+
label: String(label ?? ""),
|
|
172
|
+
record: sourceRecord
|
|
170
173
|
};
|
|
171
174
|
});
|
|
172
175
|
|