@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,162 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useRoute } from "vue-router";
|
|
4
|
+
import { useQuery } from "@tanstack/vue-query";
|
|
5
|
+
import { mdiEmailAlertOutline } from "@mdi/js";
|
|
6
|
+
import { appendQueryString } from "@jskit-ai/kernel/shared/support";
|
|
7
|
+
import {
|
|
8
|
+
useWebPlacementContext,
|
|
9
|
+
resolveSurfaceDefinitionFromPlacementContext,
|
|
10
|
+
resolveSurfacePathFromPlacementContext,
|
|
11
|
+
resolveSurfaceNavigationTargetFromPlacementContext
|
|
12
|
+
} from "@jskit-ai/shell-web/client/placement";
|
|
13
|
+
|
|
14
|
+
const { context: placementContext } = useWebPlacementContext();
|
|
15
|
+
const route = useRoute();
|
|
16
|
+
|
|
17
|
+
function normalizePendingInvitesCount(value) {
|
|
18
|
+
const numeric = Number(value);
|
|
19
|
+
if (!Number.isInteger(numeric) || numeric < 1) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
return numeric;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveReturnTo() {
|
|
26
|
+
const fullPath = String(route?.fullPath || "").trim();
|
|
27
|
+
if (fullPath.startsWith("/") && !fullPath.startsWith("//")) {
|
|
28
|
+
return fullPath;
|
|
29
|
+
}
|
|
30
|
+
const path = String(route?.path || "").trim();
|
|
31
|
+
if (path.startsWith("/") && !path.startsWith("//")) {
|
|
32
|
+
return path;
|
|
33
|
+
}
|
|
34
|
+
return "/";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveReturnToHref() {
|
|
38
|
+
if (typeof window === "object" && window?.location?.href) {
|
|
39
|
+
return String(window.location.href || "").trim() || resolveReturnTo();
|
|
40
|
+
}
|
|
41
|
+
return resolveReturnTo();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function countPendingInvites(entries = []) {
|
|
45
|
+
if (!Array.isArray(entries)) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let total = 0;
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
if (!entry || typeof entry !== "object") {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
total += 1;
|
|
55
|
+
}
|
|
56
|
+
return total;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const authenticated = computed(() => placementContext.value?.auth?.authenticated === true);
|
|
60
|
+
|
|
61
|
+
const bootstrapSummaryQuery = useQuery({
|
|
62
|
+
queryKey: ["local-main", "account", "invites-cue", "bootstrap"],
|
|
63
|
+
enabled: authenticated,
|
|
64
|
+
staleTime: 5_000,
|
|
65
|
+
refetchInterval: 15_000,
|
|
66
|
+
queryFn: async () => {
|
|
67
|
+
const response = await fetch("/api/bootstrap", {
|
|
68
|
+
method: "GET",
|
|
69
|
+
credentials: "include",
|
|
70
|
+
headers: {
|
|
71
|
+
accept: "application/json"
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new Error(`Bootstrap request failed with status ${response.status}.`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return response.json();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const placementPendingInvitesCount = computed(() =>
|
|
83
|
+
normalizePendingInvitesCount(placementContext.value?.pendingInvitesCount)
|
|
84
|
+
);
|
|
85
|
+
const bootstrapPendingInvitesCount = computed(() => {
|
|
86
|
+
const payload = bootstrapSummaryQuery.data.value;
|
|
87
|
+
const invitesEnabled = payload?.app?.features?.workspaceInvites === true;
|
|
88
|
+
if (!invitesEnabled) {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return countPendingInvites(payload?.pendingInvites);
|
|
93
|
+
});
|
|
94
|
+
const pendingInvitesCount = computed(() =>
|
|
95
|
+
Math.max(placementPendingInvitesCount.value, bootstrapPendingInvitesCount.value)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const placementWorkspaceInvitesEnabled = computed(() => placementContext.value?.workspaceInvitesEnabled === true);
|
|
99
|
+
const bootstrapWorkspaceInvitesEnabled = computed(() => {
|
|
100
|
+
const payload = bootstrapSummaryQuery.data.value;
|
|
101
|
+
return payload?.app?.features?.workspaceInvites === true;
|
|
102
|
+
});
|
|
103
|
+
const workspaceInvitesEnabled = computed(
|
|
104
|
+
() => placementWorkspaceInvitesEnabled.value || bootstrapWorkspaceInvitesEnabled.value
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const isVisible = computed(() => {
|
|
108
|
+
return (
|
|
109
|
+
authenticated.value &&
|
|
110
|
+
workspaceInvitesEnabled.value &&
|
|
111
|
+
pendingInvitesCount.value > 0
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const resolvedTo = computed(() => {
|
|
116
|
+
const hasAccountSurface = Boolean(resolveSurfaceDefinitionFromPlacementContext(placementContext.value, "account"));
|
|
117
|
+
const accountSettingsPath = hasAccountSurface
|
|
118
|
+
? resolveSurfacePathFromPlacementContext(placementContext.value, "account", "/")
|
|
119
|
+
: "/account";
|
|
120
|
+
const accountSettingsNavigation = resolveSurfaceNavigationTargetFromPlacementContext(placementContext.value, {
|
|
121
|
+
path: accountSettingsPath,
|
|
122
|
+
surfaceId: "account"
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const query = new URLSearchParams({
|
|
126
|
+
section: "invites",
|
|
127
|
+
returnTo: accountSettingsNavigation.sameOrigin ? resolveReturnTo() : resolveReturnToHref()
|
|
128
|
+
});
|
|
129
|
+
return appendQueryString(accountSettingsPath, query.toString());
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const resolvedNavigationTarget = computed(() =>
|
|
133
|
+
resolveSurfaceNavigationTargetFromPlacementContext(placementContext.value, {
|
|
134
|
+
path: resolvedTo.value,
|
|
135
|
+
surfaceId: "account"
|
|
136
|
+
})
|
|
137
|
+
);
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<template>
|
|
141
|
+
<v-badge
|
|
142
|
+
v-if="isVisible"
|
|
143
|
+
color="error"
|
|
144
|
+
:content="pendingInvitesCount"
|
|
145
|
+
:model-value="pendingInvitesCount > 0"
|
|
146
|
+
bordered
|
|
147
|
+
offset-x="6"
|
|
148
|
+
offset-y="8"
|
|
149
|
+
>
|
|
150
|
+
<v-btn
|
|
151
|
+
:to="resolvedNavigationTarget.sameOrigin ? resolvedNavigationTarget.href : undefined"
|
|
152
|
+
:href="resolvedNavigationTarget.sameOrigin ? undefined : resolvedNavigationTarget.href"
|
|
153
|
+
variant="tonal"
|
|
154
|
+
color="warning"
|
|
155
|
+
:prepend-icon="mdiEmailAlertOutline"
|
|
156
|
+
size="small"
|
|
157
|
+
class="text-none"
|
|
158
|
+
>
|
|
159
|
+
Invites
|
|
160
|
+
</v-btn>
|
|
161
|
+
</v-badge>
|
|
162
|
+
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
surfaceLabel: {
|
|
6
|
+
type: String,
|
|
7
|
+
default: "Workspace"
|
|
8
|
+
},
|
|
9
|
+
message: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: "Workspace is currently unavailable."
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const normalizedSurfaceLabel = computed(() => String(props.surfaceLabel || "").trim() || "Workspace");
|
|
16
|
+
const normalizedMessage = computed(() => String(props.message || "").trim() || "Workspace is currently unavailable.");
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<v-card rounded="lg" elevation="1" border>
|
|
21
|
+
<v-card-item>
|
|
22
|
+
<template #prepend>
|
|
23
|
+
<v-icon icon="mdi-alert-circle-outline" color="error" />
|
|
24
|
+
</template>
|
|
25
|
+
<v-card-title class="text-h5">Unavailable</v-card-title>
|
|
26
|
+
<v-card-subtitle>{{ normalizedSurfaceLabel }} surface.</v-card-subtitle>
|
|
27
|
+
</v-card-item>
|
|
28
|
+
<v-divider />
|
|
29
|
+
<v-card-text class="d-flex flex-column ga-4">
|
|
30
|
+
<p class="text-medium-emphasis mb-0">{{ normalizedMessage }}</p>
|
|
31
|
+
</v-card-text>
|
|
32
|
+
</v-card>
|
|
33
|
+
</template>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useRoute, useRouter } from "vue-router";
|
|
4
|
+
import { useAccountSettingsRuntime } from "@jskit-ai/users-web/client/composables/useAccountSettingsRuntime";
|
|
5
|
+
import AccountSettingsProfileSection from "./AccountSettingsProfileSection.vue";
|
|
6
|
+
import AccountSettingsPreferencesSection from "./AccountSettingsPreferencesSection.vue";
|
|
7
|
+
import AccountSettingsNotificationsSection from "./AccountSettingsNotificationsSection.vue";
|
|
8
|
+
import AccountSettingsInvitesSection from "./AccountSettingsInvitesSection.vue";
|
|
9
|
+
|
|
10
|
+
const runtime = useAccountSettingsRuntime();
|
|
11
|
+
const route = useRoute();
|
|
12
|
+
const router = useRouter();
|
|
13
|
+
|
|
14
|
+
const sections = Object.freeze([
|
|
15
|
+
{ title: "Profile", value: "profile" },
|
|
16
|
+
{ title: "Preferences", value: "preferences" },
|
|
17
|
+
{ title: "Notifications", value: "notifications" },
|
|
18
|
+
{ title: "Invites", value: "invites" }
|
|
19
|
+
]);
|
|
20
|
+
const sectionValues = new Set(sections.map((section) => section.value));
|
|
21
|
+
|
|
22
|
+
function normalizeSection(value) {
|
|
23
|
+
const source = Array.isArray(value) ? value[0] : value;
|
|
24
|
+
const normalized = String(source || "").trim().toLowerCase();
|
|
25
|
+
if (!sectionValues.has(normalized)) {
|
|
26
|
+
return "profile";
|
|
27
|
+
}
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readRouteSection() {
|
|
32
|
+
return normalizeSection(route?.query?.section);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const activeTab = computed({
|
|
36
|
+
get() {
|
|
37
|
+
return readRouteSection();
|
|
38
|
+
},
|
|
39
|
+
set(nextValue) {
|
|
40
|
+
const normalizedSection = normalizeSection(nextValue);
|
|
41
|
+
const currentSection = readRouteSection();
|
|
42
|
+
if (normalizedSection === currentSection) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const nextQuery = {
|
|
47
|
+
...route.query
|
|
48
|
+
};
|
|
49
|
+
if (normalizedSection === "profile") {
|
|
50
|
+
delete nextQuery.section;
|
|
51
|
+
} else {
|
|
52
|
+
nextQuery.section = normalizedSection;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void router.replace({
|
|
56
|
+
query: nextQuery
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<template>
|
|
63
|
+
<section class="settings-view py-2 py-md-4">
|
|
64
|
+
<v-card class="panel-card" rounded="lg" elevation="1" border>
|
|
65
|
+
<v-card-item>
|
|
66
|
+
<v-card-title class="panel-title">Account settings</v-card-title>
|
|
67
|
+
<v-card-subtitle>Global profile, preferences, notifications, and invitation controls.</v-card-subtitle>
|
|
68
|
+
<template #append>
|
|
69
|
+
<v-btn
|
|
70
|
+
variant="text"
|
|
71
|
+
color="secondary"
|
|
72
|
+
:to="runtime.backNavigationTarget.value.sameOrigin ? runtime.backNavigationTarget.value.href : undefined"
|
|
73
|
+
:href="runtime.backNavigationTarget.value.sameOrigin ? undefined : runtime.backNavigationTarget.value.href"
|
|
74
|
+
>
|
|
75
|
+
Back
|
|
76
|
+
</v-btn>
|
|
77
|
+
</template>
|
|
78
|
+
</v-card-item>
|
|
79
|
+
<v-divider />
|
|
80
|
+
|
|
81
|
+
<v-card-text class="pt-4">
|
|
82
|
+
<template v-if="runtime.loadingSettings.value">
|
|
83
|
+
<v-skeleton-loader type="text@2, list-item-two-line@4" class="mb-4" />
|
|
84
|
+
<v-skeleton-loader type="text@2, paragraph, button" />
|
|
85
|
+
</template>
|
|
86
|
+
<template v-else>
|
|
87
|
+
<v-progress-linear v-if="runtime.refreshingSettings.value" indeterminate class="mb-4" />
|
|
88
|
+
<v-row class="settings-layout" no-gutters>
|
|
89
|
+
<v-col cols="12" md="3" lg="2" class="pr-md-4 mb-4 mb-md-0">
|
|
90
|
+
<v-list nav density="comfortable" class="settings-section-list rounded-lg">
|
|
91
|
+
<v-list-item
|
|
92
|
+
v-for="section in sections"
|
|
93
|
+
:key="section.value"
|
|
94
|
+
:title="section.title"
|
|
95
|
+
:active="activeTab === section.value"
|
|
96
|
+
rounded="lg"
|
|
97
|
+
@click="activeTab = section.value"
|
|
98
|
+
/>
|
|
99
|
+
</v-list>
|
|
100
|
+
</v-col>
|
|
101
|
+
|
|
102
|
+
<v-col cols="12" md="9" lg="10">
|
|
103
|
+
<v-window v-model="activeTab" :touch="false" class="settings-sections-window">
|
|
104
|
+
<v-window-item value="profile">
|
|
105
|
+
<AccountSettingsProfileSection :runtime="runtime" />
|
|
106
|
+
</v-window-item>
|
|
107
|
+
|
|
108
|
+
<v-window-item value="preferences">
|
|
109
|
+
<AccountSettingsPreferencesSection :runtime="runtime" />
|
|
110
|
+
</v-window-item>
|
|
111
|
+
|
|
112
|
+
<v-window-item value="notifications">
|
|
113
|
+
<AccountSettingsNotificationsSection :runtime="runtime" />
|
|
114
|
+
</v-window-item>
|
|
115
|
+
|
|
116
|
+
<v-window-item value="invites">
|
|
117
|
+
<AccountSettingsInvitesSection :runtime="runtime" />
|
|
118
|
+
</v-window-item>
|
|
119
|
+
</v-window>
|
|
120
|
+
</v-col>
|
|
121
|
+
</v-row>
|
|
122
|
+
</template>
|
|
123
|
+
</v-card-text>
|
|
124
|
+
</v-card>
|
|
125
|
+
</section>
|
|
126
|
+
</template>
|
|
127
|
+
|
|
128
|
+
<style scoped>
|
|
129
|
+
.panel-card {
|
|
130
|
+
background-color: rgb(var(--v-theme-surface));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.panel-title {
|
|
134
|
+
font-size: 1rem;
|
|
135
|
+
font-weight: 600;
|
|
136
|
+
letter-spacing: 0.01em;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.settings-section-list {
|
|
140
|
+
border: 1px solid rgba(var(--v-theme-outline), 0.35);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
:deep(.settings-section-list .v-list-item--active) {
|
|
144
|
+
background-color: rgba(var(--v-theme-primary), 0.14);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
:deep(.settings-sections-window .v-window-x-transition-enter-active),
|
|
148
|
+
:deep(.settings-sections-window .v-window-x-transition-leave-active),
|
|
149
|
+
:deep(.settings-sections-window .v-window-x-reverse-transition-enter-active),
|
|
150
|
+
:deep(.settings-sections-window .v-window-x-reverse-transition-leave-active) {
|
|
151
|
+
transition: none !important;
|
|
152
|
+
}
|
|
153
|
+
</style>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
runtime: {
|
|
4
|
+
type: Object,
|
|
5
|
+
required: true
|
|
6
|
+
}
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const invites = props.runtime.invites;
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<v-card rounded="lg" elevation="0" border>
|
|
14
|
+
<v-card-item>
|
|
15
|
+
<v-card-title class="text-subtitle-1">Invitations</v-card-title>
|
|
16
|
+
<v-card-subtitle>Accept or refuse workspace invitations.</v-card-subtitle>
|
|
17
|
+
</v-card-item>
|
|
18
|
+
<v-divider />
|
|
19
|
+
<v-card-text>
|
|
20
|
+
<template v-if="invites.isLoading.value">
|
|
21
|
+
<v-skeleton-loader type="text@2, list-item-two-line@3" />
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<template v-else-if="invites.items.value.length < 1">
|
|
25
|
+
<v-progress-linear v-if="invites.isRefetching.value" indeterminate class="mb-4" />
|
|
26
|
+
<p class="text-body-2 text-medium-emphasis mb-0">No pending invitations.</p>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<template v-else>
|
|
30
|
+
<v-progress-linear v-if="invites.isRefetching.value" indeterminate class="mb-4" />
|
|
31
|
+
<v-list density="comfortable" class="pa-0">
|
|
32
|
+
<v-list-item
|
|
33
|
+
v-for="invite in invites.items.value"
|
|
34
|
+
:key="invite.id"
|
|
35
|
+
:title="invite.workspaceName"
|
|
36
|
+
:subtitle="`/${invite.workspaceSlug} • role: ${invite.roleId}`"
|
|
37
|
+
class="px-0"
|
|
38
|
+
>
|
|
39
|
+
<template #prepend>
|
|
40
|
+
<v-avatar color="warning" size="28">
|
|
41
|
+
<span class="text-caption font-weight-bold">?</span>
|
|
42
|
+
</v-avatar>
|
|
43
|
+
</template>
|
|
44
|
+
<template #append>
|
|
45
|
+
<div class="d-flex ga-2">
|
|
46
|
+
<v-btn
|
|
47
|
+
size="small"
|
|
48
|
+
variant="text"
|
|
49
|
+
color="error"
|
|
50
|
+
:loading="invites.action.value.token === invite.token && invites.action.value.decision === 'refuse'"
|
|
51
|
+
:disabled="
|
|
52
|
+
invites.isRefetching.value || (invites.isResolving.value && invites.action.value.token !== invite.token)
|
|
53
|
+
"
|
|
54
|
+
@click="invites.refuse(invite)"
|
|
55
|
+
>
|
|
56
|
+
Refuse
|
|
57
|
+
</v-btn>
|
|
58
|
+
<v-btn
|
|
59
|
+
size="small"
|
|
60
|
+
variant="tonal"
|
|
61
|
+
color="primary"
|
|
62
|
+
:loading="invites.action.value.token === invite.token && invites.action.value.decision === 'accept'"
|
|
63
|
+
:disabled="
|
|
64
|
+
invites.isRefetching.value || (invites.isResolving.value && invites.action.value.token !== invite.token)
|
|
65
|
+
"
|
|
66
|
+
@click="invites.accept(invite)"
|
|
67
|
+
>
|
|
68
|
+
Join
|
|
69
|
+
</v-btn>
|
|
70
|
+
</div>
|
|
71
|
+
</template>
|
|
72
|
+
</v-list-item>
|
|
73
|
+
</v-list>
|
|
74
|
+
</template>
|
|
75
|
+
</v-card-text>
|
|
76
|
+
</v-card>
|
|
77
|
+
</template>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
runtime: {
|
|
4
|
+
type: Object,
|
|
5
|
+
required: true
|
|
6
|
+
}
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const notifications = props.runtime.notifications;
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<v-card rounded="lg" elevation="0" border>
|
|
14
|
+
<v-card-item>
|
|
15
|
+
<v-card-title class="text-subtitle-1">Notifications</v-card-title>
|
|
16
|
+
</v-card-item>
|
|
17
|
+
<v-divider />
|
|
18
|
+
<v-card-text>
|
|
19
|
+
<v-form @submit.prevent="notifications.submit" novalidate>
|
|
20
|
+
<v-switch
|
|
21
|
+
v-model="notifications.form.productUpdates"
|
|
22
|
+
label="Product updates"
|
|
23
|
+
color="primary"
|
|
24
|
+
hide-details
|
|
25
|
+
:disabled="notifications.isSaving.value || notifications.isRefreshing.value"
|
|
26
|
+
class="mb-2"
|
|
27
|
+
/>
|
|
28
|
+
<v-switch
|
|
29
|
+
v-model="notifications.form.accountActivity"
|
|
30
|
+
label="Account activity alerts"
|
|
31
|
+
color="primary"
|
|
32
|
+
hide-details
|
|
33
|
+
:disabled="notifications.isSaving.value || notifications.isRefreshing.value"
|
|
34
|
+
class="mb-2"
|
|
35
|
+
/>
|
|
36
|
+
<v-switch
|
|
37
|
+
v-model="notifications.form.securityAlerts"
|
|
38
|
+
label="Security alerts (required)"
|
|
39
|
+
color="primary"
|
|
40
|
+
hide-details
|
|
41
|
+
disabled
|
|
42
|
+
class="mb-4"
|
|
43
|
+
/>
|
|
44
|
+
<v-btn
|
|
45
|
+
type="submit"
|
|
46
|
+
color="primary"
|
|
47
|
+
:loading="notifications.isSaving.value"
|
|
48
|
+
:disabled="notifications.isRefreshing.value"
|
|
49
|
+
>
|
|
50
|
+
Save notification settings
|
|
51
|
+
</v-btn>
|
|
52
|
+
</v-form>
|
|
53
|
+
</v-card-text>
|
|
54
|
+
</v-card>
|
|
55
|
+
</template>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
runtime: {
|
|
4
|
+
type: Object,
|
|
5
|
+
required: true
|
|
6
|
+
}
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const preferences = props.runtime.preferences;
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<v-card rounded="lg" elevation="0" border>
|
|
14
|
+
<v-card-item>
|
|
15
|
+
<v-card-title class="text-subtitle-1">Preferences</v-card-title>
|
|
16
|
+
</v-card-item>
|
|
17
|
+
<v-divider />
|
|
18
|
+
<v-card-text>
|
|
19
|
+
<v-form @submit.prevent="preferences.submit" novalidate>
|
|
20
|
+
<v-row>
|
|
21
|
+
<v-col cols="12" md="4">
|
|
22
|
+
<v-select
|
|
23
|
+
v-model="preferences.form.theme"
|
|
24
|
+
label="Theme"
|
|
25
|
+
:items="preferences.options.theme"
|
|
26
|
+
item-title="title"
|
|
27
|
+
item-value="value"
|
|
28
|
+
variant="outlined"
|
|
29
|
+
density="comfortable"
|
|
30
|
+
:disabled="preferences.isSaving.value || preferences.isRefreshing.value"
|
|
31
|
+
:error-messages="preferences.fieldErrors.theme ? [preferences.fieldErrors.theme] : []"
|
|
32
|
+
/>
|
|
33
|
+
</v-col>
|
|
34
|
+
|
|
35
|
+
<v-col cols="12" md="4">
|
|
36
|
+
<v-select
|
|
37
|
+
v-model="preferences.form.locale"
|
|
38
|
+
label="Language / locale"
|
|
39
|
+
:items="preferences.options.locale"
|
|
40
|
+
item-title="title"
|
|
41
|
+
item-value="value"
|
|
42
|
+
variant="outlined"
|
|
43
|
+
density="comfortable"
|
|
44
|
+
:disabled="preferences.isSaving.value || preferences.isRefreshing.value"
|
|
45
|
+
:error-messages="preferences.fieldErrors.locale ? [preferences.fieldErrors.locale] : []"
|
|
46
|
+
/>
|
|
47
|
+
</v-col>
|
|
48
|
+
|
|
49
|
+
<v-col cols="12" md="4">
|
|
50
|
+
<v-select
|
|
51
|
+
v-model="preferences.form.timeZone"
|
|
52
|
+
label="Time zone"
|
|
53
|
+
:items="preferences.options.timeZone"
|
|
54
|
+
variant="outlined"
|
|
55
|
+
density="comfortable"
|
|
56
|
+
:disabled="preferences.isSaving.value || preferences.isRefreshing.value"
|
|
57
|
+
:error-messages="preferences.fieldErrors.timeZone ? [preferences.fieldErrors.timeZone] : []"
|
|
58
|
+
/>
|
|
59
|
+
</v-col>
|
|
60
|
+
|
|
61
|
+
<v-col cols="12" md="4">
|
|
62
|
+
<v-select
|
|
63
|
+
v-model="preferences.form.dateFormat"
|
|
64
|
+
label="Date format"
|
|
65
|
+
:items="preferences.options.dateFormat"
|
|
66
|
+
item-title="title"
|
|
67
|
+
item-value="value"
|
|
68
|
+
variant="outlined"
|
|
69
|
+
density="comfortable"
|
|
70
|
+
:disabled="preferences.isSaving.value || preferences.isRefreshing.value"
|
|
71
|
+
:error-messages="preferences.fieldErrors.dateFormat ? [preferences.fieldErrors.dateFormat] : []"
|
|
72
|
+
/>
|
|
73
|
+
</v-col>
|
|
74
|
+
|
|
75
|
+
<v-col cols="12" md="4">
|
|
76
|
+
<v-select
|
|
77
|
+
v-model="preferences.form.numberFormat"
|
|
78
|
+
label="Number format"
|
|
79
|
+
:items="preferences.options.numberFormat"
|
|
80
|
+
item-title="title"
|
|
81
|
+
item-value="value"
|
|
82
|
+
variant="outlined"
|
|
83
|
+
density="comfortable"
|
|
84
|
+
:disabled="preferences.isSaving.value || preferences.isRefreshing.value"
|
|
85
|
+
:error-messages="preferences.fieldErrors.numberFormat ? [preferences.fieldErrors.numberFormat] : []"
|
|
86
|
+
/>
|
|
87
|
+
</v-col>
|
|
88
|
+
|
|
89
|
+
<v-col cols="12" md="4">
|
|
90
|
+
<v-select
|
|
91
|
+
v-model="preferences.form.currencyCode"
|
|
92
|
+
label="Currency"
|
|
93
|
+
:items="preferences.options.currency"
|
|
94
|
+
variant="outlined"
|
|
95
|
+
density="comfortable"
|
|
96
|
+
:disabled="preferences.isSaving.value || preferences.isRefreshing.value"
|
|
97
|
+
:error-messages="preferences.fieldErrors.currencyCode ? [preferences.fieldErrors.currencyCode] : []"
|
|
98
|
+
/>
|
|
99
|
+
</v-col>
|
|
100
|
+
|
|
101
|
+
<v-col cols="12" md="3">
|
|
102
|
+
<v-select
|
|
103
|
+
v-model.number="preferences.form.avatarSize"
|
|
104
|
+
label="Avatar size"
|
|
105
|
+
:items="preferences.options.avatarSize"
|
|
106
|
+
variant="outlined"
|
|
107
|
+
density="comfortable"
|
|
108
|
+
:disabled="preferences.isSaving.value || preferences.isRefreshing.value"
|
|
109
|
+
:error-messages="preferences.fieldErrors.avatarSize ? [preferences.fieldErrors.avatarSize] : []"
|
|
110
|
+
/>
|
|
111
|
+
</v-col>
|
|
112
|
+
</v-row>
|
|
113
|
+
|
|
114
|
+
<v-btn
|
|
115
|
+
type="submit"
|
|
116
|
+
color="primary"
|
|
117
|
+
:loading="preferences.isSaving.value"
|
|
118
|
+
:disabled="preferences.isRefreshing.value"
|
|
119
|
+
>
|
|
120
|
+
Save preferences
|
|
121
|
+
</v-btn>
|
|
122
|
+
</v-form>
|
|
123
|
+
</v-card-text>
|
|
124
|
+
</v-card>
|
|
125
|
+
</template>
|