@vc-shell/framework 1.0.38 → 1.0.39

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 (142) hide show
  1. package/components/atoms/vc-badge/vc-badge.stories.ts +27 -0
  2. package/components/atoms/vc-badge/vc-badge.vue +63 -0
  3. package/components/atoms/vc-button/vc-button.stories.ts +34 -0
  4. package/components/atoms/vc-button/vc-button.vue +219 -0
  5. package/components/atoms/vc-card/vc-card.vue +137 -0
  6. package/components/atoms/vc-checkbox/vc-checkbox.stories.ts +25 -0
  7. package/components/atoms/vc-checkbox/vc-checkbox.vue +130 -0
  8. package/components/atoms/vc-col/vc-col.vue +22 -0
  9. package/components/atoms/vc-container/vc-container.stories.ts +31 -0
  10. package/components/atoms/vc-container/vc-container.vue +220 -0
  11. package/components/atoms/vc-hint/vc-hint.stories.ts +23 -0
  12. package/components/atoms/vc-hint/vc-hint.vue +11 -0
  13. package/components/atoms/vc-icon/vc-icon.stories.ts +32 -0
  14. package/components/atoms/vc-icon/vc-icon.vue +36 -0
  15. package/components/atoms/vc-image/vc-image.stories.ts +40 -0
  16. package/components/atoms/vc-image/vc-image.vue +122 -0
  17. package/components/atoms/vc-info-row/vc-info-row.vue +42 -0
  18. package/components/atoms/vc-label/vc-label.stories.ts +23 -0
  19. package/components/atoms/vc-label/vc-label.vue +49 -0
  20. package/components/atoms/vc-link/vc-link.stories.ts +30 -0
  21. package/components/atoms/vc-link/vc-link.vue +46 -0
  22. package/components/atoms/vc-loading/vc-loading.vue +30 -0
  23. package/components/atoms/vc-progress/vc-progress.stories.ts +25 -0
  24. package/components/atoms/vc-progress/vc-progress.vue +65 -0
  25. package/components/atoms/vc-row/vc-row.vue +13 -0
  26. package/components/atoms/vc-status/vc-status.stories.ts +26 -0
  27. package/components/atoms/vc-status/vc-status.vue +78 -0
  28. package/components/atoms/vc-status-icon/vc-status-icon.vue +21 -0
  29. package/components/atoms/vc-switch/vc-switch.stories.ts +27 -0
  30. package/components/atoms/vc-switch/vc-switch.vue +100 -0
  31. package/components/atoms/vc-widget/vc-widget.vue +85 -0
  32. package/components/index.ts +43 -0
  33. package/components/molecules/vc-breadcrumbs/_internal/vc-breadcrumbs-item/vc-breadcrumbs-item.vue +103 -0
  34. package/components/molecules/vc-breadcrumbs/vc-breadcrumbs.stories.ts +39 -0
  35. package/components/molecules/vc-breadcrumbs/vc-breadcrumbs.vue +21 -0
  36. package/components/molecules/vc-editor/vc-editor.vue +117 -0
  37. package/components/molecules/vc-file-upload/vc-file-upload.vue +134 -0
  38. package/components/molecules/vc-form/vc-form.stories.ts +23 -0
  39. package/components/molecules/vc-form/vc-form.vue +5 -0
  40. package/components/molecules/vc-input/vc-input.stories.ts +26 -0
  41. package/components/molecules/vc-input/vc-input.vue +443 -0
  42. package/components/molecules/vc-multivalue/vc-multivalue.vue +447 -0
  43. package/components/molecules/vc-notification/vc-notification.vue +101 -0
  44. package/components/molecules/vc-pagination/vc-pagination.stories.ts +23 -0
  45. package/components/molecules/vc-pagination/vc-pagination.vue +169 -0
  46. package/components/molecules/vc-rating/vc-rating.stories.ts +23 -0
  47. package/components/molecules/vc-rating/vc-rating.vue +77 -0
  48. package/components/molecules/vc-select/vc-select.stories.ts +25 -0
  49. package/components/molecules/vc-select/vc-select.vue +402 -0
  50. package/components/molecules/vc-slider/vc-slider.vue +106 -0
  51. package/components/molecules/vc-textarea/vc-textarea.stories.ts +23 -0
  52. package/components/molecules/vc-textarea/vc-textarea.vue +155 -0
  53. package/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue +146 -0
  54. package/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue +148 -0
  55. package/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue +157 -0
  56. package/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue +110 -0
  57. package/components/organisms/vc-app/vc-app.stories.ts +75 -0
  58. package/components/organisms/vc-app/vc-app.vue +171 -0
  59. package/components/organisms/vc-blade/_internal/vc-blade-header/vc-blade-header.vue +126 -0
  60. package/components/organisms/vc-blade/_internal/vc-blade-toolbar/_internal/vc-blade-toolbar-button/vc-blade-toolbar-button.vue +223 -0
  61. package/components/organisms/vc-blade/_internal/vc-blade-toolbar/vc-blade-toolbar.vue +67 -0
  62. package/components/organisms/vc-blade/vc-blade.stories.ts +46 -0
  63. package/components/organisms/vc-blade/vc-blade.vue +87 -0
  64. package/components/organisms/vc-dynamic-property/vc-dynamic-property.vue +292 -0
  65. package/components/organisms/vc-gallery/_internal/vc-gallery-item/vc-gallery-item.vue +123 -0
  66. package/components/organisms/vc-gallery/_internal/vc-gallery-preview/vc-gallery-preview.vue +93 -0
  67. package/components/organisms/vc-gallery/vc-gallery.vue +186 -0
  68. package/components/organisms/vc-login-form/vc-login-form.stories.ts +55 -0
  69. package/components/organisms/vc-login-form/vc-login-form.vue +48 -0
  70. package/components/organisms/vc-popup/vc-popup.stories.ts +23 -0
  71. package/components/organisms/vc-popup/vc-popup.vue +97 -0
  72. package/components/organisms/vc-table/_internal/vc-table-cell/vc-table-cell.vue +113 -0
  73. package/components/organisms/vc-table/_internal/vc-table-counter/vc-table-counter.vue +29 -0
  74. package/components/organisms/vc-table/_internal/vc-table-filter/vc-table-filter.vue +152 -0
  75. package/components/organisms/vc-table/_internal/vc-table-mobile-item/vc-table-mobile-item.vue +272 -0
  76. package/components/organisms/vc-table/vc-table.stories.ts +99 -0
  77. package/components/organisms/vc-table/vc-table.vue +638 -0
  78. package/core/api/index.ts +1 -0
  79. package/core/api/platform.ts +8332 -0
  80. package/core/composables/index.ts +8 -0
  81. package/core/composables/useAutosave/index.ts +57 -0
  82. package/core/composables/useFunctions/debounce.ts +18 -0
  83. package/core/composables/useFunctions/delay.ts +7 -0
  84. package/core/composables/useFunctions/index.ts +21 -0
  85. package/core/composables/useFunctions/once.ts +14 -0
  86. package/core/composables/useFunctions/sleep.ts +4 -0
  87. package/core/composables/useFunctions/throttle.ts +17 -0
  88. package/core/composables/useI18n/index.ts +28 -0
  89. package/core/composables/useLogger/index.ts +24 -0
  90. package/core/composables/useNotifications/index.ts +116 -0
  91. package/core/composables/usePermissions/index.ts +32 -0
  92. package/core/composables/useSettings/index.ts +36 -0
  93. package/core/composables/useUser/index.ts +266 -0
  94. package/core/directives/autofocus/index.ts +9 -0
  95. package/core/directives/click-outside/index.ts +21 -0
  96. package/core/directives/index.ts +4 -0
  97. package/core/directives/loading/index.ts +28 -0
  98. package/core/directives/permissions/index.ts +20 -0
  99. package/core/plugins/index.ts +1 -0
  100. package/core/plugins/validation/index.ts +2 -0
  101. package/core/plugins/validation/rules.ts +196 -0
  102. package/core/types/index.ts +92 -0
  103. package/core/utilities/camelToSnake.ts +7 -0
  104. package/core/utilities/index.ts +1 -0
  105. package/dist/core/composables/useNotifications/index.d.ts +1 -1
  106. package/dist/core/composables/useNotifications/index.d.ts.map +1 -1
  107. package/dist/core/composables/useUser/index.d.ts +2 -2
  108. package/dist/core/composables/useUser/index.d.ts.map +1 -1
  109. package/dist/core/plugins/validation/index.d.ts.map +1 -1
  110. package/dist/core/types/index.d.ts +1 -1
  111. package/dist/core/types/index.d.ts.map +1 -1
  112. package/dist/framework.js +70 -97
  113. package/dist/framework.js.map +1 -1
  114. package/dist/shared/app-switcher/composables/useAppSwitcher/index.d.ts +1 -1
  115. package/dist/shared/app-switcher/composables/useAppSwitcher/index.d.ts.map +1 -1
  116. package/dist/shared/app-switcher/index.d.ts +2 -2
  117. package/dist/shared/app-switcher/index.d.ts.map +1 -1
  118. package/dist/shared/blade-navigation/composables/useBladeNavigation/index.d.ts +1 -1
  119. package/dist/shared/blade-navigation/composables/useBladeNavigation/index.d.ts.map +1 -1
  120. package/dist/shared/blade-navigation/types/index.d.ts +1 -1
  121. package/dist/shared/blade-navigation/types/index.d.ts.map +1 -1
  122. package/dist/style.css +1 -1
  123. package/dist/tsconfig.tsbuildinfo +1 -0
  124. package/dist/vite.config.d.ts.map +1 -1
  125. package/package.json +11 -8
  126. package/shared/app-switcher/components/index.ts +1 -0
  127. package/shared/app-switcher/components/vc-app-switcher/vc-app-switcher.vue +90 -0
  128. package/shared/app-switcher/composables/index.ts +1 -0
  129. package/shared/app-switcher/composables/useAppSwitcher/index.ts +54 -0
  130. package/shared/app-switcher/index.ts +14 -0
  131. package/shared/assets/components/assets-details/assets-details.vue +138 -0
  132. package/shared/assets/components/index.ts +1 -0
  133. package/shared/assets/index.ts +19 -0
  134. package/shared/assets/locales/en.json +29 -0
  135. package/shared/assets/locales/index.ts +2 -0
  136. package/shared/blade-navigation/components/index.ts +1 -0
  137. package/shared/blade-navigation/components/vc-blade-navigation/vc-blade-navigation.vue +84 -0
  138. package/shared/blade-navigation/composables/index.ts +1 -0
  139. package/shared/blade-navigation/composables/useBladeNavigation/index.ts +216 -0
  140. package/shared/blade-navigation/index.ts +15 -0
  141. package/shared/blade-navigation/types/index.ts +52 -0
  142. package/shared/index.ts +16 -0
