@vc-shell/framework 1.1.25 → 1.1.26

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 (81) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/core/composables/useAppInsights/index.ts +2 -2
  3. package/core/composables/useErrorHandler/index.ts +3 -2
  4. package/core/composables/useGlobalSearch/useGlobalSearch.md +263 -0
  5. package/core/composables/usePermissions/index.ts +2 -2
  6. package/core/composables/useTheme/index.ts +74 -26
  7. package/core/composables/useUser/index.ts +29 -8
  8. package/core/composables/useUserManagement/index.ts +45 -0
  9. package/core/interceptors/index.ts +2 -2
  10. package/core/plugins/signalR/index.ts +2 -2
  11. package/core/services/global-search-service/global-search-service.md +203 -0
  12. package/core/services/widget-service.ts +35 -12
  13. package/dist/core/composables/useErrorHandler/index.d.ts.map +1 -1
  14. package/dist/core/composables/useTheme/index.d.ts +14 -5
  15. package/dist/core/composables/useTheme/index.d.ts.map +1 -1
  16. package/dist/core/composables/useUser/index.d.ts +11 -3
  17. package/dist/core/composables/useUser/index.d.ts.map +1 -1
  18. package/dist/core/composables/useUserManagement/index.d.ts +23 -0
  19. package/dist/core/composables/useUserManagement/index.d.ts.map +1 -0
  20. package/dist/core/services/widget-service.d.ts +9 -7
  21. package/dist/core/services/widget-service.d.ts.map +1 -1
  22. package/dist/framework.js +94 -93
  23. package/dist/{index-DvenBxy6.js → index-20xYwcGS.js} +22032 -21961
  24. package/dist/{index-Br7ZwtRW.js → index-3B7GY2EI.js} +1 -1
  25. package/dist/{index-DAnceKLv.js → index-BGUwsXYM.js} +1 -1
  26. package/dist/{index-eOG-NNYN.js → index-BGghog2f.js} +1 -1
  27. package/dist/{index-Cxkjjuah.js → index-BQNK41p5.js} +1 -1
  28. package/dist/{index-CIzLBvgg.js → index-BeIJlP3x.js} +1 -1
  29. package/dist/{index-BnqqEJTE.js → index-BobFEOd-.js} +1 -1
  30. package/dist/{index-BYcoxn-f.js → index-CYbdhec2.js} +1 -1
  31. package/dist/{index-BbuBDu8A.js → index-DQczMBoO.js} +1 -1
  32. package/dist/{index-cuex9jil.js → index-DSNT0XVw.js} +1 -1
  33. package/dist/{index-DoArZBIw.js → index-DXHjWa3b.js} +1 -1
  34. package/dist/{index-Dk1K3-27.js → index-Dcf_1-Il.js} +1 -1
  35. package/dist/{index-CGL9e-cM.js → index-DszRvG1r.js} +1 -1
  36. package/dist/{index-CLAYu8Qj.js → index-DyPpTQJI.js} +1 -1
  37. package/dist/{index-Cmbxdwnl.js → index-Nr1LNRXa.js} +1 -1
  38. package/dist/{index-CRwMOCjN.js → index-RMOqRXFr.js} +1 -1
  39. package/dist/{index-BLmjssqE.js → index-Tsyx9GI7.js} +1 -1
  40. package/dist/index.css +1 -1
  41. package/dist/locales/de.json +6 -0
  42. package/dist/locales/en.json +6 -0
  43. package/dist/shared/components/change-password/change-password.vue.d.ts.map +1 -1
  44. package/dist/shared/components/notifications/components/notification-container/index.d.ts +1 -1
  45. package/dist/shared/components/notifications/components/notification-container/index.d.ts.map +1 -1
  46. package/dist/shared/components/settings-menu/settings-menu.vue.d.ts.map +1 -1
  47. package/dist/shared/components/settings-menu-item/settings-menu-item.vue.d.ts +6 -11
  48. package/dist/shared/components/settings-menu-item/settings-menu-item.vue.d.ts.map +1 -1
  49. package/dist/shared/components/sidebar/sidebar.vue.d.ts +3 -11
  50. package/dist/shared/components/sidebar/sidebar.vue.d.ts.map +1 -1
  51. package/dist/shared/components/theme-selector/theme-selector.vue.d.ts.map +1 -1
  52. package/dist/shared/modules/dynamic/pages/dynamic-blade-form.vue.d.ts.map +1 -1
  53. package/dist/shared/pages/InvitePage/components/invite/Invite.vue.d.ts.map +1 -1
  54. package/dist/shared/pages/LoginPage/components/login/Login.vue.d.ts.map +1 -1
  55. package/dist/shared/pages/ResetPasswordPage/components/reset-password/ResetPassword.vue.d.ts.map +1 -1
  56. package/dist/tsconfig.tsbuildinfo +1 -1
  57. package/dist/ui/components/atoms/vc-widget/vc-widget.vue.d.ts.map +1 -1
  58. package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts.map +1 -1
  59. package/dist/ui/components/organisms/vc-blade/_internal/vc-blade-widget-container/_internal/vc-widget-container-desktop.vue.d.ts.map +1 -1
  60. package/dist/ui/components/organisms/vc-blade/_internal/vc-blade-widget-container/_internal/vc-widget-container-mobile.vue.d.ts.map +1 -1
  61. package/dist/ui/components/organisms/vc-popup/vc-popup.vue.d.ts +1 -1
  62. package/dist/ui/components/organisms/vc-popup/vc-popup.vue.d.ts.map +1 -1
  63. package/package.json +4 -4
  64. package/shared/components/change-password/change-password.vue +2 -3
  65. package/shared/components/logout-button/logout-button.vue +2 -2
  66. package/shared/components/settings-menu/settings-menu.vue +1 -4
  67. package/shared/components/settings-menu-item/settings-menu-item.vue +9 -1
  68. package/shared/components/sidebar/sidebar.vue +6 -1
  69. package/shared/components/theme-selector/theme-selector.vue +11 -11
  70. package/shared/components/user-dropdown-button/_internal/user-info.vue +2 -2
  71. package/shared/modules/dynamic/pages/dynamic-blade-form.vue +1 -0
  72. package/shared/pages/InvitePage/components/invite/Invite.vue +217 -216
  73. package/shared/pages/LoginPage/components/login/Login.vue +3 -2
  74. package/shared/pages/ResetPasswordPage/components/reset-password/ResetPassword.vue +3 -2
  75. package/ui/components/atoms/vc-widget/vc-widget.vue +13 -6
  76. package/ui/components/molecules/vc-input-dropdown/vc-input-dropdown.stories.ts +0 -2
  77. package/ui/components/organisms/vc-app/vc-app.vue +4 -3
  78. package/ui/components/organisms/vc-blade/_internal/vc-blade-widget-container/_internal/vc-widget-container-desktop.vue +2 -1
  79. package/ui/components/organisms/vc-blade/_internal/vc-blade-widget-container/_internal/vc-widget-container-mobile.vue +2 -0
  80. package/ui/components/organisms/vc-popup/vc-popup.stories.ts +398 -0
  81. package/ui/components/organisms/vc-popup/vc-popup.vue +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [1.1.26](https://github.com/VirtoCommerce/vc-shell/compare/v1.1.25...v1.1.26) (2025-06-04)
2
+
3
+
4
+ ### Features
5
+
6
+ * **global-search:** add useGlobalSearch composable and Global Search Service documentation for enhanced search functionality across the application ([e53be10](https://github.com/VirtoCommerce/vc-shell/commit/e53be1095b2b7067dc3ad9ce9f1ab23aafaa57a2))
7
+ * **localization:** add theme localization support for German and English languages ([8d8fdb0](https://github.com/VirtoCommerce/vc-shell/commit/8d8fdb0a05814ea277f7ab07978902ae86ec3102))
8
+ * **vc-popup:** add Storybook stories for VcPopup component, showcasing various configurations and customization options ([dc8e961](https://github.com/VirtoCommerce/vc-shell/commit/dc8e961be32469b7a06fed78aa68846ba95a1d70))
9
+
10
+
11
+
1
12
  ## [1.1.25](https://github.com/VirtoCommerce/vc-shell/compare/v1.1.24...v1.1.25) (2025-05-30)
2
13
 
3
14
 
@@ -1,7 +1,7 @@
1
1
  import { inject } from "vue";
2
2
  import { AppInsightsPluginOptions, useAppInsights as useInsights } from "vue3-application-insights";
3
3
  import { generateW3CId } from "@microsoft/applicationinsights-core-js";
4
- import { useUser } from "./../useUser";
4
+ import { useUserManagement } from "./../useUserManagement";
5
5
  import { ApplicationInsights, Snippet } from "@microsoft/applicationinsights-web";
6
6
 
7
7
  export interface IUseAppInsights {
@@ -14,7 +14,7 @@ export interface IUseAppInsights {
14
14
 
15
15
  export function useAppInsights(): IUseAppInsights {
16
16
  const appInsights = useInsights();
17
- const { user } = useUser();
17
+ const { user } = useUserManagement();
18
18
  const appInsightsOptions = inject<AppInsightsPluginOptions>("appInsightsOptions");
19
19
 
20
20
  function setupPageTracking() {
@@ -1,5 +1,6 @@
1
1
  import { onErrorCaptured, getCurrentInstance, ref, Ref } from "vue";
2
- import { useAppInsights, useUser } from "..";
2
+ import { useAppInsights } from "..";
3
+ import { useUserManagement } from "../useUserManagement";
3
4
 
4
5
  interface IUseErrorHandler {
5
6
  error: Ref<string | null>;
@@ -10,7 +11,7 @@ export function useErrorHandler(capture?: boolean): IUseErrorHandler {
10
11
  const error = ref<string | null>(null);
11
12
  const instance = getCurrentInstance();
12
13
  const { appInsights } = useAppInsights();
13
- const { user } = useUser();
14
+ const { user } = useUserManagement();
14
15
 
15
16
  function reset() {
16
17
  error.value = null;
@@ -0,0 +1,263 @@
1
+ # useGlobalSearch Composable
2
+
3
+ The `useGlobalSearch` composable provides access to the Global Search Service, allowing components to manage search functionality across the application, particularly for blade-based interfaces.
4
+
5
+ ## Overview
6
+
7
+ The `useGlobalSearch` composable is a core utility for managing search visibility and query state in VC-Shell applications. It's particularly useful for controlling search UI in blade components and handling mobile-specific search behavior.
8
+
9
+ ## API Reference
10
+
11
+ ### Return Value
12
+
13
+ The `useGlobalSearch` composable returns the global search state object with the following properties and methods:
14
+
15
+ ```typescript
16
+ interface GlobalSearchState {
17
+ isSearchVisible: Ref<Record<string, boolean>>; // Tracks search visibility by blade ID
18
+ searchQuery: Ref<Record<string, string>>; // Stores search queries by blade ID
19
+ toggleSearch: (bladeId: string) => void; // Toggles search visibility for a blade
20
+ setSearchQuery: (bladeId: string, query: string) => void; // Sets the search query for a blade
21
+ closeSearch: (bladeId: string) => void; // Closes search for a specific blade
22
+ }
23
+ ```
24
+
25
+ ### Methods
26
+
27
+ #### toggleSearch
28
+
29
+ Toggles search visibility for a specific blade.
30
+
31
+ ```typescript
32
+ toggleSearch(bladeId: string): void
33
+ ```
34
+
35
+ - `bladeId`: The ID of the blade to toggle search visibility for
36
+
37
+ #### setSearchQuery
38
+
39
+ Sets the search query for a specific blade.
40
+
41
+ ```typescript
42
+ setSearchQuery(bladeId: string, query: string): void
43
+ ```
44
+
45
+ - `bladeId`: The ID of the blade
46
+ - `query`: The search query string
47
+
48
+ #### closeSearch
49
+
50
+ Explicitly closes search for a specific blade.
51
+
52
+ ```typescript
53
+ closeSearch(bladeId: string): void
54
+ ```
55
+
56
+ - `bladeId`: The ID of the blade to close search for
57
+
58
+ ### Properties
59
+
60
+ #### isSearchVisible
61
+
62
+ A reactive reference to an object that tracks search visibility state for each blade by ID.
63
+
64
+ ```typescript
65
+ isSearchVisible: Ref<Record<string, boolean>>
66
+ ```
67
+
68
+ #### searchQuery
69
+
70
+ A reactive reference to an object that stores search queries for each blade by ID.
71
+
72
+ ```typescript
73
+ searchQuery: Ref<Record<string, string>>
74
+ ```
75
+
76
+ ## Usage
77
+
78
+ ### Basic Usage in a Component
79
+
80
+ ```typescript
81
+ import { useGlobalSearch } from '@vc-shell/framework';
82
+ import { computed, inject } from 'vue';
83
+
84
+ export default {
85
+ setup() {
86
+ // Get the blade ID (typically injected in blade components)
87
+ const blade = inject('BladeInstance');
88
+ const bladeId = computed(() => blade.value?.id || 'fallback-id');
89
+
90
+ // Get global search functionality
91
+ const globalSearch = useGlobalSearch();
92
+
93
+ // Determine if search should be shown
94
+ const isSearchVisible = computed(() => {
95
+ return globalSearch.isSearchVisible.value[bladeId.value] || false;
96
+ });
97
+
98
+ // Function to toggle search
99
+ function toggleSearch() {
100
+ globalSearch.toggleSearch(bladeId.value);
101
+ }
102
+
103
+ // Function to update search query
104
+ function updateSearchQuery(query) {
105
+ globalSearch.setSearchQuery(bladeId.value, query);
106
+ }
107
+
108
+ return {
109
+ isSearchVisible,
110
+ toggleSearch,
111
+ updateSearchQuery
112
+ };
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### Table Component with Search
118
+
119
+ ```vue
120
+ <template>
121
+ <div class="data-table-component">
122
+ <!-- Search Input -->
123
+ <div v-if="shouldShowSearch" class="search-container">
124
+ <input
125
+ type="text"
126
+ v-model="searchInput"
127
+ placeholder="Search..."
128
+ @input="handleSearchInput"
129
+ />
130
+ </div>
131
+
132
+ <!-- Table Content -->
133
+ <table>
134
+ <!-- Table structure -->
135
+ </table>
136
+
137
+ <!-- Mobile Search Button -->
138
+ <button
139
+ v-if="isMobile"
140
+ class="mobile-search-button"
141
+ @click="toggleSearch"
142
+ >
143
+ Search
144
+ </button>
145
+ </div>
146
+ </template>
147
+
148
+ <script setup>
149
+ import { ref, computed, inject, watch } from 'vue';
150
+ import { useGlobalSearch } from '@vc-shell/framework';
151
+
152
+ // Get mobile state
153
+ const isMobile = inject('isMobile', ref(false));
154
+
155
+ // Get blade and global search
156
+ const blade = inject('BladeInstance');
157
+ const bladeId = computed(() => blade.value?.id || 'fallback-id');
158
+ const globalSearch = useGlobalSearch();
159
+
160
+ // Track search input
161
+ const searchInput = ref('');
162
+
163
+ // Determine if search should be visible
164
+ const shouldShowSearch = computed(() => {
165
+ if (!isMobile.value) return true;
166
+ return globalSearch.isSearchVisible.value[bladeId.value];
167
+ });
168
+
169
+ // Watch for changes in the global search query
170
+ watch(() => globalSearch.searchQuery.value[bladeId.value], (newQuery) => {
171
+ if (newQuery !== undefined) {
172
+ searchInput.value = newQuery;
173
+ }
174
+ });
175
+
176
+ // Toggle search visibility
177
+ function toggleSearch() {
178
+ globalSearch.toggleSearch(bladeId.value);
179
+ }
180
+
181
+ // Handle search input changes
182
+ function handleSearchInput() {
183
+ globalSearch.setSearchQuery(bladeId.value, searchInput.value);
184
+ // Additional search logic
185
+ }
186
+ </script>
187
+ ```
188
+
189
+ ### Mobile Search Button Implementation
190
+
191
+ ```typescript
192
+ import { useGlobalSearch } from '@vc-shell/framework';
193
+ import { useAppBarMobileButtons } from '@vc-shell/framework';
194
+ import { computed, onMounted, onBeforeUnmount } from 'vue';
195
+
196
+ export default {
197
+ setup() {
198
+ const bladeId = computed(() => 'your-blade-id');
199
+ const globalSearch = useGlobalSearch();
200
+ const { register: registerMobileButton, unregister: unregisterMobileButton } = useAppBarMobileButtons();
201
+
202
+ // Register a search button in the mobile app bar
203
+ onMounted(() => {
204
+ registerMobileButton({
205
+ id: "global-search",
206
+ icon: "search-icon",
207
+ onClick: () => {
208
+ globalSearch.toggleSearch(bladeId.value);
209
+ },
210
+ onClose: () => {
211
+ globalSearch.closeSearch(bladeId.value);
212
+ },
213
+ isVisible: computed(() => true), // Button visibility logic
214
+ order: 5, // Order in the mobile button list
215
+ });
216
+ });
217
+
218
+ // Cleanup when component is unmounted
219
+ onBeforeUnmount(() => {
220
+ if (globalSearch.isSearchVisible.value[bladeId.value]) {
221
+ globalSearch.closeSearch(bladeId.value);
222
+ }
223
+ unregisterMobileButton("global-search");
224
+ });
225
+ }
226
+ }
227
+ ```
228
+
229
+ ## Provider Function
230
+
231
+ In addition to the `useGlobalSearch` composable, there's a provider function to initialize the global search service in your application:
232
+
233
+ ```typescript
234
+ import { provideGlobalSearch } from '@vc-shell/framework';
235
+
236
+ export default {
237
+ setup() {
238
+ // Create and provide the global search service to the component tree
239
+ const globalSearchService = provideGlobalSearch();
240
+
241
+ // Now child components can use useGlobalSearch()
242
+
243
+ return {};
244
+ }
245
+ }
246
+ ```
247
+
248
+ ## Best Practices
249
+
250
+ 1. **Blade-Specific Search**: Always associate search state with specific blade IDs to ensure each blade can have independent search visibility.
251
+
252
+ 2. **Mobile Considerations**: Implement different search behavior for mobile versus desktop views, using the isMobile flag.
253
+
254
+ 3. **Cleanup**: Always close search and unregister any associated UI elements when components unmount.
255
+
256
+ 4. **Search Toggling**: Use the toggleSearch method for buttons that need to both open and close search, rather than implementing toggle logic yourself.
257
+
258
+ 5. **Default Visibility**: In desktop views, typically keep search visible by default, only using toggle behavior on mobile.
259
+
260
+ ## Related Resources
261
+
262
+ - [Global Search Service](../../services/global-search-service/global-search-service.md) - Documentation for the underlying service
263
+ - [Blade Navigation](../navigation.md) - Information about the blade navigation system
@@ -1,12 +1,12 @@
1
1
  import { Ref, ref } from "vue";
2
- import { useUser } from "./../useUser";
2
+ import { useUserManagement } from "./../useUserManagement";
3
3
 
4
4
  interface IUsePermissions {
5
5
  hasAccess(permissions: string | string[] | undefined): boolean;
6
6
  }
7
7
  const userPermissions: Ref<string[]> = ref([]);
8
8
  export function usePermissions(): IUsePermissions {
9
- const { user } = useUser();
9
+ const { user } = useUserManagement();
10
10
 
11
11
  if (user.value) {
12
12
  userPermissions.value = user.value?.permissions ?? [];
@@ -1,47 +1,67 @@
1
1
  import { BasicColorSchema, useColorMode, useCycleList } from "@vueuse/core";
2
2
  import { computed, watchEffect, ref, type Ref } from "vue";
3
+ import { useI18n } from "vue-i18n";
4
+ import * as _ from "lodash-es";
5
+ import { i18n } from "../../plugins/i18n";
6
+
7
+ export interface ThemeDefinition {
8
+ key: string;
9
+ localizationKey?: string;
10
+ }
11
+
12
+ export interface DisplayTheme {
13
+ key: string;
14
+ name: string; // localized name
15
+ }
3
16
 
4
17
  export interface IUseTheme {
5
- themes: Ref<string[]>;
6
- current: Ref<string>;
18
+ themes: Ref<DisplayTheme[]>;
19
+ currentThemeKey: Ref<string>;
20
+ currentLocalizedName: Ref<string>;
7
21
  next: () => void;
8
- register: (customNames: string | string[]) => void;
9
- unregister: (customNames: string | string[]) => void;
10
- setTheme: (theme: string) => void;
22
+ register: (themesToAdd: ThemeDefinition | ThemeDefinition[]) => void;
23
+ unregister: (themeKeysToRemove: string | string[]) => void;
24
+ setTheme: (themeKey: string) => void;
11
25
  }
12
26
 
13
- const registeredThemes: Ref<string[]> = ref([
14
- // "auto"
15
- ]);
27
+ // Initialize with a default "light" theme, assuming a convention for its localization key.
28
+ const _themeRegistry: Ref<ThemeDefinition[]> = ref([{ key: "light", localizationKey: "CORE.THEMES.LIGHT" }]);
29
+
16
30
  export const useTheme = (): IUseTheme => {
17
- function register(customNames: string | string[]) {
18
- (typeof customNames === "string" ? [customNames] : customNames).forEach((name) => {
19
- if (!registeredThemes.value.includes(name)) {
20
- registeredThemes.value.push(name);
31
+ const { t } = i18n.global;
32
+
33
+ function register(themesToAdd: ThemeDefinition | ThemeDefinition[]) {
34
+ (Array.isArray(themesToAdd) ? themesToAdd : [themesToAdd]).forEach((themeDef) => {
35
+ if (!_themeRegistry.value.some((t) => t.key === themeDef.key)) {
36
+ _themeRegistry.value.push(themeDef);
21
37
  }
22
38
  });
23
39
  }
24
40
 
25
- function unregister(customNames: string | string[]) {
26
- (typeof customNames === "string" ? [customNames] : customNames).forEach((name) => {
27
- const index = registeredThemes.value.indexOf(name);
41
+ function unregister(themeKeysToRemove: string | string[]) {
42
+ (Array.isArray(themeKeysToRemove) ? themeKeysToRemove : [themeKeysToRemove]).forEach((keyToRemove) => {
43
+ const index = _themeRegistry.value.findIndex((t) => t.key === keyToRemove);
28
44
  if (index >= 0) {
29
- registeredThemes.value.splice(index, 1);
45
+ // Ensure we don't unregister the last theme if it's active, or handle active theme change.
46
+ // For simplicity now, just remove. Consider active theme implications if this is an issue.
47
+ // Especially if 'light' is removed and it's the initial/fallback.
48
+ // However, 'light' is added by default and this function is for custom themes.
49
+ _themeRegistry.value.splice(index, 1);
30
50
  }
31
51
  });
32
52
  }
33
53
 
34
- function setTheme(theme: string) {
35
- state.value = theme;
36
- }
54
+ const themeKeys = computed(() => _themeRegistry.value.map((t) => t.key));
37
55
 
38
56
  const mode = useColorMode({
57
+ attribute: "data-theme",
39
58
  emitAuto: true,
40
- initialValue: "light",
59
+ initialValue: "light", // Should align with a default theme in _themeRegistry
41
60
  modes: {
42
- ...registeredThemes.value.reduce(
61
+ // Dynamically build modes from registered theme keys
62
+ ...themeKeys.value.reduce(
43
63
  (acc, name) => {
44
- acc[name] = name;
64
+ acc[name] = name; // Maps theme key to itself (e.g., "ocean": "ocean")
45
65
  return acc;
46
66
  },
47
67
  {} as Record<string, string>,
@@ -49,13 +69,41 @@ export const useTheme = (): IUseTheme => {
49
69
  },
50
70
  });
51
71
 
52
- const { state, next } = useCycleList(registeredThemes.value, { initialValue: mode });
72
+ const { state, next } = useCycleList(themeKeys, { initialValue: mode });
73
+
74
+ watchEffect(() => {
75
+ if (state.value) {
76
+ // Ensure state.value is not undefined
77
+ mode.value = state.value as BasicColorSchema; // state.value is a theme key
78
+ }
79
+ });
80
+
81
+ function setTheme(themeKey: string) {
82
+ if (themeKeys.value.includes(themeKey)) {
83
+ state.value = themeKey;
84
+ } else {
85
+ console.warn(`[useTheme] Attempted to set an unregistered theme: ${themeKey}`);
86
+ }
87
+ }
53
88
 
54
- watchEffect(() => (mode.value = state.value as BasicColorSchema));
89
+ const currentLocalizedName = computed(() => {
90
+ const currentDef = _themeRegistry.value.find((t) => t.key === state.value);
91
+ if (currentDef) {
92
+ return currentDef.localizationKey ? t(currentDef.localizationKey) : _.capitalize(currentDef.key);
93
+ }
94
+ // Fallback if current theme key somehow not in registry (should not happen with proper setTheme guard)
95
+ return state.value ? _.capitalize(state.value) : "";
96
+ });
55
97
 
56
98
  return {
57
- themes: computed(() => registeredThemes.value),
58
- current: state,
99
+ themes: computed(() =>
100
+ _themeRegistry.value.map((themeDef) => ({
101
+ key: themeDef.key,
102
+ name: themeDef.localizationKey ? t(themeDef.localizationKey) : _.capitalize(themeDef.key),
103
+ })),
104
+ ),
105
+ currentThemeKey: state,
106
+ currentLocalizedName,
59
107
  next,
60
108
  register,
61
109
  unregister,
@@ -16,11 +16,11 @@ import { RequestPasswordResult } from "./../../types";
16
16
  import { createSharedComposable } from "@vueuse/core";
17
17
  import { useExternalProvider } from "../../../shared/components/sign-in/useExternalProvider";
18
18
 
19
- interface IUseUser {
19
+ // Interface for the full internal API provided by _createInternalUserLogic
20
+ export interface IUserInternalAPI {
20
21
  user: ComputedRef<UserDetail | undefined>;
21
22
  loading: ComputedRef<boolean>;
22
23
  isAdministrator: ComputedRef<boolean | undefined>;
23
- // getAccessToken: () => Promise<string | undefined>;
24
24
  loadUser: () => Promise<UserDetail>;
25
25
  signIn: (username: string, password: string) => Promise<SignInResult | { succeeded: boolean; error?: any }>;
26
26
  signOut: () => Promise<void>;
@@ -32,8 +32,19 @@ interface IUseUser {
32
32
  getLoginType: () => Promise<LoginType[]>;
33
33
  isAuthenticated: ComputedRef<boolean>;
34
34
  }
35
+
36
+ export interface IAppUserAPI {
37
+ user: ComputedRef<UserDetail | undefined>;
38
+ loading: ComputedRef<boolean>;
39
+ isAuthenticated: ComputedRef<boolean>;
40
+ isAdministrator: ComputedRef<boolean | undefined>;
41
+ loadUser: () => Promise<UserDetail>;
42
+ signOut: () => Promise<void>;
43
+ }
44
+
35
45
  const user: Ref<UserDetail | undefined> = ref();
36
- function useUserFn(): IUseUser {
46
+
47
+ export function _createInternalUserLogic(): IUserInternalAPI {
37
48
  const loading: Ref<boolean> = ref(false);
38
49
 
39
50
  const { storage: externalSignInStorage, signOut: externalSignOut } = useExternalProvider();
@@ -72,7 +83,7 @@ function useUserFn(): IUseUser {
72
83
  username: string,
73
84
  password: string,
74
85
  ): Promise<SignInResult | { succeeded: boolean; error?: any; status?: number }> {
75
- console.debug(`[@vc-shell/framework#useUser:signIn] - Entry point`);
86
+ console.debug(`[@vc-shell/framework#_createInternalUserLogic:signIn] - Entry point`);
76
87
  try {
77
88
  loading.value = true;
78
89
  const result = await securityClient.login(new LoginRequest({ userName: username, password }));
@@ -98,7 +109,7 @@ function useUserFn(): IUseUser {
98
109
  }
99
110
 
100
111
  async function signOut(): Promise<void> {
101
- console.debug(`[@vc-shell/framework#useUser:signOut] - Entry point`);
112
+ console.debug(`[@vc-shell/framework#_createInternalUserLogic:signOut] - Entry point`);
102
113
 
103
114
  user.value = undefined;
104
115
 
@@ -110,12 +121,12 @@ function useUserFn(): IUseUser {
110
121
  }
111
122
 
112
123
  async function loadUser(): Promise<UserDetail> {
113
- console.debug(`[@vc-shell/framework#useUser:loadUser] - Entry point`);
124
+ console.debug(`[@vc-shell/framework#_createInternalUserLogic:loadUser] - Entry point`);
114
125
 
115
126
  try {
116
127
  loading.value = true;
117
128
  user.value = await securityClient.getCurrentUser();
118
- console.log("[useUser]: an user details has been loaded", user.value);
129
+ console.log("[_createInternalUserLogic]: an user details has been loaded", user.value);
119
130
  } catch (e: any) {
120
131
  console.error(e);
121
132
  } finally {
@@ -187,4 +198,14 @@ function useUserFn(): IUseUser {
187
198
  };
188
199
  }
189
200
 
190
- export const useUser = createSharedComposable(() => useUserFn());
201
+ export const useUser = createSharedComposable((): IAppUserAPI => {
202
+ const internals = _createInternalUserLogic();
203
+ return {
204
+ user: internals.user,
205
+ loading: internals.loading,
206
+ isAuthenticated: internals.isAuthenticated,
207
+ isAdministrator: internals.isAdministrator,
208
+ loadUser: internals.loadUser,
209
+ signOut: internals.signOut,
210
+ };
211
+ });
@@ -0,0 +1,45 @@
1
+ import { computed, ComputedRef } from "vue";
2
+ import { createSharedComposable } from "@vueuse/core";
3
+ import { _createInternalUserLogic, IUserInternalAPI } from "../useUser"; // Import the internal logic
4
+ import { SecurityResult, IdentityResult, LoginType, UserDetail, SignInResult } from "./../../api/platform";
5
+ import { RequestPasswordResult } from "./../../types";
6
+
7
+ // Interface for the API exposed by useUserManagement (for framework/admin use)
8
+ export interface IUserManagementAPI {
9
+ user: ComputedRef<UserDetail | undefined>;
10
+ loading: ComputedRef<boolean>;
11
+ isAdministrator: ComputedRef<boolean | undefined>;
12
+ isAuthenticated: ComputedRef<boolean>;
13
+ // Methods specific to user management
14
+ validateToken: (userId: string, token: string) => Promise<boolean>;
15
+ validatePassword: (password: string) => Promise<IdentityResult>;
16
+ resetPasswordByToken: (userId: string, password: string, token: string) => Promise<SecurityResult>;
17
+ getLoginType: () => Promise<LoginType[]>;
18
+ loadUser: () => Promise<UserDetail>;
19
+ requestPasswordReset: (loginOrEmail: string) => Promise<RequestPasswordResult>;
20
+ changeUserPassword: (oldPassword: string, newPassword: string) => Promise<SecurityResult | undefined>;
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ signIn: (username: string, password: string) => Promise<SignInResult | { succeeded: boolean; error?: any }>;
23
+ signOut: () => Promise<void>;
24
+ }
25
+
26
+ export const useUserManagement = createSharedComposable((): IUserManagementAPI => {
27
+ // Utilize the same internal logic instance
28
+ const internals: IUserInternalAPI = _createInternalUserLogic();
29
+
30
+ return {
31
+ user: internals.user,
32
+ loading: internals.loading,
33
+ isAdministrator: internals.isAdministrator,
34
+ isAuthenticated: internals.isAuthenticated,
35
+ validateToken: internals.validateToken,
36
+ validatePassword: internals.validatePassword,
37
+ resetPasswordByToken: internals.resetPasswordByToken,
38
+ getLoginType: internals.getLoginType,
39
+ loadUser: internals.loadUser,
40
+ requestPasswordReset: internals.requestPasswordReset,
41
+ changeUserPassword: internals.changeUserPassword,
42
+ signIn: internals.signIn,
43
+ signOut: internals.signOut,
44
+ };
45
+ });
@@ -1,11 +1,11 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { Router } from "vue-router";
3
- import { useUser } from "../composables/useUser";
3
+ import { useUserManagement } from "../composables/useUserManagement";
4
4
  import { notification } from "../../shared";
5
5
 
6
6
  export function registerInterceptors(router: Router) {
7
7
  const { fetch: originalFetch } = window;
8
- const { signOut, isAuthenticated } = useUser();
8
+ const { signOut, isAuthenticated } = useUserManagement();
9
9
 
10
10
  window.fetch = async (...args) => {
11
11
  /**
@@ -2,7 +2,7 @@ import { App, watch, ref, InjectionKey } from "vue";
2
2
  import { HubConnection, HubConnectionBuilder, LogLevel } from "@microsoft/signalr";
3
3
  import { PushNotification } from "../../api/platform";
4
4
  import { useNotifications } from "./../../composables/useNotifications";
5
- import { useUser } from "../../composables/useUser";
5
+ import { useUserManagement } from "../../composables/useUserManagement";
6
6
  import { useCypressSignalRMock } from "cypress-signalr-mock";
7
7
 
8
8
  const { addNotification } = useNotifications();
@@ -34,7 +34,7 @@ export const signalR = {
34
34
  },
35
35
  ) {
36
36
  currentCreator.value = options?.creator;
37
- const { isAuthenticated } = useUser();
37
+ const { isAuthenticated } = useUserManagement();
38
38
  let reconnect = false;
39
39
  const connection =
40
40
  useCypressSignalRMock("pushNotificationHub", { enableForVitest: true }) ??