@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.
- package/package.descriptor.mjs +507 -0
- package/package.json +31 -0
- package/src/client/components/ConsoleSettingsClientElement.vue +24 -0
- package/src/client/components/MembersAdminClientElement.vue +404 -0
- package/src/client/components/ProfileClientElement.vue +242 -0
- package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +39 -0
- package/src/client/components/UsersShellMenuLinkItem.vue +140 -0
- package/src/client/components/UsersSurfaceAwareMenuLinkItem.vue +87 -0
- package/src/client/components/UsersWorkspaceMembersMenuItem.vue +36 -0
- package/src/client/components/UsersWorkspacePermissionMenuItem.vue +90 -0
- package/src/client/components/UsersWorkspaceSelector.vue +237 -0
- package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +39 -0
- package/src/client/components/UsersWorkspaceToolsWidget.vue +23 -0
- package/src/client/components/WorkspaceMembersClientElement.vue +663 -0
- package/src/client/components/WorkspaceSettingsClientElement.vue +230 -0
- package/src/client/components/WorkspacesClientElement.vue +514 -0
- package/src/client/composables/accountSettingsAvatarUploadRuntime.js +241 -0
- package/src/client/composables/accountSettingsInvitesRuntime.js +88 -0
- package/src/client/composables/accountSettingsRuntimeConstants.js +77 -0
- package/src/client/composables/accountSettingsRuntimeHelpers.js +75 -0
- package/src/client/composables/errorMessageHelpers.js +66 -0
- package/src/client/composables/internal/useOperationScope.js +144 -0
- package/src/client/composables/modelStateHelpers.js +49 -0
- package/src/client/composables/operationUiHelpers.js +121 -0
- package/src/client/composables/operationValidationHelpers.js +52 -0
- package/src/client/composables/refValueHelpers.js +19 -0
- package/src/client/composables/scopeHelpers.js +145 -0
- package/src/client/composables/useAccess.js +109 -0
- package/src/client/composables/useAccountSettingsRuntime.js +533 -0
- package/src/client/composables/useAddEdit.js +135 -0
- package/src/client/composables/useAddEditCore.js +137 -0
- package/src/client/composables/useBootstrapQuery.js +52 -0
- package/src/client/composables/useCommand.js +112 -0
- package/src/client/composables/useCommandCore.js +130 -0
- package/src/client/composables/useEndpointResource.js +104 -0
- package/src/client/composables/useFieldErrorBag.js +61 -0
- package/src/client/composables/useList.js +85 -0
- package/src/client/composables/useListCore.js +65 -0
- package/src/client/composables/usePagedCollection.js +125 -0
- package/src/client/composables/usePaths.js +108 -0
- package/src/client/composables/useRealtimeQueryInvalidation.js +105 -0
- package/src/client/composables/useScopeRuntime.js +107 -0
- package/src/client/composables/useSurfaceRouteContext.js +31 -0
- package/src/client/composables/useUiFeedback.js +96 -0
- package/src/client/composables/useView.js +89 -0
- package/src/client/composables/useViewCore.js +104 -0
- package/src/client/composables/useWorkspaceRouteContext.js +28 -0
- package/src/client/composables/useWorkspaceSurfaceId.js +43 -0
- package/src/client/index.js +7 -0
- package/src/client/lib/bootstrap.js +95 -0
- package/src/client/lib/httpClient.js +67 -0
- package/src/client/lib/menuIcons.js +192 -0
- package/src/client/lib/permissions.js +34 -0
- package/src/client/lib/profileSurfaceMenuLinks.js +142 -0
- package/src/client/lib/surfaceAccessPolicy.js +350 -0
- package/src/client/lib/theme.js +99 -0
- package/src/client/lib/workspaceLinkResolver.js +207 -0
- package/src/client/lib/workspaceSurfaceContext.js +82 -0
- package/src/client/lib/workspaceSurfacePaths.js +163 -0
- package/src/client/providers/UsersWebClientProvider.js +85 -0
- package/src/client/runtime/bootstrapPlacementRouteGuards.js +371 -0
- package/src/client/runtime/bootstrapPlacementRuntime.js +413 -0
- package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +32 -0
- package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +157 -0
- package/src/client/support/contractGuards.js +34 -0
- package/src/client/support/realtimeWorkspace.js +12 -0
- package/src/client/support/runtimeNormalization.js +27 -0
- package/src/client/support/workspaceQueryKeys.js +15 -0
- package/templates/packages/main/src/client/components/AccountPendingInvitesCue.vue +162 -0
- package/templates/src/components/WorkspaceNotFoundCard.vue +33 -0
- package/templates/src/components/account/settings/AccountSettingsClientElement.vue +153 -0
- package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +77 -0
- package/templates/src/components/account/settings/AccountSettingsNotificationsSection.vue +55 -0
- package/templates/src/components/account/settings/AccountSettingsPreferencesSection.vue +125 -0
- package/templates/src/components/account/settings/AccountSettingsProfileSection.vue +94 -0
- package/templates/src/composables/useWorkspaceNotFoundState.js +48 -0
- package/templates/src/pages/account/index.vue +17 -0
- package/templates/src/pages/admin/members/index.vue +7 -0
- package/templates/src/pages/admin/workspace/settings/index.vue +16 -0
- package/templates/src/pages/console/settings/index.vue +16 -0
- package/templates/src/surfaces/admin/index.vue +29 -0
- package/templates/src/surfaces/admin/root.vue +20 -0
- package/templates/src/surfaces/app/index.vue +27 -0
- package/templates/src/surfaces/app/root.vue +20 -0
- package/test/bootstrap.test.js +38 -0
- package/test/bootstrapPlacementRuntime.test.js +991 -0
- package/test/errorMessageHelpers.test.js +28 -0
- package/test/exportsContract.test.js +39 -0
- package/test/menuIcons.test.js +33 -0
- package/test/permissions.test.js +35 -0
- package/test/profileSurfaceMenuLinks.test.js +207 -0
- package/test/refValueHelpers.test.js +14 -0
- package/test/scopeHelpers.test.js +57 -0
- package/test/surfaceAccessPolicy.test.js +129 -0
- package/test/theme.test.js +95 -0
- package/test/workspaceLinkResolver.test.js +61 -0
- package/test/workspaceSurfacePaths.test.js +39 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import Uppy from "@uppy/core";
|
|
2
|
+
import Dashboard from "@uppy/dashboard";
|
|
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";
|
|
9
|
+
import { resolveFieldErrors } from "@jskit-ai/http-runtime/client";
|
|
10
|
+
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
|
+
|
|
35
|
+
function createAccountSettingsAvatarUploadRuntime({
|
|
36
|
+
queryClient,
|
|
37
|
+
sessionQueryKey,
|
|
38
|
+
accountSettingsQueryKey,
|
|
39
|
+
selectedAvatarFileName,
|
|
40
|
+
applySettingsData,
|
|
41
|
+
reportAccountFeedback
|
|
42
|
+
} = {}) {
|
|
43
|
+
let avatarUppy = null;
|
|
44
|
+
|
|
45
|
+
async function resolveCsrfToken() {
|
|
46
|
+
const sessionPayload = await queryClient.fetchQuery({
|
|
47
|
+
queryKey: sessionQueryKey,
|
|
48
|
+
queryFn: () =>
|
|
49
|
+
usersWebHttpClient.request("/api/session", {
|
|
50
|
+
method: "GET"
|
|
51
|
+
}),
|
|
52
|
+
staleTime: 60_000
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const csrfToken = String(sessionPayload?.csrfToken || "");
|
|
56
|
+
if (!csrfToken) {
|
|
57
|
+
throw new Error("Unable to prepare secure avatar upload request.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return csrfToken;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function setup() {
|
|
64
|
+
if (typeof window === "undefined") {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (avatarUppy) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const uppy = new Uppy({
|
|
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
|
+
|
|
151
|
+
applySettingsData(data);
|
|
152
|
+
queryClient.setQueryData(accountSettingsQueryKey, data);
|
|
153
|
+
|
|
154
|
+
const dashboard = uppy.getPlugin("Dashboard");
|
|
155
|
+
if (dashboard && typeof dashboard.closeModal === "function") {
|
|
156
|
+
dashboard.closeModal();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
reportAccountFeedback({
|
|
160
|
+
message: "Avatar uploaded.",
|
|
161
|
+
severity: "success",
|
|
162
|
+
channel: "snackbar",
|
|
163
|
+
dedupeKey: "users-web.account-settings-runtime:avatar-uploaded"
|
|
164
|
+
});
|
|
165
|
+
selectedAvatarFileName.value = "";
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
uppy.on("upload-error", (_file, error, response) => {
|
|
169
|
+
const body = response?.body && typeof response.body === "object" ? response.body : {};
|
|
170
|
+
const fieldErrors = resolveFieldErrors(body);
|
|
171
|
+
|
|
172
|
+
reportAccountFeedback({
|
|
173
|
+
message: String(fieldErrors.avatar || body?.error || error?.message || "Unable to upload avatar."),
|
|
174
|
+
severity: "error",
|
|
175
|
+
channel: "banner",
|
|
176
|
+
dedupeKey: "users-web.account-settings-runtime:avatar-upload-error"
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
uppy.on("restriction-failed", (_file, error) => {
|
|
181
|
+
reportAccountFeedback({
|
|
182
|
+
message: String(error?.message || "Selected avatar file does not meet upload restrictions."),
|
|
183
|
+
severity: "error",
|
|
184
|
+
channel: "banner",
|
|
185
|
+
dedupeKey: "users-web.account-settings-runtime:avatar-upload-restriction"
|
|
186
|
+
});
|
|
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) {
|
|
210
|
+
reportAccountFeedback({
|
|
211
|
+
message: "Avatar editor is unavailable in this environment.",
|
|
212
|
+
severity: "error",
|
|
213
|
+
channel: "banner",
|
|
214
|
+
dedupeKey: "users-web.account-settings-runtime:avatar-editor-unavailable"
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
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
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export { createAccountSettingsAvatarUploadRuntime };
|
|
@@ -0,0 +1,88 @@
|
|
|
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 };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const AVATAR_ALLOWED_MIME_TYPES = Object.freeze(["image/jpeg", "image/png", "image/webp"]);
|
|
2
|
+
const AVATAR_MAX_UPLOAD_BYTES = 5 * 1024 * 1024;
|
|
3
|
+
const AVATAR_DEFAULT_SIZE = 64;
|
|
4
|
+
|
|
5
|
+
const THEME_OPTIONS = Object.freeze([
|
|
6
|
+
{ title: "System", value: "system" },
|
|
7
|
+
{ title: "Light", value: "light" },
|
|
8
|
+
{ title: "Dark", value: "dark" }
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const LOCALE_OPTIONS = Object.freeze([
|
|
12
|
+
{ title: "English (US)", value: "en-US" },
|
|
13
|
+
{ title: "English (UK)", value: "en-GB" },
|
|
14
|
+
{ title: "Italian", value: "it-IT" },
|
|
15
|
+
{ title: "Spanish", value: "es-ES" }
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const DATE_FORMAT_OPTIONS = Object.freeze([
|
|
19
|
+
{ title: "System", value: "system" },
|
|
20
|
+
{ title: "MM/DD/YYYY", value: "mdy" },
|
|
21
|
+
{ title: "DD/MM/YYYY", value: "dmy" },
|
|
22
|
+
{ title: "YYYY-MM-DD", value: "ymd" }
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const NUMBER_FORMAT_OPTIONS = Object.freeze([
|
|
26
|
+
{ title: "System", value: "system" },
|
|
27
|
+
{ title: "1,234.56", value: "comma-dot" },
|
|
28
|
+
{ title: "1.234,56", value: "dot-comma" },
|
|
29
|
+
{ title: "1 234,56", value: "space-comma" }
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const CURRENCY_OPTIONS = Object.freeze(["USD", "EUR", "GBP", "AUD", "JPY"]);
|
|
33
|
+
|
|
34
|
+
const TIME_ZONE_OPTIONS = Object.freeze([
|
|
35
|
+
"UTC",
|
|
36
|
+
"America/New_York",
|
|
37
|
+
"America/Chicago",
|
|
38
|
+
"America/Denver",
|
|
39
|
+
"America/Los_Angeles",
|
|
40
|
+
"Europe/London",
|
|
41
|
+
"Europe/Rome",
|
|
42
|
+
"Asia/Tokyo",
|
|
43
|
+
"Australia/Sydney"
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const AVATAR_SIZE_OPTIONS = Object.freeze([32, 40, 48, 56, 64, 72, 80, 96, 112, 128]);
|
|
47
|
+
|
|
48
|
+
const ACCOUNT_SETTINGS_DEFAULTS = Object.freeze({
|
|
49
|
+
preferences: {
|
|
50
|
+
theme: "system",
|
|
51
|
+
locale: "en-US",
|
|
52
|
+
timeZone: "UTC",
|
|
53
|
+
dateFormat: "system",
|
|
54
|
+
numberFormat: "system",
|
|
55
|
+
currencyCode: "USD",
|
|
56
|
+
avatarSize: AVATAR_DEFAULT_SIZE
|
|
57
|
+
},
|
|
58
|
+
notifications: {
|
|
59
|
+
productUpdates: true,
|
|
60
|
+
accountActivity: true,
|
|
61
|
+
securityAlerts: true
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
ACCOUNT_SETTINGS_DEFAULTS,
|
|
67
|
+
AVATAR_ALLOWED_MIME_TYPES,
|
|
68
|
+
AVATAR_DEFAULT_SIZE,
|
|
69
|
+
AVATAR_MAX_UPLOAD_BYTES,
|
|
70
|
+
AVATAR_SIZE_OPTIONS,
|
|
71
|
+
CURRENCY_OPTIONS,
|
|
72
|
+
DATE_FORMAT_OPTIONS,
|
|
73
|
+
LOCALE_OPTIONS,
|
|
74
|
+
NUMBER_FORMAT_OPTIONS,
|
|
75
|
+
THEME_OPTIONS,
|
|
76
|
+
TIME_ZONE_OPTIONS
|
|
77
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ACCOUNT_SETTINGS_DEFAULTS } from "./accountSettingsRuntimeConstants.js";
|
|
2
|
+
import {
|
|
3
|
+
normalizeReturnToPath as normalizeSharedReturnToPath,
|
|
4
|
+
resolveAllowedOriginsFromPlacementContext
|
|
5
|
+
} from "@jskit-ai/kernel/shared/support";
|
|
6
|
+
import { normalizeRecord } from "../support/runtimeNormalization.js";
|
|
7
|
+
|
|
8
|
+
function normalizeReturnToPath(value, { fallback = "/", accountSettingsPath = "/account", allowedOrigins = [] } = {}) {
|
|
9
|
+
return normalizeSharedReturnToPath(value, {
|
|
10
|
+
fallback,
|
|
11
|
+
allowedOrigins,
|
|
12
|
+
blockedPathnames: [accountSettingsPath],
|
|
13
|
+
pickFirstArrayValue: true
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveAllowedReturnToOrigins(contextValue = null) {
|
|
18
|
+
return resolveAllowedOriginsFromPlacementContext(contextValue);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeSettingsPayload(value) {
|
|
22
|
+
return normalizeRecord(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizePendingInvite(entry) {
|
|
26
|
+
if (!entry || typeof entry !== "object") {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const id = Number(entry.id);
|
|
31
|
+
const workspaceId = Number(entry.workspaceId);
|
|
32
|
+
if (!Number.isInteger(id) || id < 1 || !Number.isInteger(workspaceId) || workspaceId < 1) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const workspaceSlug = String(entry.workspaceSlug || "").trim();
|
|
37
|
+
if (!workspaceSlug) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const token = String(entry.token || "").trim();
|
|
42
|
+
if (!token) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
id,
|
|
48
|
+
token,
|
|
49
|
+
workspaceId,
|
|
50
|
+
workspaceSlug,
|
|
51
|
+
workspaceName: String(entry.workspaceName || workspaceSlug).trim() || workspaceSlug,
|
|
52
|
+
workspaceAvatarUrl: String(entry.workspaceAvatarUrl || "").trim(),
|
|
53
|
+
roleId: String(entry.roleId || "member").trim().toLowerCase() || "member",
|
|
54
|
+
status: String(entry.status || "pending").trim().toLowerCase() || "pending",
|
|
55
|
+
expiresAt: String(entry.expiresAt || "").trim()
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeAvatarSize(value) {
|
|
60
|
+
const numeric = Number(value);
|
|
61
|
+
if (!Number.isInteger(numeric)) {
|
|
62
|
+
return ACCOUNT_SETTINGS_DEFAULTS.preferences.avatarSize;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const clamped = Math.min(128, Math.max(32, numeric));
|
|
66
|
+
return clamped;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
resolveAllowedReturnToOrigins,
|
|
71
|
+
normalizeAvatarSize,
|
|
72
|
+
normalizePendingInvite,
|
|
73
|
+
normalizeReturnToPath,
|
|
74
|
+
normalizeSettingsPayload
|
|
75
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
function normalizeErrorStatus(error) {
|
|
2
|
+
if (error == null || typeof error !== "object") {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const hasStatus = Object.prototype.hasOwnProperty.call(error, "status");
|
|
7
|
+
const hasStatusCode = Object.prototype.hasOwnProperty.call(error, "statusCode");
|
|
8
|
+
if (!hasStatus && !hasStatusCode) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const status = Number(hasStatus ? error.status : error.statusCode);
|
|
13
|
+
return Number.isInteger(status) ? status : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isGenericTransportMessage(error) {
|
|
17
|
+
const normalizedMessage = String(error?.message || "").trim();
|
|
18
|
+
if (!normalizedMessage) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const status = normalizeErrorStatus(error);
|
|
23
|
+
if (status === 0) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return status != null && status >= 400 && normalizedMessage === `Request failed with status ${status}.`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toQueryErrorMessage(error, fallbackMessage = "", defaultMessage = "Request failed.") {
|
|
31
|
+
if (!error) {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const normalizedFallback = String(fallbackMessage || "").trim();
|
|
36
|
+
const normalizedMessage = String(error?.message || "").trim();
|
|
37
|
+
if (normalizedMessage && !isGenericTransportMessage(error)) {
|
|
38
|
+
return normalizedMessage;
|
|
39
|
+
}
|
|
40
|
+
if (normalizedFallback) {
|
|
41
|
+
return normalizedFallback;
|
|
42
|
+
}
|
|
43
|
+
if (normalizedMessage) {
|
|
44
|
+
return normalizedMessage;
|
|
45
|
+
}
|
|
46
|
+
return String(defaultMessage || "Request failed.").trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toUiErrorMessage(error, fallbackMessage = "", defaultMessage = "Request failed.") {
|
|
50
|
+
const normalizedFallback = String(fallbackMessage || "").trim();
|
|
51
|
+
if (normalizedFallback) {
|
|
52
|
+
return normalizedFallback;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const normalizedMessage = String(error?.message || "").trim();
|
|
56
|
+
if (normalizedMessage) {
|
|
57
|
+
return normalizedMessage;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return String(defaultMessage || "Request failed.").trim();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
toQueryErrorMessage,
|
|
65
|
+
toUiErrorMessage
|
|
66
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { computed, unref } from "vue";
|
|
2
|
+
import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
3
|
+
import { useScopeRuntime } from "../useScopeRuntime.js";
|
|
4
|
+
import { useOperationRealtime } from "../useRealtimeQueryInvalidation.js";
|
|
5
|
+
import {
|
|
6
|
+
normalizePermissions,
|
|
7
|
+
resolvePermissionAccess,
|
|
8
|
+
resolveEnabled,
|
|
9
|
+
resolveQueryKey
|
|
10
|
+
} from "../scopeHelpers.js";
|
|
11
|
+
|
|
12
|
+
function normalizePermissionSets(permissionSets = {}) {
|
|
13
|
+
const source = permissionSets && typeof permissionSets === "object" && !Array.isArray(permissionSets)
|
|
14
|
+
? permissionSets
|
|
15
|
+
: {};
|
|
16
|
+
const normalized = {};
|
|
17
|
+
|
|
18
|
+
for (const [key, value] of Object.entries(source)) {
|
|
19
|
+
normalized[key] = normalizePermissions(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function hasAnyPermissions(permissionSets = {}) {
|
|
26
|
+
for (const list of Object.values(permissionSets)) {
|
|
27
|
+
if (Array.isArray(list) && list.length > 0) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function useOperationScope({
|
|
36
|
+
ownershipFilter = USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
37
|
+
surfaceId = "",
|
|
38
|
+
access = "auto",
|
|
39
|
+
placementSource = "users-web.operation",
|
|
40
|
+
apiSuffix = "",
|
|
41
|
+
model,
|
|
42
|
+
readEnabled = true,
|
|
43
|
+
queryKeyFactory = null,
|
|
44
|
+
permissionSets = {},
|
|
45
|
+
realtime = null
|
|
46
|
+
} = {}) {
|
|
47
|
+
const normalizedPermissionSets = normalizePermissionSets(permissionSets);
|
|
48
|
+
const scopeRuntime = useScopeRuntime({
|
|
49
|
+
ownershipFilter,
|
|
50
|
+
surfaceId,
|
|
51
|
+
accessMode: access,
|
|
52
|
+
hasPermissionRequirements: hasAnyPermissions(normalizedPermissionSets),
|
|
53
|
+
placementSource
|
|
54
|
+
});
|
|
55
|
+
const normalizedOwnershipFilter = scopeRuntime.normalizedOwnershipFilter;
|
|
56
|
+
const routeContext = scopeRuntime.routeContext;
|
|
57
|
+
const workspaceSlugFromRoute = scopeRuntime.workspaceSlugFromRoute;
|
|
58
|
+
const hasRouteWorkspaceSlug = scopeRuntime.hasRouteWorkspaceSlug;
|
|
59
|
+
const accessRuntime = scopeRuntime.access;
|
|
60
|
+
|
|
61
|
+
const apiPath = computed(() =>
|
|
62
|
+
scopeRuntime.resolveApiPath(apiSuffix, {
|
|
63
|
+
model
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const queryEnabled = computed(() =>
|
|
68
|
+
resolveEnabled(readEnabled, {
|
|
69
|
+
surfaceId: routeContext.currentSurfaceId.value,
|
|
70
|
+
workspaceSlug: workspaceSlugFromRoute.value,
|
|
71
|
+
ownershipFilter: normalizedOwnershipFilter,
|
|
72
|
+
model
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const queryKey = computed(() =>
|
|
77
|
+
resolveQueryKey(queryKeyFactory, {
|
|
78
|
+
surfaceId: routeContext.currentSurfaceId.value,
|
|
79
|
+
workspaceSlug: workspaceSlugFromRoute.value,
|
|
80
|
+
ownershipFilter: normalizedOwnershipFilter
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
const realtimeBinding = useOperationRealtime({
|
|
84
|
+
realtime,
|
|
85
|
+
queryKey: typeof queryKeyFactory === "function" ? queryKey : null,
|
|
86
|
+
enabled: computed(() => hasRouteWorkspaceSlug.value && Boolean(apiPath.value))
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
function queryCanRun(accessGate = true) {
|
|
90
|
+
return computed(() =>
|
|
91
|
+
queryEnabled.value &&
|
|
92
|
+
hasRouteWorkspaceSlug.value &&
|
|
93
|
+
Boolean(apiPath.value) &&
|
|
94
|
+
Boolean(unref(accessGate))
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function permissionGate(key = "") {
|
|
99
|
+
const list = normalizedPermissionSets[String(key || "")] || [];
|
|
100
|
+
return computed(() => resolvePermissionAccess(accessRuntime, list));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function loadError(baseError = "") {
|
|
104
|
+
return computed(() => {
|
|
105
|
+
if (scopeRuntime.workspaceRouteError.value) {
|
|
106
|
+
return scopeRuntime.workspaceRouteError.value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const bootstrapError = String(accessRuntime.bootstrapError.value || "").trim();
|
|
110
|
+
if (bootstrapError) {
|
|
111
|
+
return bootstrapError;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (baseError === undefined || baseError === null || baseError === "") {
|
|
115
|
+
return "";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return String(unref(baseError) || "").trim();
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isLoading(baseLoading = false) {
|
|
123
|
+
return computed(() => Boolean(unref(baseLoading)) || accessRuntime.isBootstrapping.value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return Object.freeze({
|
|
127
|
+
scopeRuntime,
|
|
128
|
+
routeContext,
|
|
129
|
+
normalizedOwnershipFilter,
|
|
130
|
+
workspaceSlugFromRoute,
|
|
131
|
+
hasRouteWorkspaceSlug,
|
|
132
|
+
access: accessRuntime,
|
|
133
|
+
apiPath,
|
|
134
|
+
queryEnabled,
|
|
135
|
+
queryKey,
|
|
136
|
+
queryCanRun,
|
|
137
|
+
realtime: realtimeBinding,
|
|
138
|
+
permissionGate,
|
|
139
|
+
loadError,
|
|
140
|
+
isLoading
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export { useOperationScope };
|