@@ -0,0 +1,8 @@
1
+ export { default as useFunctions } from "./useFunctions";
2
+ export { default as useI18n } from "./useI18n";
3
+ export { default as useLogger } from "./useLogger";
4
+ export { default as useUser } from "./useUser";
5
+ export { default as useNotifications } from "./useNotifications";
6
+ export { default as useSettings } from "./useSettings";
7
+ export { default as usePermissions } from "./usePermissions";
8
+ export { default as useAutosave } from "./useAutosave";
@@ -0,0 +1,57 @@
1
+ import { computed, isRef, Ref, ref, unref, watch } from "vue";
2
+
3
+ interface IUseAutosave {
4
+ savedValue: Ref<Record<string, unknown>>;
5
+ loadAutosaved: () => void;
6
+ resetAutosaved: () => void;
7
+ }
8
+
9
+ export default (data, modified, defaultName: string): IUseAutosave => {
10
+ const rawValue = computed(() => (isRef(data) ? unref(data) : data));
11
+ const isModified = computed(() =>
12
+ isRef(modified) ? unref(modified) : modified
13
+ );
14
+ const savedValue = ref();
15
+
16
+ const isSaving = ref(false);
17
+
18
+ watch(isModified, () => {
19
+ if (isModified.value) {
20
+ isSaving.value = true;
21
+ } else {
22
+ isSaving.value = false;
23
+ resetAutosaved();
24
+ }
25
+ });
26
+
27
+ watch(
28
+ [rawValue, isSaving],
29
+ () => {
30
+ if (isSaving.value) {
31
+ saveToStorage();
32
+ }
33
+ },
34
+ { deep: true }
35
+ );
36
+
37
+ function saveToStorage() {
38
+ localStorage.setItem(defaultName, JSON.stringify(rawValue.value));
39
+ }
40
+
41
+ function loadAutosaved() {
42
+ const savedData = JSON.parse(localStorage.getItem(defaultName));
43
+ if (savedData) {
44
+ savedValue.value = savedData;
45
+ }
46
+ }
47
+
48
+ function resetAutosaved() {
49
+ localStorage.removeItem(defaultName);
50
+ }
51
+
52
+ return {
53
+ savedValue: computed(() => savedValue.value),
54
+ loadAutosaved,
55
+ resetAutosaved,
56
+ };
57
+ };
@@ -0,0 +1,18 @@
1
+ export default function debounce(
2
+ func: (...args: unknown[]) => void,
3
+ delay: number
4
+ ): (...args: unknown[]) => void {
5
+ console.debug(`[@vc-shell/framework#useFunctions:debounce] - Entry point`);
6
+ let timer: number | null = null;
7
+
8
+ return function (...args: unknown[]): void {
9
+ if (timer) {
10
+ clearTimeout(timer);
11
+ }
12
+
13
+ timer = window.setTimeout(() => {
14
+ timer = null;
15
+ func(...args);
16
+ }, delay);
17
+ };
18
+ }
@@ -0,0 +1,7 @@
1
+ export default function delay(
2
+ func: (...args: unknown[]) => void,
3
+ delay = 0
4
+ ): void {
5
+ console.debug(`[@vc-shell/framework#useFunctions:delay] - Entry point`);
6
+ setTimeout(func, delay);
7
+ }
@@ -0,0 +1,21 @@
1
+ import debounce from "./debounce";
2
+ import delay from "./delay";
3
+ import once from "./once";
4
+ import throttle from "./throttle";
5
+
6
+ interface IUseFunctions {
7
+ debounce: typeof debounce;
8
+ delay: typeof delay;
9
+ once: typeof once;
10
+ throttle: typeof throttle;
11
+ }
12
+
13
+ export default function useFunctions(): IUseFunctions {
14
+ console.debug("useFunctions entry point");
15
+ return {
16
+ debounce,
17
+ delay,
18
+ once,
19
+ throttle,
20
+ };
21
+ }
@@ -0,0 +1,14 @@
1
+ const resultsMap = new WeakMap();
2
+
3
+ export default function once(
4
+ func: (...args: unknown[]) => void
5
+ ): (...args: unknown[]) => unknown {
6
+ console.debug(`[@vc-shell/framework#useFunctions:once] - Entry point`);
7
+ return function (...args: unknown[]): unknown {
8
+ if (!resultsMap.has(func)) {
9
+ const result = func(...args);
10
+ resultsMap.set(func, result);
11
+ }
12
+ return resultsMap.get(func);
13
+ };
14
+ }
@@ -0,0 +1,4 @@
1
+ export default function sleep(ms: number): Promise<void> {
2
+ console.debug(`[@vc-shell/framework#useFunctions:sleep] - Entry point`);
3
+ return new Promise((resolve) => setTimeout(resolve, ms));
4
+ }
@@ -0,0 +1,17 @@
1
+ export default function throttle(
2
+ func: (...args: unknown[]) => void,
3
+ delay: number
4
+ ): (...args: unknown[]) => void {
5
+ console.debug(`[@vc-shell/framework#useFunctions:throttle] - Entry point`);
6
+ let wasThrottled = false;
7
+
8
+ return function (...args: unknown[]): void {
9
+ if (!wasThrottled) {
10
+ func(...args);
11
+ wasThrottled = true;
12
+ setTimeout(() => {
13
+ wasThrottled = false;
14
+ }, delay);
15
+ }
16
+ };
17
+ }
@@ -0,0 +1,28 @@
1
+ import { App } from "vue";
2
+ import {Composer, createI18n, useI18n as VueUseI18n, VueMessageType} from "vue-i18n";
3
+
4
+ export function init(app: App): App {
5
+ console.debug(`[@vc-shell/framework#useI18n:init] - Entry point`);
6
+ const i18nPlugin = createI18n({
7
+ legacy: false,
8
+ globalInjection: true,
9
+ locale: "en",
10
+ fallbackLocale: "en",
11
+ });
12
+ app.use(i18nPlugin);
13
+
14
+ app.config.globalProperties.$mergeLocaleMessage =
15
+ i18nPlugin.global.mergeLocaleMessage;
16
+
17
+ return app;
18
+ }
19
+
20
+ export default function useI18n(): Composer<
21
+ unknown,
22
+ unknown,
23
+ unknown,
24
+ {}
25
+ > {
26
+ console.debug(`[@vc-shell/framework#useI18n] - Entry point`);
27
+ return VueUseI18n({ useScope: "global" });
28
+ }
@@ -0,0 +1,24 @@
1
+ import { App } from "vue";
2
+ import {
3
+ createLogger,
4
+ LogLevel,
5
+ useLogger as useVueLogger,
6
+ VueLogger,
7
+ } from "vue-logger-plugin";
8
+
9
+ export function init(app: App): App {
10
+ console.debug(`[@vc-shell/framework#useLogger:init] - Entry point`);
11
+ app.use(
12
+ createLogger({
13
+ enabled: import.meta.env.APP_LOG_ENABLED === "true",
14
+ level: (import.meta.env.APP_LOG_LEVEL ?? "debug") as LogLevel,
15
+ })
16
+ );
17
+
18
+ return app;
19
+ }
20
+
21
+ export default function useLogger(): VueLogger {
22
+ console.debug(`[@vc-shell/framework#useLogger] - Entry point`);
23
+ return useVueLogger();
24
+ }
@@ -0,0 +1,116 @@
1
+ import { PushNotification, PushNotificationClient } from "@/core/api";
2
+ import useUser from "../useUser";
3
+ import { computed, ComputedRef, readonly, ref } from "vue";
4
+ import useLogger from "../useLogger";
5
+ import { orderBy, remove } from "lodash-es";
6
+
7
+ const notificationsClient = new PushNotificationClient();
8
+
9
+ interface INotifications {
10
+ readonly notifications: ComputedRef<Readonly<PushNotification[]>>;
11
+ readonly popupNotifications: ComputedRef<Readonly<PushNotification[]>>;
12
+ loadFromHistory(take?: number): void;
13
+ addNotification(message: PushNotification): void;
14
+ markAsRead(message: PushNotification): void;
15
+ dismiss(message: PushNotification): void;
16
+ dismissAll(): void;
17
+ markAllAsRead(): void;
18
+ }
19
+
20
+ const notifications = ref<PushNotification[]>([]);
21
+ const popupNotifications = ref<PushNotification[]>([]);
22
+
23
+ export default (): INotifications => {
24
+ const { getAccessToken } = useUser();
25
+ const logger = useLogger();
26
+
27
+ async function loadFromHistory(take = 10) {
28
+ const token = await getAccessToken();
29
+ if (token) {
30
+ // TODO temporary workaround to get push notifications without base type
31
+ try {
32
+ notificationsClient.setAuthToken(token);
33
+ const result = await fetch("/api/platform/pushnotifications", {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "application/json-patch+json",
37
+ Accept: "application/json",
38
+ authorization: `Bearer ${token}`,
39
+ },
40
+ body: JSON.stringify({ take }),
41
+ });
42
+
43
+ result.text().then((response) => {
44
+ notifications.value =
45
+ <PushNotification[]>JSON.parse(response).notifyEvents ?? [];
46
+ });
47
+ } catch (e) {
48
+ logger.error(e);
49
+ throw e;
50
+ }
51
+ }
52
+ }
53
+
54
+ function addNotification(message: PushNotification) {
55
+ if (message.notifyType !== "IndexProgressPushNotification") {
56
+ const existsNotification = notifications.value.find(
57
+ (x) => x.id == message.id
58
+ );
59
+
60
+ if (existsNotification) {
61
+ message.isNew = existsNotification.isNew;
62
+ Object.assign(existsNotification, message);
63
+ } else {
64
+ popupNotifications.value.unshift(message);
65
+ notifications.value.unshift(message);
66
+ }
67
+ }
68
+ }
69
+
70
+ function markAsRead(message: PushNotification) {
71
+ message.isNew = false;
72
+ remove(popupNotifications.value, (x) => x.id == message.id);
73
+ }
74
+
75
+ function dismiss(message: PushNotification) {
76
+ remove(popupNotifications.value, (x) => x.id == message.id);
77
+ remove(notifications.value, (x) => x.id == message.id);
78
+ }
79
+
80
+ function dismissAll() {
81
+ popupNotifications.value = [];
82
+ notifications.value = [];
83
+ }
84
+
85
+ async function markAllAsRead() {
86
+ const token = await getAccessToken();
87
+ if (token) {
88
+ notificationsClient.setAuthToken(token);
89
+ try {
90
+ await notificationsClient.markAllAsRead();
91
+ notifications.value = notifications.value.map((x) => {
92
+ if (x.isNew) {
93
+ x.isNew = false;
94
+ }
95
+ return x;
96
+ });
97
+ } catch (e) {
98
+ logger.error(e);
99
+ throw e;
100
+ }
101
+ }
102
+ }
103
+
104
+ return {
105
+ notifications: computed(() =>
106
+ readonly(orderBy(notifications.value, ["created"], ["desc"]))
107
+ ),
108
+ popupNotifications: computed(() => readonly(popupNotifications.value)),
109
+ loadFromHistory,
110
+ addNotification,
111
+ dismissAll,
112
+ dismiss,
113
+ markAsRead,
114
+ markAllAsRead,
115
+ };
116
+ };
@@ -0,0 +1,32 @@
1
+ import {useUser} from "@/core/composables";
2
+ import { computed, ComputedRef } from "vue";
3
+
4
+ interface IUsePermissions {
5
+ readonly userPermissions: ComputedRef<string[]>;
6
+ checkPermission(permissions: string | string[]): boolean;
7
+ }
8
+
9
+ export default (): IUsePermissions => {
10
+ const { user } = useUser();
11
+
12
+ function checkPermission(permissions: string | string[] | undefined) {
13
+ if (!permissions) {
14
+ return true;
15
+ } else if (permissions || (permissions && permissions instanceof Array)) {
16
+ if (typeof permissions === "string") {
17
+ return user.value?.permissions.includes(permissions);
18
+ } else if (permissions.length > 0) {
19
+ return user.value?.permissions.some((role) => {
20
+ return permissions.includes(role);
21
+ });
22
+ }
23
+ } else {
24
+ console.error("Permissions must be a string or strings array");
25
+ }
26
+ }
27
+
28
+ return {
29
+ userPermissions: computed(() => user.value?.permissions),
30
+ checkPermission,
31
+ };
32
+ };
@@ -0,0 +1,36 @@
1
+ import {useUser, useLogger} from "@/core/composables";
2
+ import { computed, Ref, ref } from "vue";
3
+ import { SettingClient } from "@/core/api";
4
+
5
+ interface IUseSettings {
6
+ readonly uiSettings: Ref<Record<string, string>>;
7
+ getUiCustomizationSettings: () => void;
8
+ }
9
+
10
+ const uiSettings = ref<Record<string, string>>();
11
+ export default (): IUseSettings => {
12
+ const { getAccessToken } = useUser();
13
+ const logger = useLogger();
14
+
15
+ async function getApiClient() {
16
+ const client = new SettingClient();
17
+ client.setAuthToken(await getAccessToken());
18
+ return client;
19
+ }
20
+
21
+ async function getUiCustomizationSettings() {
22
+ const client = await getApiClient();
23
+
24
+ try {
25
+ const result = await client.getUICustomizationSetting();
26
+ uiSettings.value = JSON.parse(result.defaultValue);
27
+ } catch (e) {
28
+ logger.error(e);
29
+ }
30
+ }
31
+
32
+ return {
33
+ uiSettings: computed(() => uiSettings.value),
34
+ getUiCustomizationSettings,
35
+ };
36
+ };
@@ -0,0 +1,266 @@
1
+ import { computed, Ref, ref, ComputedRef } from "vue";
2
+ import ClientOAuth2 from "client-oauth2";
3
+ import {
4
+ UserDetail,
5
+ SecurityClient,
6
+ ResetPasswordConfirmRequest,
7
+ SecurityResult,
8
+ ValidatePasswordResetTokenRequest,
9
+ IdentityResult,
10
+ } from "@/core/api";
11
+ import { AuthData, RequestPasswordResult, SignInResults } from "@/core/types";
12
+ //The Platform Manager uses the same key to store authorization data in the
13
+ //local storage, so we can exchange authorization data between the Platform Manager
14
+ //and the user application that is hosted in the same domain as the sub application.
15
+ const VC_AUTH_DATA_KEY = "ls.authenticationData";
16
+
17
+ const user: Ref<UserDetail> = ref();
18
+ const loading: Ref<boolean> = ref(false);
19
+ const authData: Ref<AuthData> = ref();
20
+ const authClient = new ClientOAuth2({
21
+ accessTokenUri: `/connect/token`,
22
+ scopes: ["offline_access"],
23
+ });
24
+ const securityClient = new SecurityClient();
25
+
26
+ interface IUseUser {
27
+ user: ComputedRef<UserDetail | null>;
28
+ loading: ComputedRef<boolean>;
29
+ getAccessToken: () => Promise<string | null>;
30
+ loadUser: () => Promise<UserDetail>;
31
+ signIn: (username: string, password: string) => Promise<SignInResults>;
32
+ signOut: () => Promise<void>;
33
+ validateToken: (userId: string, token: string) => Promise<boolean>;
34
+ validatePassword: (password: string) => Promise<IdentityResult>;
35
+ resetPasswordByToken: (
36
+ userId: string,
37
+ password: string,
38
+ token: string
39
+ ) => Promise<SecurityResult>;
40
+ requestPasswordReset: (
41
+ loginOrEmail: string
42
+ ) => Promise<RequestPasswordResult>;
43
+ changeUserPassword: (
44
+ oldPassword: string,
45
+ newPassword: string
46
+ ) => Promise<SecurityResult>;
47
+ }
48
+
49
+ export default (): IUseUser => {
50
+ async function validateToken(
51
+ userId: string,
52
+ token: string
53
+ ): Promise<boolean> {
54
+ let result = false;
55
+ try {
56
+ loading.value = true;
57
+ result = await securityClient.validatePasswordResetToken(userId, {
58
+ token,
59
+ } as ValidatePasswordResetTokenRequest);
60
+ } catch (e) {
61
+ //TODO: log error
62
+ } finally {
63
+ loading.value = false;
64
+ }
65
+ return result;
66
+ }
67
+
68
+ async function validatePassword(password: string): Promise<IdentityResult> {
69
+ return securityClient.validatePassword(password);
70
+ }
71
+
72
+ async function resetPasswordByToken(
73
+ userId: string,
74
+ password: string,
75
+ token: string
76
+ ): Promise<SecurityResult> {
77
+ return securityClient.resetPasswordByToken(userId, {
78
+ newPassword: password,
79
+ token,
80
+ } as ResetPasswordConfirmRequest);
81
+ }
82
+
83
+ async function signIn(
84
+ username: string,
85
+ password: string
86
+ ): Promise<SignInResults> {
87
+ console.debug(`[@vc-shell/framework#useUser:signIn] - Entry point`);
88
+ let token = undefined;
89
+ try {
90
+ loading.value = true;
91
+ token = await authClient.owner.getToken(username, password);
92
+ } catch (e) {
93
+ //TODO: log error
94
+ return { succeeded: false, error: e as string };
95
+ } finally {
96
+ loading.value = false;
97
+ }
98
+
99
+ if (token) {
100
+ authData.value = {
101
+ accessToken: token.accessToken,
102
+ refreshToken: token.refreshToken,
103
+ expiresAt: addOffsetToCurrentDate(Number(token.data["expires_in"])),
104
+ userName: username,
105
+ };
106
+ console.log(
107
+ "[userUsers]: an access token has been obtained successfully",
108
+ authData.value
109
+ );
110
+
111
+ storeAuthData(authData.value);
112
+ await loadUser();
113
+ }
114
+ return { succeeded: true };
115
+ }
116
+
117
+ async function signOut(): Promise<void> {
118
+ console.debug(`[@vc-shell/framework#useUser:signOut] - Entry point`);
119
+ user.value = undefined;
120
+ authData.value = undefined;
121
+ storeAuthData({});
122
+ //todo
123
+ }
124
+
125
+ async function loadUser(): Promise<UserDetail> {
126
+ console.debug(`[@vc-shell/framework#useUser:loadUser] - Entry point`);
127
+ const token = await getAccessToken();
128
+ if (token) {
129
+ securityClient.setAuthToken(token);
130
+ try {
131
+ loading.value = true;
132
+ user.value = await securityClient.getCurrentUser();
133
+ console.log("[userUsers]: an user details has been loaded", user.value);
134
+ } catch (e) {
135
+ console.dir(e);
136
+ //TODO: log error
137
+ } finally {
138
+ loading.value = false;
139
+ }
140
+ }
141
+ return { ...user.value } as UserDetail;
142
+ }
143
+
144
+ async function getAccessToken(): Promise<string | null> {
145
+ console.debug(`[@vc-shell/framework#useUser:getAccessToken] - Entry point`);
146
+ if (!authData.value) {
147
+ authData.value = readAuthData();
148
+ }
149
+
150
+ if (authData.value && Date.now() >= (authData.value.expiresAt ?? 0)) {
151
+ const token = authClient.createToken(
152
+ authData.value.accessToken ?? authData.value.token ?? "",
153
+ authData.value.refreshToken ?? "",
154
+ {}
155
+ );
156
+ console.log(
157
+ "[userUsers]: an access token is expired, using refresh token to receive a new"
158
+ );
159
+ try {
160
+ const newToken = await token.refresh();
161
+ if (newToken) {
162
+ authData.value = {
163
+ ...authData.value,
164
+ accessToken: newToken.accessToken,
165
+ token: newToken.accessToken,
166
+ refreshToken: newToken.refreshToken,
167
+ expiresAt: addOffsetToCurrentDate(
168
+ Number(newToken.data["expires_in"])
169
+ ),
170
+ };
171
+ storeAuthData(authData.value);
172
+ }
173
+ } catch (e) {
174
+ console.log("[userUsers]: getAccessToken() returns error", e);
175
+ }
176
+ }
177
+
178
+ return authData.value?.accessToken ?? authData.value?.token;
179
+ }
180
+
181
+ function storeAuthData(authData: AuthData) {
182
+ localStorage.setItem(
183
+ VC_AUTH_DATA_KEY,
184
+ JSON.stringify({ ...(authData || {}) })
185
+ );
186
+ }
187
+ function readAuthData(): AuthData {
188
+ return JSON.parse(
189
+ localStorage.getItem(VC_AUTH_DATA_KEY) || "{}"
190
+ ) as AuthData;
191
+ }
192
+
193
+ function addOffsetToCurrentDate(offsetInSeconds: number): number {
194
+ return Date.now() + offsetInSeconds * 1000;
195
+ }
196
+
197
+ async function requestPasswordReset(
198
+ loginOrEmail: string
199
+ ): Promise<RequestPasswordResult> {
200
+ try {
201
+ loading.value = true;
202
+ await securityClient.requestPasswordReset(loginOrEmail);
203
+ return { succeeded: true };
204
+ } catch (e) {
205
+ //TODO: log error
206
+ return { succeeded: false, error: e as string };
207
+ } finally {
208
+ loading.value = false;
209
+ }
210
+ }
211
+
212
+ async function changeUserPassword(
213
+ oldPassword: string,
214
+ newPassword: string
215
+ ): Promise<SecurityResult> {
216
+ const token = await getAccessToken();
217
+ let result;
218
+
219
+ // TODO it's temporary workaround to get valid errors
220
+ if (token) {
221
+ try {
222
+ loading.value = true;
223
+ const res = await fetch(
224
+ "/api/platform/security/currentuser/changepassword",
225
+ {
226
+ method: "POST",
227
+ headers: {
228
+ "Content-Type": "application/json-patch+json",
229
+ Accept: "text/plain",
230
+ authorization: `Bearer ${token}`,
231
+ },
232
+ body: JSON.stringify({
233
+ oldPassword,
234
+ newPassword,
235
+ }),
236
+ }
237
+ );
238
+ if (res.status !== 500) {
239
+ result = await res.text().then((response) => {
240
+ return JSON.parse(response);
241
+ });
242
+ }
243
+ } catch (e) {
244
+ return { succeeded: false, errors: [e.message] } as SecurityResult;
245
+ } finally {
246
+ loading.value = false;
247
+ }
248
+ }
249
+
250
+ return result as SecurityResult;
251
+ }
252
+
253
+ return {
254
+ user: computed(() => user.value),
255
+ loading: computed(() => loading.value),
256
+ getAccessToken,
257
+ loadUser,
258
+ signIn,
259
+ signOut,
260
+ validateToken,
261
+ validatePassword,
262
+ resetPasswordByToken,
263
+ requestPasswordReset,
264
+ changeUserPassword,
265
+ };
266
+ };
@@ -0,0 +1,9 @@
1
+ import { Directive, DirectiveBinding } from "vue";
2
+
3
+ export default {
4
+ mounted(el: HTMLElement, binding: DirectiveBinding): void {
5
+ if (binding.value) {
6
+ el.focus();
7
+ }
8
+ },
9
+ } as Directive;
@@ -0,0 +1,21 @@
1
+ import { Directive, DirectiveBinding } from "vue";
2
+
3
+ interface OutsideClickableHTMLElement extends HTMLElement {
4
+ _outsideClickHandler: (event: MouseEvent | TouchEvent) => void;
5
+ }
6
+
7
+ export default {
8
+ mounted(el: OutsideClickableHTMLElement, binding: DirectiveBinding): void {
9
+ const closeHandler = binding.value;
10
+ el._outsideClickHandler = function (event: MouseEvent | TouchEvent) {
11
+ if (!el.contains(event.target as Node)) {
12
+ event.stopPropagation();
13
+ closeHandler();
14
+ }
15
+ };
16
+ document.addEventListener("click", el._outsideClickHandler);
17
+ },
18
+ unmounted(el: OutsideClickableHTMLElement): void {
19
+ document.removeEventListener("click", el._outsideClickHandler);
20
+ },
21
+ } as Directive;