@acorex-platform/module-auth-client 0.0.15

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 (79) hide show
  1. package/fesm2022/acorex-platform-module-auth-client-acorex-platform-module-auth-client-B7tIKOw6.mjs +3174 -0
  2. package/fesm2022/acorex-platform-module-auth-client-acorex-platform-module-auth-client-B7tIKOw6.mjs.map +1 -0
  3. package/fesm2022/acorex-platform-module-auth-client-app-chooser.component-Cm2ZagaI.mjs +67 -0
  4. package/fesm2022/acorex-platform-module-auth-client-app-chooser.component-Cm2ZagaI.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-module-auth-client-blank.layout-BRGgntib.mjs +18 -0
  6. package/fesm2022/acorex-platform-module-auth-client-blank.layout-BRGgntib.mjs.map +1 -0
  7. package/fesm2022/acorex-platform-module-auth-client-change-password.command-ZyXAjUKw.mjs +168 -0
  8. package/fesm2022/acorex-platform-module-auth-client-change-password.command-ZyXAjUKw.mjs.map +1 -0
  9. package/fesm2022/acorex-platform-module-auth-client-change-photo.command-BC8lw--F.mjs +192 -0
  10. package/fesm2022/acorex-platform-module-auth-client-change-photo.command-BC8lw--F.mjs.map +1 -0
  11. package/fesm2022/acorex-platform-module-auth-client-contracts.mjs +80 -0
  12. package/fesm2022/acorex-platform-module-auth-client-contracts.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-module-auth-client-edit-account-info.command-B-mVlXxg.mjs +129 -0
  14. package/fesm2022/acorex-platform-module-auth-client-edit-account-info.command-B-mVlXxg.mjs.map +1 -0
  15. package/fesm2022/acorex-platform-module-auth-client-edit-signature.command-CcTJJwAX.mjs +32 -0
  16. package/fesm2022/acorex-platform-module-auth-client-edit-signature.command-CcTJJwAX.mjs.map +1 -0
  17. package/fesm2022/acorex-platform-module-auth-client-lock-screen.command-41QnG1TQ.mjs +30 -0
  18. package/fesm2022/acorex-platform-module-auth-client-lock-screen.command-41QnG1TQ.mjs.map +1 -0
  19. package/fesm2022/acorex-platform-module-auth-client-login.module-DYtNYzF6.mjs +57 -0
  20. package/fesm2022/acorex-platform-module-auth-client-login.module-DYtNYzF6.mjs.map +1 -0
  21. package/fesm2022/acorex-platform-module-auth-client-master.layout-DpdD9kgk.mjs +24 -0
  22. package/fesm2022/acorex-platform-module-auth-client-master.layout-DpdD9kgk.mjs.map +1 -0
  23. package/fesm2022/acorex-platform-module-auth-client-menu.provider-Bz-kRip4.mjs +19 -0
  24. package/fesm2022/acorex-platform-module-auth-client-menu.provider-Bz-kRip4.mjs.map +1 -0
  25. package/fesm2022/acorex-platform-module-auth-client-oauth-callback.component-BspBH4Ul.mjs +105 -0
  26. package/fesm2022/acorex-platform-module-auth-client-oauth-callback.component-BspBH4Ul.mjs.map +1 -0
  27. package/fesm2022/acorex-platform-module-auth-client-open-sessions.command-CXSGuqYp.mjs +196 -0
  28. package/fesm2022/acorex-platform-module-auth-client-open-sessions.command-CXSGuqYp.mjs.map +1 -0
  29. package/fesm2022/acorex-platform-module-auth-client-password.component-BuqUhET7.mjs +59 -0
  30. package/fesm2022/acorex-platform-module-auth-client-password.component-BuqUhET7.mjs.map +1 -0
  31. package/fesm2022/acorex-platform-module-auth-client-password.component-Cihg82vM.mjs +529 -0
  32. package/fesm2022/acorex-platform-module-auth-client-password.component-Cihg82vM.mjs.map +1 -0
  33. package/fesm2022/acorex-platform-module-auth-client-pin-code.constants-CRsFG7fg.mjs +7 -0
  34. package/fesm2022/acorex-platform-module-auth-client-pin-code.constants-CRsFG7fg.mjs.map +1 -0
  35. package/fesm2022/acorex-platform-module-auth-client-profile-activity-chart.component-B8lBvZsA.mjs +158 -0
  36. package/fesm2022/acorex-platform-module-auth-client-profile-activity-chart.component-B8lBvZsA.mjs.map +1 -0
  37. package/fesm2022/acorex-platform-module-auth-client-profile-coming-soon.command-DBbFbzAx.mjs +31 -0
  38. package/fesm2022/acorex-platform-module-auth-client-profile-coming-soon.command-DBbFbzAx.mjs.map +1 -0
  39. package/fesm2022/acorex-platform-module-auth-client-profile-delete-account-danger-zone.component-CtJho4ab.mjs +79 -0
  40. package/fesm2022/acorex-platform-module-auth-client-profile-delete-account-danger-zone.component-CtJho4ab.mjs.map +1 -0
  41. package/fesm2022/acorex-platform-module-auth-client-profile-page-component.provider-B-Qtd4_j.mjs +27 -0
  42. package/fesm2022/acorex-platform-module-auth-client-profile-page-component.provider-B-Qtd4_j.mjs.map +1 -0
  43. package/fesm2022/acorex-platform-module-auth-client-profile-page.component-tbhf7K0V.mjs +446 -0
  44. package/fesm2022/acorex-platform-module-auth-client-profile-page.component-tbhf7K0V.mjs.map +1 -0
  45. package/fesm2022/acorex-platform-module-auth-client-register-password.component-hSJyEA6Q.mjs +145 -0
  46. package/fesm2022/acorex-platform-module-auth-client-register-password.component-hSJyEA6Q.mjs.map +1 -0
  47. package/fesm2022/acorex-platform-module-auth-client-register.module-DXm6AQpo.mjs +62 -0
  48. package/fesm2022/acorex-platform-module-auth-client-register.module-DXm6AQpo.mjs.map +1 -0
  49. package/fesm2022/acorex-platform-module-auth-client-remove-pin-code.command-zUZooOtv.mjs +91 -0
  50. package/fesm2022/acorex-platform-module-auth-client-remove-pin-code.command-zUZooOtv.mjs.map +1 -0
  51. package/fesm2022/acorex-platform-module-auth-client-result.component-DYJaB-FD.mjs +36 -0
  52. package/fesm2022/acorex-platform-module-auth-client-result.component-DYJaB-FD.mjs.map +1 -0
  53. package/fesm2022/acorex-platform-module-auth-client-routes-REjaWcqY.mjs +9 -0
  54. package/fesm2022/acorex-platform-module-auth-client-routes-REjaWcqY.mjs.map +1 -0
  55. package/fesm2022/acorex-platform-module-auth-client-session-lock-overlay.component-DqXGuIgN.mjs +346 -0
  56. package/fesm2022/acorex-platform-module-auth-client-session-lock-overlay.component-DqXGuIgN.mjs.map +1 -0
  57. package/fesm2022/acorex-platform-module-auth-client-set-pin-code.command-DXTVbrS0.mjs +33 -0
  58. package/fesm2022/acorex-platform-module-auth-client-set-pin-code.command-DXTVbrS0.mjs.map +1 -0
  59. package/fesm2022/acorex-platform-module-auth-client-set-pin-code.component-5TxB6aFi.mjs +188 -0
  60. package/fesm2022/acorex-platform-module-auth-client-set-pin-code.component-5TxB6aFi.mjs.map +1 -0
  61. package/fesm2022/acorex-platform-module-auth-client-settings.provider-n1qbs6e1.mjs +177 -0
  62. package/fesm2022/acorex-platform-module-auth-client-settings.provider-n1qbs6e1.mjs.map +1 -0
  63. package/fesm2022/acorex-platform-module-auth-client-show-profile-command-CxMz1yy-.mjs +70 -0
  64. package/fesm2022/acorex-platform-module-auth-client-show-profile-command-CxMz1yy-.mjs.map +1 -0
  65. package/fesm2022/acorex-platform-module-auth-client-signout-command-DqqrP5pk.mjs +24 -0
  66. package/fesm2022/acorex-platform-module-auth-client-signout-command-DqqrP5pk.mjs.map +1 -0
  67. package/fesm2022/acorex-platform-module-auth-client-tenant-chooser.component-ihsqCJKN.mjs +59 -0
  68. package/fesm2022/acorex-platform-module-auth-client-tenant-chooser.component-ihsqCJKN.mjs.map +1 -0
  69. package/fesm2022/acorex-platform-module-auth-client-two-factor-code.component-CiENsIEh.mjs +40 -0
  70. package/fesm2022/acorex-platform-module-auth-client-two-factor-code.component-CiENsIEh.mjs.map +1 -0
  71. package/fesm2022/acorex-platform-module-auth-client-two-factor.module-BJqdeuAz.mjs +45 -0
  72. package/fesm2022/acorex-platform-module-auth-client-two-factor.module-BJqdeuAz.mjs.map +1 -0
  73. package/fesm2022/acorex-platform-module-auth-client-view-login-history.command-Dl0os5lD.mjs +108 -0
  74. package/fesm2022/acorex-platform-module-auth-client-view-login-history.command-Dl0os5lD.mjs.map +1 -0
  75. package/fesm2022/acorex-platform-module-auth-client.mjs +2 -0
  76. package/fesm2022/acorex-platform-module-auth-client.mjs.map +1 -0
  77. package/package.json +28 -0
  78. package/types/acorex-platform-module-auth-client-contracts.d.ts +67 -0
  79. package/types/acorex-platform-module-auth-client.d.ts +390 -0
@@ -0,0 +1,3174 @@
1
+ import * as i1$4 from '@acorex-platform/framework-client/common';
2
+ import { AXPFileStorageService, AXPSettingsService, AXPHomePageService, AXPDataProvider, AXPCommonSettings, AXP_PROTECTED_ROUTE_GUARDS, AXPCommonModule, AXP_MENU_PROVIDER, AXP_SETTING_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/common';
3
+ import { AXPSessionService, AXPUserProfileIdentityService, AXPUnauthorizedError, AXPUnauthenticatedError, AXPAuthStrategyRegistryService, AXPAuthGuard, AXPSessionStatus } from '@acorex-platform/framework-client/auth';
4
+ import { AXPUserAvatarService, AXPUserAvatarComponent, AXP_PAGE_COMPONENT_PROVIDER } from '@acorex-platform/framework-client/layout/components';
5
+ import { AXPFileStorageStatus, AXPWidgetsCatalog, coerceUnknownToTrimmedString, AXPPlatformScope, AXPKeyboardShortcutPriority, verifyPassword } from '@acorex-platform/framework-shared/core';
6
+ import { RootConfig } from '@acorex-platform/module-person-core-client/contracts';
7
+ import { RootConfig as RootConfig$1 } from '@acorex-platform/module-security-management-client/contracts';
8
+ import { AXPEntityService, AXP_ENTITY_STORAGE_MIDDLEWARE } from '@acorex-platform/framework-client/layout/entity';
9
+ import * as i0 from '@angular/core';
10
+ import { inject, Injectable, isDevMode, InjectionToken, NgModule, input, output, viewChild, viewChildren, signal, computed, effect, ChangeDetectionStrategy, Component, DestroyRef, ViewEncapsulation, ViewChild, Injector, afterNextRender, provideAppInitializer } from '@angular/core';
11
+ import { first, isEmpty, isNil, isString, orderBy, get, defaultTo } from 'lodash';
12
+ import { AXDataSource } from '@acorex/cdk/common';
13
+ import { AXMAuthDataSourceKeys, AXPAuthSettings, AXMAuthCommandKeys } from '@acorex-platform/module-auth-client/contracts';
14
+ import { AXPLayoutThemeService, AXPMenuVerticalMode } from '@acorex-platform/framework-client/themes/shared';
15
+ import { HttpErrorResponse } from '@angular/common/http';
16
+ import * as i1$5 from '@angular/router';
17
+ import { Router, RouterModule, ROUTES } from '@angular/router';
18
+ import * as i1 from '@acorex/core/validation';
19
+ import { AXValidationModule } from '@acorex/core/validation';
20
+ import * as i1$3 from '@acorex-platform/framework-client/core';
21
+ import { axpApplicationLogoKey, AXPEntityLogoRegistrationService, AXPLogoComponent, axpTenantLogoKey, AXPHookService, AXPKeyboardShortcutRegistry, chordToKbdItemKeys, getPrimaryKeyboardShortcutChord, normalizeKeyboardShortcuts, AXPDeviceService, AXPComponentSlotModule, AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER, provideLazyProvider, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPComponentSlotDirective } from '@acorex-platform/framework-client/core';
22
+ import { AXPCommandService, provideCommandSetups } from '@acorex-platform/framework-client/runtime';
23
+ import { AXPRootLayoutComponent } from '@acorex-platform/framework-client/themes/default';
24
+ import * as i4$1 from '@angular/common';
25
+ import { CommonModule, AsyncPipe } from '@angular/common';
26
+ import { first as first$1, firstValueFrom, filter, combineLatest, switchMap, from } from 'rxjs';
27
+ import * as i1$1 from '@acorex/components/button';
28
+ import { AXButtonModule } from '@acorex/components/button';
29
+ import * as i3$1 from '@acorex/components/decorators';
30
+ import { AXDecoratorModule } from '@acorex/components/decorators';
31
+ import * as i2$2 from '@acorex/components/popover';
32
+ import { AXPopoverModule } from '@acorex/components/popover';
33
+ import { RootConfig as RootConfig$2 } from '@acorex-platform/module-application-management-client/contracts';
34
+ import * as i5 from '@acorex/cdk/list-navigation';
35
+ import { AXListNavigationDirective, AXListNavigationItemDirective, AXListNavigationModule } from '@acorex/cdk/list-navigation';
36
+ import * as i4 from '@acorex/components/search-box';
37
+ import { AXSearchBoxModule } from '@acorex/components/search-box';
38
+ import * as i3 from '@acorex/components/select-box';
39
+ import { AXSelectBoxModule } from '@acorex/components/select-box';
40
+ import * as i2 from '@acorex/components/skeleton';
41
+ import { AXSkeletonModule } from '@acorex/components/skeleton';
42
+ import * as i2$1 from '@acorex/core/translation';
43
+ import { AXTranslationModule, AXTranslationService } from '@acorex/core/translation';
44
+ import { AXAvatarModule } from '@acorex/components/avatar';
45
+ import { AXImageModule } from '@acorex/components/image';
46
+ import { AXLoadingModule } from '@acorex/components/loading';
47
+ import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
48
+ import * as i1$2 from '@angular/forms';
49
+ import { FormsModule } from '@angular/forms';
50
+ import { AXToastService } from '@acorex/components/toast';
51
+ import { AXMDeviceSessionsService } from '@acorex-platform/module-security-management-client';
52
+ import { AXOverlayService } from '@acorex/cdk/overlay';
53
+ import { AXPlatform } from '@acorex/core/platform';
54
+
55
+ //#region ---- Hook keys ----
56
+ const PROFILE_PAGE_GROUPS_HOOK = 'profile-page.groups';
57
+ const PROFILE_PAGE_SECTIONS_HOOK = 'profile-page.sections';
58
+ const PROFILE_PAGE_HEADER_META_HOOK = 'profile-page.header-meta';
59
+ //#endregion
60
+ //#region ---- Helpers ----
61
+ /** Resolves section content; top-level `actions` imply card mode. */
62
+ function resolveUserProfileSectionContent(section) {
63
+ if (section.content) {
64
+ return section.content;
65
+ }
66
+ return { mode: 'card', actions: section.actions ?? [] };
67
+ }
68
+ /** Whether the group renders a full-tab component instead of sections. */
69
+ function isUserProfileGroupComponent(group) {
70
+ return group.content?.mode === 'component';
71
+ }
72
+ /** Visible actions on a section card. */
73
+ function visibleUserProfileSectionActions(actions) {
74
+ return actions.filter((action) => action.visible !== false);
75
+ }
76
+ /** Default card action: explicit `default`, otherwise the first visible action. */
77
+ function resolveDefaultUserProfileSectionAction(actions) {
78
+ const visibleActions = visibleUserProfileSectionActions(actions);
79
+ if (!visibleActions.length) {
80
+ return undefined;
81
+ }
82
+ return visibleActions.find((action) => action.default) ?? visibleActions[0];
83
+ }
84
+ //#endregion
85
+
86
+ //#region ---- Avatar storage constants ----
87
+ const AXP_PROFILE_PHOTO_REF_TYPE = 'PersonCore.Person';
88
+ const AXP_PROFILE_PHOTO_USER_REF_TYPE = 'SecurityManagement.User';
89
+ const AXP_PROFILE_PHOTO_CATEGORY = 'profile-photo';
90
+ const FILE_STORAGE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
91
+ //#endregion
92
+ //#region ---- Avatar storage helpers ----
93
+ function isFileStorageId(value) {
94
+ if (!value) {
95
+ return false;
96
+ }
97
+ return FILE_STORAGE_ID_PATTERN.test(value.trim());
98
+ }
99
+ function isExternalAvatarUrl(value) {
100
+ if (!value) {
101
+ return false;
102
+ }
103
+ return /^https?:\/\//i.test(value.trim());
104
+ }
105
+ function isDataImageUrl(value) {
106
+ if (!value) {
107
+ return false;
108
+ }
109
+ return value.trim().startsWith('data:image/');
110
+ }
111
+ function normalizeAvatarRef(value) {
112
+ if (value === null || value === undefined) {
113
+ return null;
114
+ }
115
+ if (typeof value !== 'string') {
116
+ return null;
117
+ }
118
+ const trimmed = value.trim();
119
+ if (!trimmed || trimmed === 'null') {
120
+ return null;
121
+ }
122
+ return trimmed;
123
+ }
124
+ function dataUrlToBlob(dataUrl) {
125
+ const [header, base64] = dataUrl.split(',');
126
+ const mimeMatch = /data:([^;]+)/.exec(header);
127
+ const mimeType = mimeMatch?.[1] ?? 'image/png';
128
+ const binary = atob(base64);
129
+ const bytes = new Uint8Array(binary.length);
130
+ for (let i = 0; i < binary.length; i++) {
131
+ bytes[i] = binary.charCodeAt(i);
132
+ }
133
+ return new Blob([bytes], { type: mimeType });
134
+ }
135
+ //#endregion
136
+
137
+ //#endregion
138
+ //#region ---- User avatar sync service (client) ----
139
+ /**
140
+ * Client-side profile photo operations for self-service flows.
141
+ * Bidirectional Person ↔ User sync runs on the server via HCM avatar middleware.
142
+ */
143
+ class AXPUserAvatarSyncService {
144
+ constructor() {
145
+ //#region ---- Services & Dependencies ----
146
+ this.entityService = inject(AXPEntityService);
147
+ this.fileStorage = inject(AXPFileStorageService);
148
+ this.sessionService = inject(AXPSessionService);
149
+ this.identityService = inject(AXPUserProfileIdentityService);
150
+ this.userAvatarService = inject(AXPUserAvatarService);
151
+ }
152
+ //#endregion
153
+ //#region ---- Public API ----
154
+ async resolveStorageTarget(userId) {
155
+ const person = await this.findPersonByUserId(userId);
156
+ if (person?.id) {
157
+ return { kind: 'person', personId: person.id, userId };
158
+ }
159
+ return { kind: 'user', userId };
160
+ }
161
+ async getCurrentAvatarRef(userId) {
162
+ const sessionUser = this.sessionService.user;
163
+ if (sessionUser?.id === userId) {
164
+ const sessionRef = normalizeAvatarRef(sessionUser.avatar);
165
+ if (sessionRef) {
166
+ return sessionRef;
167
+ }
168
+ }
169
+ const target = await this.resolveStorageTarget(userId);
170
+ if (target.kind === 'person') {
171
+ const person = await this.personData().byKey(target.personId);
172
+ return normalizeAvatarRef(person?.image);
173
+ }
174
+ const user = await this.userData().byKey(userId);
175
+ return normalizeAvatarRef(user?.avatar);
176
+ }
177
+ async getCurrentAvatarBlob(userId) {
178
+ const ref = await this.getCurrentAvatarRef(userId);
179
+ if (!ref) {
180
+ return null;
181
+ }
182
+ if (isDataImageUrl(ref)) {
183
+ return dataUrlToBlob(ref);
184
+ }
185
+ if (isExternalAvatarUrl(ref)) {
186
+ const response = await fetch(ref);
187
+ return response.blob();
188
+ }
189
+ if (isFileStorageId(ref)) {
190
+ return this.fileStorage.download(ref);
191
+ }
192
+ return null;
193
+ }
194
+ async uploadAndSetAvatar(userId, file) {
195
+ const target = await this.resolveStorageTarget(userId);
196
+ const previousRef = await this.getCurrentAvatarRef(userId);
197
+ const refId = target.kind === 'person' ? target.personId : userId;
198
+ const refType = target.kind === 'person' ? AXP_PROFILE_PHOTO_REF_TYPE : AXP_PROFILE_PHOTO_USER_REF_TYPE;
199
+ const info = await this.fileStorage.save({
200
+ file,
201
+ name: file.name || 'profile-photo.png',
202
+ refId,
203
+ refType,
204
+ category: AXP_PROFILE_PHOTO_CATEGORY,
205
+ status: AXPFileStorageStatus.Temporary,
206
+ });
207
+ const fileId = info.fileId?.trim();
208
+ if (!fileId) {
209
+ throw new Error('File storage did not return a fileId.');
210
+ }
211
+ await this.fileStorage.commit(fileId);
212
+ if (target.kind === 'person') {
213
+ await this.personData().update(target.personId, { image: fileId });
214
+ }
215
+ else {
216
+ await this.userData().update(userId, { avatar: fileId });
217
+ }
218
+ await this.notifyAvatarChanged(userId, fileId);
219
+ if (previousRef && isFileStorageId(previousRef) && previousRef !== fileId) {
220
+ await this.safeRemoveFile(previousRef);
221
+ }
222
+ return fileId;
223
+ }
224
+ async removeAvatar(userId) {
225
+ const target = await this.resolveStorageTarget(userId);
226
+ const previousRef = await this.getCurrentAvatarRef(userId);
227
+ if (target.kind === 'person') {
228
+ await this.personData().update(target.personId, { image: undefined });
229
+ }
230
+ else {
231
+ await this.userData().update(userId, { avatar: null });
232
+ }
233
+ await this.notifyAvatarChanged(userId, null);
234
+ if (previousRef && isFileStorageId(previousRef)) {
235
+ await this.safeRemoveFile(previousRef);
236
+ }
237
+ }
238
+ /** Keeps session, avatar cache, and shell avatars in sync after any avatar change. */
239
+ async notifyAvatarChanged(userId, avatar) {
240
+ await this.refreshClientDisplay(userId, avatar);
241
+ }
242
+ //#endregion
243
+ //#region ---- Private helpers ----
244
+ async refreshClientDisplay(userId, avatar) {
245
+ const isCurrentUser = this.sessionService.user?.id === userId;
246
+ if (isCurrentUser) {
247
+ await this.identityService.applySessionPatch({
248
+ avatar: avatar === null ? '' : avatar,
249
+ });
250
+ }
251
+ this.userAvatarService.clearCache(userId);
252
+ await this.userAvatarService.refreshUserInfo(userId);
253
+ }
254
+ personData() {
255
+ return this.entityService
256
+ .withEntity(RootConfig.module.name, RootConfig.entities.person.name)
257
+ .data();
258
+ }
259
+ userData() {
260
+ return this.entityService
261
+ .withEntity(RootConfig$1.module.name, RootConfig$1.entities.users.name)
262
+ .data();
263
+ }
264
+ async findPersonByUserId(userId) {
265
+ const result = await this.personData().query({
266
+ filter: { field: 'userId', operator: { type: 'equal' }, value: userId },
267
+ take: 1,
268
+ skip: 0,
269
+ });
270
+ return first(result.items) ?? null;
271
+ }
272
+ async safeRemoveFile(fileId) {
273
+ try {
274
+ await this.fileStorage.remove(fileId);
275
+ }
276
+ catch {
277
+ try {
278
+ await this.fileStorage.markForDeletion(fileId);
279
+ }
280
+ catch {
281
+ /* ignore cleanup failures */
282
+ }
283
+ }
284
+ }
285
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUserAvatarSyncService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
286
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUserAvatarSyncService, providedIn: 'root' }); }
287
+ }
288
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUserAvatarSyncService, decorators: [{
289
+ type: Injectable,
290
+ args: [{ providedIn: 'root' }]
291
+ }] });
292
+
293
+ //#region ---- User activity data source types ----
294
+ //#endregion
295
+
296
+ //#region ---- User activity helpers ----
297
+ //#region ---- Constants ----
298
+ const AUTH_USER_ACTIVITY_DEFAULT_WEEKS = 53;
299
+ const LEVEL_THRESHOLDS = [0, 3, 8, 15, 30];
300
+ //#endregion
301
+ //#region ---- Date range ----
302
+ function resolveDefaultUserActivityDateRange(weeksToShow = AUTH_USER_ACTIVITY_DEFAULT_WEEKS, referenceDate = new Date()) {
303
+ const today = new Date(referenceDate);
304
+ today.setHours(0, 0, 0, 0);
305
+ const endSunday = new Date(today);
306
+ endSunday.setDate(today.getDate() + (6 - today.getDay()));
307
+ const startDate = new Date(endSunday);
308
+ startDate.setDate(endSunday.getDate() - weeksToShow * 7 + 1);
309
+ startDate.setHours(0, 0, 0, 0);
310
+ return { startDate, endDate: endSunday };
311
+ }
312
+ //#endregion
313
+ //#region ---- Session aggregation ----
314
+ function normalizeDateRange(startDate, endDate) {
315
+ const start = new Date(startDate);
316
+ const end = new Date(endDate);
317
+ start.setHours(0, 0, 0, 0);
318
+ end.setHours(0, 0, 0, 0);
319
+ return start <= end ? { rangeStart: start, rangeEnd: end } : { rangeStart: end, rangeEnd: start };
320
+ }
321
+ function toIsoDate(date) {
322
+ return date.toISOString().slice(0, 10);
323
+ }
324
+ function toDayKey(value) {
325
+ const date = new Date(value);
326
+ if (Number.isNaN(date.getTime())) {
327
+ return undefined;
328
+ }
329
+ date.setHours(0, 0, 0, 0);
330
+ return toIsoDate(date);
331
+ }
332
+ /** Counts sign-in events per calendar day from device session rows. */
333
+ function aggregateDailySessionCounts(sessions, params) {
334
+ const { rangeStart, rangeEnd } = normalizeDateRange(params.startDate, params.endDate);
335
+ const counts = new Map();
336
+ for (const session of sessions) {
337
+ if (session.userId !== params.userId || session.lastLoginDate == null) {
338
+ continue;
339
+ }
340
+ const dayKey = toDayKey(session.lastLoginDate);
341
+ if (!dayKey) {
342
+ continue;
343
+ }
344
+ const day = new Date(dayKey);
345
+ if (day < rangeStart || day > rangeEnd) {
346
+ continue;
347
+ }
348
+ counts.set(dayKey, (counts.get(dayKey) ?? 0) + 1);
349
+ }
350
+ return counts;
351
+ }
352
+ //#endregion
353
+ //#region ---- Week grid ----
354
+ function countToActivityLevel(count) {
355
+ if (count <= LEVEL_THRESHOLDS[1]) {
356
+ return count === 0 ? 0 : 1;
357
+ }
358
+ if (count <= LEVEL_THRESHOLDS[2]) {
359
+ return 2;
360
+ }
361
+ if (count <= LEVEL_THRESHOLDS[3]) {
362
+ return 3;
363
+ }
364
+ return 4;
365
+ }
366
+ function startOfWeekSunday(date) {
367
+ const value = new Date(date);
368
+ value.setHours(0, 0, 0, 0);
369
+ value.setDate(value.getDate() - value.getDay());
370
+ return value;
371
+ }
372
+ function buildUserActivityWeeks(params, dailyCounts) {
373
+ const { rangeStart, rangeEnd } = normalizeDateRange(params.startDate, params.endDate);
374
+ const today = new Date();
375
+ today.setHours(0, 0, 0, 0);
376
+ const gridStart = startOfWeekSunday(rangeStart);
377
+ const gridEnd = startOfWeekSunday(rangeEnd);
378
+ gridEnd.setDate(gridEnd.getDate() + 6);
379
+ const weeks = [];
380
+ const cursor = new Date(gridStart);
381
+ while (cursor <= gridEnd) {
382
+ const days = [];
383
+ for (let dayIndex = 0; dayIndex < 7; dayIndex += 1) {
384
+ const date = new Date(cursor);
385
+ const inRange = date >= rangeStart && date <= rangeEnd;
386
+ const dayKey = toIsoDate(date);
387
+ const count = inRange && date <= today ? (dailyCounts.get(dayKey) ?? 0) : 0;
388
+ days.push({
389
+ date: dayKey,
390
+ count,
391
+ level: countToActivityLevel(count),
392
+ });
393
+ cursor.setDate(cursor.getDate() + 1);
394
+ }
395
+ weeks.push({ days });
396
+ }
397
+ return weeks;
398
+ }
399
+ function computeUserActivityStats(weeks) {
400
+ const days = weeks.flatMap((week) => week.days).filter((day) => day.count > 0);
401
+ const total = days.reduce((sum, day) => sum + day.count, 0);
402
+ const activeDayCount = days.length || 1;
403
+ let longestStreak = 0;
404
+ let currentStreak = 0;
405
+ for (const week of weeks) {
406
+ for (const day of week.days) {
407
+ if (day.count > 0) {
408
+ currentStreak += 1;
409
+ longestStreak = Math.max(longestStreak, currentStreak);
410
+ }
411
+ else {
412
+ currentStreak = 0;
413
+ }
414
+ }
415
+ }
416
+ const weekCount = weeks.length || 1;
417
+ return {
418
+ longestStreak,
419
+ avgPerDay: Math.round(total / activeDayCount),
420
+ avgPerWeek: Math.round(total / weekCount),
421
+ total,
422
+ };
423
+ }
424
+ function buildUserActivityDataSourceItem(params, sessions) {
425
+ const dailyCounts = aggregateDailySessionCounts(sessions, params);
426
+ const weeks = buildUserActivityWeeks(params, dailyCounts);
427
+ return {
428
+ id: `${params.userId}:${toIsoDate(params.startDate)}:${toIsoDate(params.endDate)}`,
429
+ userId: params.userId,
430
+ startDate: toIsoDate(params.startDate),
431
+ endDate: toIsoDate(params.endDate),
432
+ weeks,
433
+ stats: computeUserActivityStats(weeks),
434
+ };
435
+ }
436
+ function formatCompactActivityCount(value) {
437
+ if (value >= 1_000_000) {
438
+ return `${(value / 1_000_000).toFixed(2)}M`;
439
+ }
440
+ if (value >= 1_000) {
441
+ return `${Math.round(value / 1000)}K`;
442
+ }
443
+ return String(value);
444
+ }
445
+ //#endregion
446
+
447
+ //#region ---- User activity filter helpers ----
448
+ function isResolvedFilterScalar(value) {
449
+ if (value == null) {
450
+ return false;
451
+ }
452
+ if (value instanceof Date) {
453
+ return !Number.isNaN(value.getTime());
454
+ }
455
+ const text = String(value).trim();
456
+ return text.length > 0 && !text.includes('{{');
457
+ }
458
+ function parseFilterDate(value) {
459
+ if (!isResolvedFilterScalar(value)) {
460
+ return undefined;
461
+ }
462
+ if (value instanceof Date) {
463
+ return value;
464
+ }
465
+ const parsed = new Date(String(value));
466
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
467
+ }
468
+ function extractFilterFieldValue(filter, field, operators) {
469
+ if (!filter) {
470
+ return undefined;
471
+ }
472
+ if (filter.filters?.length) {
473
+ for (const child of filter.filters) {
474
+ const value = extractFilterFieldValue(child, field, operators);
475
+ if (value !== undefined) {
476
+ return value;
477
+ }
478
+ }
479
+ return undefined;
480
+ }
481
+ const operator = String(filter.operator?.type ?? '');
482
+ if (filter.field === field && operators.includes(operator)) {
483
+ return isResolvedFilterScalar(filter.value) ? filter.value : undefined;
484
+ }
485
+ return undefined;
486
+ }
487
+ function extractUserActivityUserId(filter) {
488
+ const value = extractFilterFieldValue(filter, 'userId', ['equal', 'eq']);
489
+ if (value == null) {
490
+ return undefined;
491
+ }
492
+ const text = String(value).trim();
493
+ return text.length > 0 ? text : undefined;
494
+ }
495
+ function extractUserActivityStartDate(filter) {
496
+ const gte = parseFilterDate(extractFilterFieldValue(filter, 'startDate', ['gte', 'greaterThanOrEqual', 'equal', 'eq']));
497
+ if (gte) {
498
+ return gte;
499
+ }
500
+ return parseFilterDate(extractFilterFieldValue(filter, 'dateFrom', ['gte', 'greaterThanOrEqual', 'equal', 'eq']));
501
+ }
502
+ function extractUserActivityEndDate(filter) {
503
+ const lte = parseFilterDate(extractFilterFieldValue(filter, 'endDate', ['lte', 'lessThanOrEqual', 'equal', 'eq']));
504
+ if (lte) {
505
+ return lte;
506
+ }
507
+ return parseFilterDate(extractFilterFieldValue(filter, 'dateTo', ['lte', 'lessThanOrEqual', 'equal', 'eq']));
508
+ }
509
+ //#endregion
510
+
511
+ //#region ---- User activity data source ----
512
+ function resolveLoadParams(request) {
513
+ const userId = extractUserActivityUserId(request?.filter);
514
+ if (isEmpty(userId)) {
515
+ return null;
516
+ }
517
+ const defaultRange = resolveDefaultUserActivityDateRange();
518
+ const startDate = extractUserActivityStartDate(request?.filter) ?? defaultRange.startDate;
519
+ const endDate = extractUserActivityEndDate(request?.filter) ?? defaultRange.endDate;
520
+ return {
521
+ userId: userId,
522
+ startDate,
523
+ endDate,
524
+ };
525
+ }
526
+ /** Contribution-style user activity heatmap derived from device session sign-in dates. */
527
+ class AXMUserActivityDataSourceDefinition {
528
+ constructor() {
529
+ //#region ---- Services & Dependencies ----
530
+ this.entityService = inject(AXPEntityService);
531
+ //#endregion
532
+ }
533
+ //#endregion
534
+ //#region ---- Public API ----
535
+ items() {
536
+ const sampleParams = resolveDefaultUserActivityDateRange();
537
+ const sampleItem = buildUserActivityDataSourceItem({ userId: 'sample-user', ...sampleParams }, []);
538
+ return Promise.resolve([
539
+ {
540
+ name: AXMAuthDataSourceKeys.UserActivity,
541
+ title: 'User Activity',
542
+ columns: [
543
+ {
544
+ name: 'id',
545
+ title: 'ID',
546
+ datatype: 'string',
547
+ type: AXPWidgetsCatalog.text,
548
+ },
549
+ {
550
+ name: 'userId',
551
+ title: 'User ID',
552
+ datatype: 'string',
553
+ type: AXPWidgetsCatalog.text,
554
+ },
555
+ {
556
+ name: 'startDate',
557
+ title: 'Start Date',
558
+ datatype: 'date',
559
+ type: AXPWidgetsCatalog.dateTime,
560
+ },
561
+ {
562
+ name: 'endDate',
563
+ title: 'End Date',
564
+ datatype: 'date',
565
+ type: AXPWidgetsCatalog.dateTime,
566
+ },
567
+ ],
568
+ filters: [
569
+ {
570
+ field: 'userId',
571
+ title: 'User',
572
+ operator: { type: 'equal' },
573
+ widget: { type: AXPWidgetsCatalog.text },
574
+ filterType: { advance: true, inline: true },
575
+ },
576
+ {
577
+ field: 'startDate',
578
+ title: 'Start Date',
579
+ operator: { type: 'gte' },
580
+ widget: { type: AXPWidgetsCatalog.dateTime, options: { format: 'date' } },
581
+ filterType: { advance: true, inline: true },
582
+ },
583
+ {
584
+ field: 'endDate',
585
+ title: 'End Date',
586
+ operator: { type: 'lte' },
587
+ widget: { type: AXPWidgetsCatalog.dateTime, options: { format: 'date' } },
588
+ filterType: { advance: true, inline: true },
589
+ },
590
+ ],
591
+ textField: { name: 'id', title: 'ID' },
592
+ valueField: { name: 'id', title: 'ID' },
593
+ samples: [sampleItem],
594
+ source: () => new AXDataSource({
595
+ key: 'id',
596
+ pageSize: 1,
597
+ load: async (request) => {
598
+ const params = resolveLoadParams(request);
599
+ if (!params) {
600
+ return { items: [], total: 0 };
601
+ }
602
+ const sessions = await this.loadUserSessions(params.userId);
603
+ const item = buildUserActivityDataSourceItem(params, sessions);
604
+ return { items: [item], total: 1 };
605
+ },
606
+ }),
607
+ },
608
+ ]);
609
+ }
610
+ //#endregion
611
+ //#region ---- Private Methods ----
612
+ async loadUserSessions(userId) {
613
+ const response = await this.entityService
614
+ .withEntity(RootConfig$1.module.name, RootConfig$1.entities.deviceSessions.name)
615
+ .data()
616
+ .query({
617
+ skip: 0,
618
+ take: 1000,
619
+ filter: {
620
+ field: 'userId',
621
+ operator: { type: 'equal' },
622
+ value: userId,
623
+ },
624
+ });
625
+ return response.items ?? [];
626
+ }
627
+ }
628
+ //#endregion
629
+
630
+ //#region ---- Auth data source provider ----
631
+ /**
632
+ * Registers auth module data source definitions.
633
+ */
634
+ class AXMAuthDataSourceProvider {
635
+ constructor() {
636
+ this.userActivity = new AXMUserActivityDataSourceDefinition();
637
+ }
638
+ async items() {
639
+ return [...(await this.userActivity.items())];
640
+ }
641
+ }
642
+ //#endregion
643
+
644
+ var datasource_provider = /*#__PURE__*/Object.freeze({
645
+ __proto__: null,
646
+ AXMAuthDataSourceProvider: AXMAuthDataSourceProvider
647
+ });
648
+
649
+ class AXMAuthErrorHandler {
650
+ constructor() {
651
+ this.router = inject(Router);
652
+ this.layoutService = inject(AXPLayoutThemeService);
653
+ }
654
+ handleError(error, next) {
655
+ if (error instanceof HttpErrorResponse) {
656
+ // if (error.status == 401) {
657
+ // this.router.navigate(['/auth/login']);
658
+ // }
659
+ // else if (error.status == 404) {
660
+ // this.router.navigate(['/404']);
661
+ // }
662
+ }
663
+ else if (error instanceof AXPUnauthorizedError) {
664
+ const redirectUrl = error.data?.redirectUrl;
665
+ console.error(error.message);
666
+ this.router.navigate(['/401'], { queryParams: redirectUrl ? { redirectUrl: redirectUrl } : null });
667
+ }
668
+ else if (error instanceof AXPUnauthenticatedError) {
669
+ console.error(error.message);
670
+ const redirectUrl = error.data?.redirectUrl;
671
+ this.router.navigate(['/auth/login'], { queryParams: redirectUrl ? { redirectUrl: redirectUrl } : null });
672
+ }
673
+ else {
674
+ if (isDevMode()) {
675
+ next(error); // Pass the error to the next handler
676
+ }
677
+ else {
678
+ next(error); // maybe we need a error page
679
+ }
680
+ }
681
+ this.layoutService.setNavigationLoading(false);
682
+ }
683
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthErrorHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
684
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthErrorHandler }); }
685
+ }
686
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthErrorHandler, decorators: [{
687
+ type: Injectable
688
+ }] });
689
+
690
+ /**
691
+ * Guard for login page. Clears incomplete sessions so a prior OAuth attempt
692
+ * does not leak into the next username/password sign-in.
693
+ */
694
+ const canActivateLogin = async (route, state) => {
695
+ const sessionService = inject(AXPSessionService);
696
+ const session = sessionService.getSessionData();
697
+ if (session?.user?.id && (!session.tenant?.id || !session.application?.id)) {
698
+ await sessionService.signout();
699
+ }
700
+ return true;
701
+ };
702
+
703
+ var AXMAuthenticationTypes;
704
+ (function (AXMAuthenticationTypes) {
705
+ AXMAuthenticationTypes[AXMAuthenticationTypes["UsernamePassword"] = 0] = "UsernamePassword";
706
+ AXMAuthenticationTypes[AXMAuthenticationTypes["UsernameEmailPassword"] = 1] = "UsernameEmailPassword";
707
+ AXMAuthenticationTypes[AXMAuthenticationTypes["EmailPassword"] = 2] = "EmailPassword";
708
+ AXMAuthenticationTypes[AXMAuthenticationTypes["MobilePassword"] = 3] = "MobilePassword";
709
+ AXMAuthenticationTypes[AXMAuthenticationTypes["EmailCode"] = 4] = "EmailCode";
710
+ AXMAuthenticationTypes[AXMAuthenticationTypes["MobileCode"] = 5] = "MobileCode";
711
+ })(AXMAuthenticationTypes || (AXMAuthenticationTypes = {}));
712
+ const AXM_AUTH_CONFIG_TOKEN = new InjectionToken('app-module-auth-config', {
713
+ providedIn: 'root',
714
+ factory: () => {
715
+ return AXMDefaultAuthConfigs;
716
+ },
717
+ });
718
+ const AXMDefaultAuthConfigs = {
719
+ type: AXMAuthenticationTypes.UsernamePassword,
720
+ // defaultStrategyName:'oidc',
721
+ // حذف authConfig از اینجا چون از environment خوانده می‌شود
722
+ defaultStrategyName: 'user-pass',
723
+ };
724
+ function configAuthModule(config = AXMDefaultAuthConfigs) {
725
+ return { ...AXMDefaultAuthConfigs, ...config };
726
+ }
727
+ /**
728
+ * Injection token for login page guard.
729
+ * Apps can provide their own custom guard by providing this token.
730
+ * If not provided, the default guard (canActivateLogin) will be used.
731
+ */
732
+ const AXM_LOGIN_GUARD_TOKEN = new InjectionToken('AXM_LOGIN_GUARD_TOKEN', {
733
+ providedIn: 'root',
734
+ factory: () => canActivateLogin,
735
+ });
736
+ //#endregion
737
+
738
+ //#region ---- Types ----
739
+ const AXPAuthPasswordComplexityLevels = {
740
+ Low: 'Low',
741
+ Medium: 'Medium',
742
+ High: 'High',
743
+ };
744
+ //#endregion
745
+ //#region ---- Policy ----
746
+ const PASSWORD_COMPLEXITY_POLICIES = {
747
+ [AXPAuthPasswordComplexityLevels.Low]: {
748
+ minLength: 6,
749
+ requireUppercase: false,
750
+ requireLowercase: false,
751
+ requireDigit: false,
752
+ requireSpecialCharacter: false,
753
+ },
754
+ [AXPAuthPasswordComplexityLevels.Medium]: {
755
+ minLength: 8,
756
+ requireUppercase: true,
757
+ requireLowercase: true,
758
+ requireDigit: true,
759
+ requireSpecialCharacter: false,
760
+ },
761
+ [AXPAuthPasswordComplexityLevels.High]: {
762
+ minLength: 12,
763
+ requireUppercase: true,
764
+ requireLowercase: true,
765
+ requireDigit: true,
766
+ requireSpecialCharacter: true,
767
+ },
768
+ };
769
+ const SPECIAL_CHARACTER_PATTERN = /[^a-zA-Z0-9]/;
770
+ //#endregion
771
+ //#region ---- Utility Methods ----
772
+ /**
773
+ * Normalizes tenant password complexity setting values to a supported level.
774
+ */
775
+ function normalizePasswordComplexityLevel(value) {
776
+ if (isNil(value)) {
777
+ return AXPAuthPasswordComplexityLevels.Medium;
778
+ }
779
+ const normalized = coerceUnknownToTrimmedString(value).toLowerCase();
780
+ if (normalized === 'low') {
781
+ return AXPAuthPasswordComplexityLevels.Low;
782
+ }
783
+ if (normalized === 'medium') {
784
+ return AXPAuthPasswordComplexityLevels.Medium;
785
+ }
786
+ if (normalized === 'high') {
787
+ return AXPAuthPasswordComplexityLevels.High;
788
+ }
789
+ return AXPAuthPasswordComplexityLevels.Medium;
790
+ }
791
+ /**
792
+ * Returns the validation policy for the given complexity level.
793
+ */
794
+ function getPasswordComplexityPolicy(level) {
795
+ return PASSWORD_COMPLEXITY_POLICIES[level];
796
+ }
797
+ /**
798
+ * Validates a password against the configured complexity policy.
799
+ */
800
+ function validatePasswordAgainstComplexity(password, level) {
801
+ const policy = getPasswordComplexityPolicy(level);
802
+ if (password.length < policy.minLength) {
803
+ return false;
804
+ }
805
+ if (policy.requireUppercase && !/[A-Z]/.test(password)) {
806
+ return false;
807
+ }
808
+ if (policy.requireLowercase && !/[a-z]/.test(password)) {
809
+ return false;
810
+ }
811
+ if (policy.requireDigit && !/\d/.test(password)) {
812
+ return false;
813
+ }
814
+ if (policy.requireSpecialCharacter && !SPECIAL_CHARACTER_PATTERN.test(password)) {
815
+ return false;
816
+ }
817
+ return true;
818
+ }
819
+ /**
820
+ * Resolves the i18n key for the password complexity hint shown under the new password field.
821
+ */
822
+ function getPasswordComplexityHintKey(level) {
823
+ return `@auth:profile-page.commands.change-password.complexity.hint.${level.toLowerCase()}`;
824
+ }
825
+ /**
826
+ * Resolves the i18n key for the password complexity validation error message.
827
+ */
828
+ function getPasswordComplexityValidationMessageKey(level) {
829
+ return `@auth:profile-page.commands.change-password.complexity.validation.${level.toLowerCase()}`;
830
+ }
831
+ //#endregion
832
+
833
+ //#region ---- Password Complexity Service ----
834
+ /**
835
+ * Resolves tenant password complexity policy for auth flows (change password, registration, etc.).
836
+ * Uses tenant scope explicitly so inherited user-scope snapshots do not override tenant policy.
837
+ */
838
+ class AXPAuthPasswordComplexityService {
839
+ constructor() {
840
+ //#region ---- Services & Dependencies ----
841
+ this.settingsService = inject(AXPSettingsService);
842
+ }
843
+ //#endregion
844
+ //#region ---- Public Methods ----
845
+ async getLevel() {
846
+ const tenantValue = await this.settingsService
847
+ .scope(AXPPlatformScope.Tenant)
848
+ .get(AXPAuthSettings.PasswordComplexity);
849
+ if (tenantValue !== undefined && tenantValue !== null) {
850
+ return normalizePasswordComplexityLevel(tenantValue);
851
+ }
852
+ const effectiveValue = await this.settingsService.get(AXPAuthSettings.PasswordComplexity);
853
+ return normalizePasswordComplexityLevel(effectiveValue);
854
+ }
855
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthPasswordComplexityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
856
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthPasswordComplexityService, providedIn: 'root' }); }
857
+ }
858
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthPasswordComplexityService, decorators: [{
859
+ type: Injectable,
860
+ args: [{ providedIn: 'root' }]
861
+ }] });
862
+
863
+ /**
864
+ * Validates passwords against tenant-configured complexity levels (Low, Medium, High).
865
+ */
866
+ class PasswordComplexityValidationRule {
867
+ constructor() {
868
+ //#region ---- Services & Dependencies ----
869
+ this.passwordComplexityService = inject(AXPAuthPasswordComplexityService);
870
+ }
871
+ //#endregion
872
+ get name() {
873
+ return 'password-complexity';
874
+ }
875
+ async validate(value, options) {
876
+ const complexity = await this.resolveComplexity(options?.complexity);
877
+ const message = options?.message ?? '';
878
+ if (!isString(value) || value.length === 0) {
879
+ return {
880
+ rule: this.name,
881
+ result: false,
882
+ message,
883
+ value,
884
+ };
885
+ }
886
+ const isValid = validatePasswordAgainstComplexity(value, complexity);
887
+ return {
888
+ rule: this.name,
889
+ result: isValid,
890
+ message: isValid ? '' : message,
891
+ value,
892
+ };
893
+ }
894
+ //#endregion
895
+ //#region ---- Utility Methods ----
896
+ async resolveComplexity(override) {
897
+ if (override !== undefined && override !== null && String(override).trim() !== '') {
898
+ return normalizePasswordComplexityLevel(override);
899
+ }
900
+ return this.passwordComplexityService.getLevel();
901
+ }
902
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PasswordComplexityValidationRule, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
903
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PasswordComplexityValidationRule }); }
904
+ }
905
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PasswordComplexityValidationRule, decorators: [{
906
+ type: Injectable
907
+ }] });
908
+
909
+ //#region ---- Auth Validation Rules Module ----
910
+ class AXMAuthValidationRulesModule {
911
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthValidationRulesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
912
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthValidationRulesModule, imports: [i1.AXValidationModule] }); }
913
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthValidationRulesModule, imports: [AXValidationModule.forChild({
914
+ rules: [PasswordComplexityValidationRule],
915
+ })] }); }
916
+ }
917
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthValidationRulesModule, decorators: [{
918
+ type: NgModule,
919
+ args: [{
920
+ imports: [
921
+ AXValidationModule.forChild({
922
+ rules: [PasswordComplexityValidationRule],
923
+ }),
924
+ ],
925
+ }]
926
+ }] });
927
+
928
+ const routes = [
929
+ {
930
+ path: 'auth',
931
+ children: [
932
+ {
933
+ path: '',
934
+ pathMatch: 'full',
935
+ redirectTo: 'login',
936
+ },
937
+ {
938
+ path: '',
939
+ loadComponent: () => import('./acorex-platform-module-auth-client-master.layout-DpdD9kgk.mjs').then((c) => c.AXPAuthMasterLayoutComponent),
940
+ loadChildren: () => import('./acorex-platform-module-auth-client-login.module-DYtNYzF6.mjs').then((c) => c.AXPSignInModule),
941
+ },
942
+ {
943
+ path: '',
944
+ loadComponent: () => import('./acorex-platform-module-auth-client-master.layout-DpdD9kgk.mjs').then((c) => c.AXPAuthMasterLayoutComponent),
945
+ loadChildren: () => import('./acorex-platform-module-auth-client-two-factor.module-BJqdeuAz.mjs').then((c) => c.AXPTwoFactorModule),
946
+ },
947
+ {
948
+ path: '',
949
+ loadComponent: () => import('./acorex-platform-module-auth-client-master.layout-DpdD9kgk.mjs').then((c) => c.AXPAuthMasterLayoutComponent),
950
+ loadChildren: () => import('./acorex-platform-module-auth-client-routes-REjaWcqY.mjs').then((c) => c),
951
+ },
952
+ {
953
+ path: 'axp-oauth-callback',
954
+ loadComponent: () => import('./acorex-platform-module-auth-client-oauth-callback.component-BspBH4Ul.mjs').then((c) => c.AXPOAuthCallbackComponent),
955
+ },
956
+ {
957
+ path: 'account',
958
+ loadComponent: () => import('./acorex-platform-module-auth-client-blank.layout-BRGgntib.mjs').then((c) => c.AXPAuthBlankLayoutComponent),
959
+ loadChildren: () => Promise.resolve().then(function () { return account_module; }).then((c) => c.AXPAccountModule),
960
+ },
961
+ {
962
+ path: '',
963
+ loadComponent: () => import('./acorex-platform-module-auth-client-master.layout-DpdD9kgk.mjs').then((c) => c.AXPAuthMasterLayoutComponent),
964
+ loadChildren: () => import('./acorex-platform-module-auth-client-register.module-DXm6AQpo.mjs').then((c) => c.AXPRegisterModule),
965
+ },
966
+ ],
967
+ },
968
+ ];
969
+
970
+ class AXPAppChooserListComponent {
971
+ //#endregion
972
+ //#region ---- Computed Properties ----
973
+ isApplicationSelected(application) {
974
+ return application.id === this.sessionService.application?.id;
975
+ }
976
+ applicationLogoKey(application) {
977
+ return axpApplicationLogoKey(application.id);
978
+ }
979
+ //#endregion
980
+ //#region ---- Constructor ----
981
+ constructor() {
982
+ this.entityService = inject(AXPEntityService);
983
+ //#region ---- Services & Dependencies ----
984
+ this.sessionService = inject(AXPSessionService);
985
+ this.homePageService = inject(AXPHomePageService);
986
+ this.authStrategyRegistry = inject(AXPAuthStrategyRegistryService);
987
+ this.logoRegistration = inject(AXPEntityLogoRegistrationService);
988
+ this.applicationService = this.entityService.withEntity(RootConfig$2.module.name, RootConfig$2.entities.application.name).data();
989
+ this.strategyName = this.sessionService.getSessionData()?.strategy;
990
+ this.strategy = this.authStrategyRegistry.get(this.strategyName);
991
+ //#endregion
992
+ //#region ---- Inputs & Outputs ----
993
+ this.simple = input(false, ...(ngDevMode ? [{ debugName: "simple" }] : /* istanbul ignore next */ []));
994
+ this.applicationChosen = output();
995
+ //#endregion
996
+ //#region ---- View References ----
997
+ this.listNav = viewChild(AXListNavigationDirective, ...(ngDevMode ? [{ debugName: "listNav" }] : /* istanbul ignore next */ []));
998
+ this.listItems = viewChildren(AXListNavigationItemDirective, ...(ngDevMode ? [{ debugName: "listItems" }] : /* istanbul ignore next */ []));
999
+ //#endregion
1000
+ //#region ---- State ----
1001
+ this.isLoading = signal(true, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
1002
+ this.allApplications = signal([], ...(ngDevMode ? [{ debugName: "allApplications" }] : /* istanbul ignore next */ []));
1003
+ this.useSelectBox = computed(() => this.allApplications().length > 3, ...(ngDevMode ? [{ debugName: "useSelectBox" }] : /* istanbul ignore next */ []));
1004
+ this.selectedApplication = signal(null, ...(ngDevMode ? [{ debugName: "selectedApplication" }] : /* istanbul ignore next */ []));
1005
+ this.isSelectBoxOpen = signal(false, ...(ngDevMode ? [{ debugName: "isSelectBoxOpen" }] : /* istanbul ignore next */ []));
1006
+ this.canApply = computed(() => !!this.selectedApplication(), ...(ngDevMode ? [{ debugName: "canApply" }] : /* istanbul ignore next */ []));
1007
+ this.skeletonItems = [1, 2, 3];
1008
+ effect(() => {
1009
+ if (!this.isLoading() && !this.useSelectBox() && this.allApplications().length > 0) {
1010
+ queueMicrotask(() => this.focusFirstListItem());
1011
+ }
1012
+ });
1013
+ }
1014
+ //#endregion
1015
+ //#region ---- Lifecycle Methods ----
1016
+ ngOnInit() {
1017
+ this.sessionService.applications$.pipe(first$1()).subscribe((applications) => {
1018
+ this.logoRegistration.registerApplications(applications);
1019
+ this.allApplications.set(applications);
1020
+ if (applications.length > 0) {
1021
+ this.selectedApplication.set(applications[0]);
1022
+ }
1023
+ this.isLoading.set(false);
1024
+ });
1025
+ }
1026
+ //#endregion
1027
+ //#region ---- UI Handlers ----
1028
+ handleApplicationSelect(event) {
1029
+ if (!event.isUserInteraction) {
1030
+ return;
1031
+ }
1032
+ this.selectedApplication.set(event.component.selectedItems[0] ?? null);
1033
+ }
1034
+ handleListItemKeyDown(event, application) {
1035
+ if (event.key === 'Enter' || event.key === ' ') {
1036
+ event.preventDefault();
1037
+ void this.chooseApplication(application);
1038
+ }
1039
+ }
1040
+ handleSelectBoxKeyDown(event) {
1041
+ if (!(event instanceof KeyboardEvent)) {
1042
+ return;
1043
+ }
1044
+ if (event.key !== 'Enter' && event.key !== 'NumpadEnter') {
1045
+ return;
1046
+ }
1047
+ if (this.isSelectBoxOpen()) {
1048
+ return;
1049
+ }
1050
+ event.preventDefault();
1051
+ event.stopPropagation();
1052
+ if (this.canApply()) {
1053
+ this.applySelection();
1054
+ }
1055
+ }
1056
+ applySelection() {
1057
+ const application = this.selectedApplication();
1058
+ if (application) {
1059
+ void this.chooseApplication(application);
1060
+ }
1061
+ }
1062
+ async chooseApplication(item) {
1063
+ const tenantId = this.sessionService.tenant?.id || '';
1064
+ if (!tenantId) {
1065
+ throw new Error('Tenant ID is missing. Cannot choose application.');
1066
+ }
1067
+ const fullApplication = await this.applicationService.byKey(item.id);
1068
+ if (!fullApplication) {
1069
+ throw new Error(`Application not found: ${item.id}`);
1070
+ }
1071
+ let selectedEdition = item.edition;
1072
+ if (!selectedEdition && fullApplication.editions && fullApplication.editions.length > 0) {
1073
+ selectedEdition = {
1074
+ id: fullApplication.editions[0].id,
1075
+ title: fullApplication.editions[0].title,
1076
+ description: fullApplication.editions[0].description,
1077
+ };
1078
+ }
1079
+ const applicationWithEdition = {
1080
+ ...item,
1081
+ version: fullApplication.version || item.version,
1082
+ edition: selectedEdition,
1083
+ };
1084
+ if (this.strategy && typeof this.strategy.updateToken === 'function') {
1085
+ const updateContext = {
1086
+ application: applicationWithEdition,
1087
+ user: this.sessionService.user,
1088
+ tenant: this.sessionService.tenant,
1089
+ };
1090
+ await this.sessionService.updateToken(updateContext);
1091
+ }
1092
+ this.applicationChosen.emit();
1093
+ await this.homePageService.navigateTo();
1094
+ }
1095
+ //#endregion
1096
+ //#region ---- Utility Methods ----
1097
+ focusFirstListItem() {
1098
+ const nav = this.listNav();
1099
+ const items = this.listItems();
1100
+ const applications = this.allApplications();
1101
+ const currentIndex = applications.findIndex((application) => this.isApplicationSelected(application));
1102
+ if (nav && items.length > 0) {
1103
+ nav.navigateTo(items[currentIndex >= 0 ? currentIndex : 0]);
1104
+ }
1105
+ }
1106
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAppChooserListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1107
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPAppChooserListComponent, isStandalone: true, selector: "axp-app-chooser-list", inputs: { simple: { classPropertyName: "simple", publicName: "simple", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { applicationChosen: "applicationChosen" }, viewQueries: [{ propertyName: "listNav", first: true, predicate: AXListNavigationDirective, descendants: true, isSignal: true }, { propertyName: "listItems", predicate: AXListNavigationItemDirective, descendants: true, isSignal: true }], ngImport: i0, template: "@if (isLoading()) {\n <div class=\"w-full flex flex-col gap-3\">\n @for (item of skeletonItems; track item) {\n <div class=\"flex gap-2 items-center p-2\">\n <ax-skeleton [animated]=\"true\" class=\"w-12 h-12 rounded-full shrink-0\"></ax-skeleton>\n <ax-skeleton [animated]=\"true\" class=\"flex-1 h-6 rounded-md\"></ax-skeleton>\n </div>\n }\n </div>\n} @else if (useSelectBox()) {\n <div class=\"w-full flex flex-col gap-4\" (keydown.capture)=\"handleSelectBoxKeyDown($event)\">\n <ax-select-box\n class=\"w-full\"\n [dataSource]=\"allApplications()\"\n textField=\"title\"\n valueField=\"id\"\n [itemHeight]=\"56\"\n [value]=\"selectedApplication()\"\n [placeholder]=\"('@auth:application-chooser.select-placeholder' | translate | async) ?? ''\"\n [itemTemplate]=\"applicationItemTemplate\"\n (onOpened)=\"isSelectBoxOpen.set(true)\"\n (onClosed)=\"isSelectBoxOpen.set(false)\"\n (onValueChanged)=\"handleApplicationSelect($event)\"\n >\n <ax-search-box look=\"fill\"></ax-search-box>\n </ax-select-box>\n <ax-button\n class=\"w-full\"\n color=\"primary\"\n [disabled]=\"!canApply()\"\n [text]=\"('@general:actions.apply.title' | translate | async) ?? ''\"\n (onClick)=\"applySelection()\"\n ></ax-button>\n </div>\n} @else {\n <div\n axListNavigation\n #listNav=\"axListNavigation\"\n class=\"w-full overflow-auto max-h-[300px] flex flex-col gap-1\"\n role=\"listbox\"\n >\n @for (application of allApplications(); track application.id) {\n <div\n axListNavigationItem\n #navItem=\"axListNavigationItem\"\n tabindex=\"0\"\n role=\"option\"\n [attr.aria-selected]=\"isApplicationSelected(application)\"\n class=\"flex gap-2 items-center p-2 cursor-pointer rounded-md transition-colors outline-none hover:bg-primary-lightest hover:dark:bg-primary-dark\"\n [class.bg-primary-lighter]=\"isApplicationSelected(application)\"\n [class.dark:bg-primary-darker]=\"isApplicationSelected(application)\"\n [class.text-primary-on-lighter]=\"isApplicationSelected(application)\"\n [class.dark:text-primary-on-darker]=\"isApplicationSelected(application)\"\n [class.ring-2]=\"navItem.isActive()\"\n [class.ring-primary]=\"navItem.isActive()\"\n (click)=\"chooseApplication(application)\"\n (keydown)=\"handleListItemKeyDown($event, application)\"\n >\n <div class=\"flex flex-1 gap-2 items-center min-w-0\">\n <div\n class=\"shrink-0 flex justify-center items-center rounded-full bg-neutral-100\"\n [class.w-10]=\"simple()\"\n [class.h-10]=\"simple()\"\n [class.w-12]=\"!simple()\"\n [class.h-12]=\"!simple()\"\n >\n <axp-logo\n [logoKey]=\"applicationLogoKey(application)\"\n appearance=\"colored\"\n [size]=\"40\"\n [attr.alt]=\"application.title\"\n class=\"flex! items-center justify-center text-sm! bg-neutral-200 dark:bg-neutral-800 rounded-full\"\n [class.w-8]=\"simple()\"\n [class.h-8]=\"simple()\"\n [class.w-10]=\"!simple()\"\n [class.h-10]=\"!simple()\"\n ></axp-logo>\n </div>\n <span class=\"flex-1 truncate text-start md:text-base text-sm\">{{ application.title }}</span>\n @if (application.edition?.title) {\n <span class=\"text-muted text-xs shrink-0\">{{ application.edition?.title }}</span>\n }\n </div>\n </div>\n }\n </div>\n}\n\n<ng-template #applicationItemTemplate let-item>\n <div class=\"flex gap-2 items-center p-2\">\n <div\n class=\"w-10 h-10 flex justify-center items-center rounded-full bg-neutral-100 shrink-0\"\n >\n <axp-logo\n [logoKey]=\"applicationLogoKey(item.data)\"\n appearance=\"colored\"\n [size]=\"32\"\n [attr.alt]=\"item.data.title\"\n class=\"flex! items-center justify-center text-sm! w-8 h-8 bg-neutral-200 rounded-full\"\n ></axp-logo>\n </div>\n <span class=\"flex-1 text-start md:text-base text-sm\">{{ item.data.title }}</span>\n @if (item.data.edition?.title) {\n <span class=\"text-muted text-xs\">{{ item.data.edition.title }}</span>\n }\n </div>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i2.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "ngmodule", type: AXSelectBoxModule }, { kind: "component", type: i3.AXSelectBoxComponent, selector: "ax-select-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "minValue", "maxValue", "value", "state", "name", "id", "type", "look", "multiple", "valueField", "textField", "disabledField", "textTemplate", "selectedItems", "isItemTruncated", "showItemTooltip", "itemHeight", "maxVisibleItems", "dataSource", "minRecordsForSearch", "caption", "itemTemplate", "selectedTemplate", "emptyTemplate", "loadingTemplate", "dropdownWidth", "searchBoxAutoFocus"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onOpened", "onClosed", "onItemSelected", "onItemClick"] }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i4.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXListNavigationModule }, { kind: "directive", type: i5.AXListNavigationDirective, selector: "[axListNavigation]", inputs: ["orientation"], outputs: ["onNavigationChanged", "onKeypress"], exportAs: ["axListNavigation"] }, { kind: "directive", type: i5.AXListNavigationItemDirective, selector: "[axListNavigationItem]", outputs: ["onKeypress"], exportAs: ["axListNavigationItem"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPLogoComponent, selector: "axp-logo", inputs: ["logoKey", "appearance", "size", "format"] }, { kind: "pipe", type: i4$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1108
+ }
1109
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAppChooserListComponent, decorators: [{
1110
+ type: Component,
1111
+ args: [{ selector: 'axp-app-chooser-list', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
1112
+ CommonModule,
1113
+ AXButtonModule,
1114
+ AXDecoratorModule,
1115
+ AXSkeletonModule,
1116
+ AXSelectBoxModule,
1117
+ AXSearchBoxModule,
1118
+ AXListNavigationModule,
1119
+ AXTranslationModule,
1120
+ AXPLogoComponent,
1121
+ ], template: "@if (isLoading()) {\n <div class=\"w-full flex flex-col gap-3\">\n @for (item of skeletonItems; track item) {\n <div class=\"flex gap-2 items-center p-2\">\n <ax-skeleton [animated]=\"true\" class=\"w-12 h-12 rounded-full shrink-0\"></ax-skeleton>\n <ax-skeleton [animated]=\"true\" class=\"flex-1 h-6 rounded-md\"></ax-skeleton>\n </div>\n }\n </div>\n} @else if (useSelectBox()) {\n <div class=\"w-full flex flex-col gap-4\" (keydown.capture)=\"handleSelectBoxKeyDown($event)\">\n <ax-select-box\n class=\"w-full\"\n [dataSource]=\"allApplications()\"\n textField=\"title\"\n valueField=\"id\"\n [itemHeight]=\"56\"\n [value]=\"selectedApplication()\"\n [placeholder]=\"('@auth:application-chooser.select-placeholder' | translate | async) ?? ''\"\n [itemTemplate]=\"applicationItemTemplate\"\n (onOpened)=\"isSelectBoxOpen.set(true)\"\n (onClosed)=\"isSelectBoxOpen.set(false)\"\n (onValueChanged)=\"handleApplicationSelect($event)\"\n >\n <ax-search-box look=\"fill\"></ax-search-box>\n </ax-select-box>\n <ax-button\n class=\"w-full\"\n color=\"primary\"\n [disabled]=\"!canApply()\"\n [text]=\"('@general:actions.apply.title' | translate | async) ?? ''\"\n (onClick)=\"applySelection()\"\n ></ax-button>\n </div>\n} @else {\n <div\n axListNavigation\n #listNav=\"axListNavigation\"\n class=\"w-full overflow-auto max-h-[300px] flex flex-col gap-1\"\n role=\"listbox\"\n >\n @for (application of allApplications(); track application.id) {\n <div\n axListNavigationItem\n #navItem=\"axListNavigationItem\"\n tabindex=\"0\"\n role=\"option\"\n [attr.aria-selected]=\"isApplicationSelected(application)\"\n class=\"flex gap-2 items-center p-2 cursor-pointer rounded-md transition-colors outline-none hover:bg-primary-lightest hover:dark:bg-primary-dark\"\n [class.bg-primary-lighter]=\"isApplicationSelected(application)\"\n [class.dark:bg-primary-darker]=\"isApplicationSelected(application)\"\n [class.text-primary-on-lighter]=\"isApplicationSelected(application)\"\n [class.dark:text-primary-on-darker]=\"isApplicationSelected(application)\"\n [class.ring-2]=\"navItem.isActive()\"\n [class.ring-primary]=\"navItem.isActive()\"\n (click)=\"chooseApplication(application)\"\n (keydown)=\"handleListItemKeyDown($event, application)\"\n >\n <div class=\"flex flex-1 gap-2 items-center min-w-0\">\n <div\n class=\"shrink-0 flex justify-center items-center rounded-full bg-neutral-100\"\n [class.w-10]=\"simple()\"\n [class.h-10]=\"simple()\"\n [class.w-12]=\"!simple()\"\n [class.h-12]=\"!simple()\"\n >\n <axp-logo\n [logoKey]=\"applicationLogoKey(application)\"\n appearance=\"colored\"\n [size]=\"40\"\n [attr.alt]=\"application.title\"\n class=\"flex! items-center justify-center text-sm! bg-neutral-200 dark:bg-neutral-800 rounded-full\"\n [class.w-8]=\"simple()\"\n [class.h-8]=\"simple()\"\n [class.w-10]=\"!simple()\"\n [class.h-10]=\"!simple()\"\n ></axp-logo>\n </div>\n <span class=\"flex-1 truncate text-start md:text-base text-sm\">{{ application.title }}</span>\n @if (application.edition?.title) {\n <span class=\"text-muted text-xs shrink-0\">{{ application.edition?.title }}</span>\n }\n </div>\n </div>\n }\n </div>\n}\n\n<ng-template #applicationItemTemplate let-item>\n <div class=\"flex gap-2 items-center p-2\">\n <div\n class=\"w-10 h-10 flex justify-center items-center rounded-full bg-neutral-100 shrink-0\"\n >\n <axp-logo\n [logoKey]=\"applicationLogoKey(item.data)\"\n appearance=\"colored\"\n [size]=\"32\"\n [attr.alt]=\"item.data.title\"\n class=\"flex! items-center justify-center text-sm! w-8 h-8 bg-neutral-200 rounded-full\"\n ></axp-logo>\n </div>\n <span class=\"flex-1 text-start md:text-base text-sm\">{{ item.data.title }}</span>\n @if (item.data.edition?.title) {\n <span class=\"text-muted text-xs\">{{ item.data.edition.title }}</span>\n }\n </div>\n</ng-template>\n" }]
1122
+ }], ctorParameters: () => [], propDecorators: { simple: [{ type: i0.Input, args: [{ isSignal: true, alias: "simple", required: false }] }], applicationChosen: [{ type: i0.Output, args: ["applicationChosen"] }], listNav: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXListNavigationDirective), { isSignal: true }] }], listItems: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => AXListNavigationItemDirective), { isSignal: true }] }] } });
1123
+
1124
+ class AXMAppChooserSlotComponent {
1125
+ constructor() {
1126
+ this.sessionService = inject(AXPSessionService);
1127
+ this.applications$ = this.sessionService.applications$;
1128
+ this.appPopover = viewChild('appPopover', ...(ngDevMode ? [{ debugName: "appPopover" }] : /* istanbul ignore next */ []));
1129
+ }
1130
+ closePopover() {
1131
+ this.appPopover()?.close();
1132
+ }
1133
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAppChooserSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1134
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXMAppChooserSlotComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "appPopover", first: true, predicate: ["appPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (((applications$ | async)?.length ?? 0) > 1) {\n <ax-button color=\"primary\" #appChooser>\n <ax-icon class=\"fa-regular fa-grid-2\"> </ax-icon>\n </ax-button>\n <ax-popover [target]=\"appChooser\" [openOn]=\"'toggle'\" [closeOn]=\"'clickOut'\" #appPopover>\n <div class=\"bg-lightest border overflow-hidden rounded-md shadow-md w-full min-w-64 p-1\">\n <axp-app-chooser-list [simple]=\"true\" (applicationChosen)=\"closePopover()\"></axp-app-chooser-list>\n </div>\n </ax-popover>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2$2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: AXPAppChooserListComponent, selector: "axp-app-chooser-list", inputs: ["simple"], outputs: ["applicationChosen"] }, { kind: "pipe", type: i4$1.AsyncPipe, name: "async" }] }); }
1135
+ }
1136
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAppChooserSlotComponent, decorators: [{
1137
+ type: Component,
1138
+ args: [{ imports: [CommonModule, AXButtonModule, AXPopoverModule, AXDecoratorModule, AXPAppChooserListComponent], template: "@if (((applications$ | async)?.length ?? 0) > 1) {\n <ax-button color=\"primary\" #appChooser>\n <ax-icon class=\"fa-regular fa-grid-2\"> </ax-icon>\n </ax-button>\n <ax-popover [target]=\"appChooser\" [openOn]=\"'toggle'\" [closeOn]=\"'clickOut'\" #appPopover>\n <div class=\"bg-lightest border overflow-hidden rounded-md shadow-md w-full min-w-64 p-1\">\n <axp-app-chooser-list [simple]=\"true\" (applicationChosen)=\"closePopover()\"></axp-app-chooser-list>\n </div>\n </ax-popover>\n}\n" }]
1139
+ }], propDecorators: { appPopover: [{ type: i0.ViewChild, args: ['appPopover', { isSignal: true }] }] } });
1140
+
1141
+ class AXPTenantChooserListComponent {
1142
+ tenantLogoKey(tenant) {
1143
+ return axpTenantLogoKey(tenant.id);
1144
+ }
1145
+ //#endregion
1146
+ //#region ---- Computed Properties ----
1147
+ isTenantSelected(tenant) {
1148
+ return tenant.id === this.sessionService.tenant?.id;
1149
+ }
1150
+ //#endregion
1151
+ //#region ---- Constructor ----
1152
+ constructor() {
1153
+ //#region ---- Services & Dependencies ----
1154
+ this.sessionService = inject(AXPSessionService);
1155
+ this.authStrategyRegistry = inject(AXPAuthStrategyRegistryService);
1156
+ this.logoRegistration = inject(AXPEntityLogoRegistrationService);
1157
+ this.router = inject(Router);
1158
+ this.strategyName = this.sessionService.getSessionData()?.strategy;
1159
+ this.strategy = this.authStrategyRegistry.get(this.strategyName);
1160
+ //#endregion
1161
+ //#region ---- Inputs & Outputs ----
1162
+ this.simple = input(false, ...(ngDevMode ? [{ debugName: "simple" }] : /* istanbul ignore next */ []));
1163
+ this.tenantChosen = output();
1164
+ //#endregion
1165
+ //#region ---- View References ----
1166
+ this.listNav = viewChild(AXListNavigationDirective, ...(ngDevMode ? [{ debugName: "listNav" }] : /* istanbul ignore next */ []));
1167
+ this.listItems = viewChildren(AXListNavigationItemDirective, ...(ngDevMode ? [{ debugName: "listItems" }] : /* istanbul ignore next */ []));
1168
+ //#endregion
1169
+ //#region ---- State ----
1170
+ this.isLoading = signal(true, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
1171
+ this.allTenants = signal([], ...(ngDevMode ? [{ debugName: "allTenants" }] : /* istanbul ignore next */ []));
1172
+ this.searchValue = signal('', ...(ngDevMode ? [{ debugName: "searchValue" }] : /* istanbul ignore next */ []));
1173
+ this.showAll = signal(false, ...(ngDevMode ? [{ debugName: "showAll" }] : /* istanbul ignore next */ []));
1174
+ this.useSelectBox = computed(() => this.allTenants().length > 3, ...(ngDevMode ? [{ debugName: "useSelectBox" }] : /* istanbul ignore next */ []));
1175
+ this.selectedTenant = signal(null, ...(ngDevMode ? [{ debugName: "selectedTenant" }] : /* istanbul ignore next */ []));
1176
+ this.isSelectBoxOpen = signal(false, ...(ngDevMode ? [{ debugName: "isSelectBoxOpen" }] : /* istanbul ignore next */ []));
1177
+ this.canApply = computed(() => !!this.selectedTenant(), ...(ngDevMode ? [{ debugName: "canApply" }] : /* istanbul ignore next */ []));
1178
+ this.skeletonItems = [1, 2, 3];
1179
+ /** Tenants shown in the list (respects search and view-all toggle). */
1180
+ this.displayedTenants = computed(() => {
1181
+ const tenants = this.filterTenants(this.allTenants());
1182
+ if (this.showAll() || tenants.length <= 4) {
1183
+ return tenants;
1184
+ }
1185
+ return tenants.slice(0, 4);
1186
+ }, ...(ngDevMode ? [{ debugName: "displayedTenants" }] : /* istanbul ignore next */ []));
1187
+ this.showSearchAndViewAll = computed(() => this.allTenants().length > 4, ...(ngDevMode ? [{ debugName: "showSearchAndViewAll" }] : /* istanbul ignore next */ []));
1188
+ effect(() => {
1189
+ if (!this.isLoading() && !this.useSelectBox() && !this.simple() && this.allTenants().length > 0) {
1190
+ queueMicrotask(() => this.focusFirstListItem());
1191
+ }
1192
+ });
1193
+ }
1194
+ //#endregion
1195
+ //#region ---- Lifecycle Methods ----
1196
+ ngOnInit() {
1197
+ this.sessionService.tenants$.pipe(first$1()).subscribe((tenants) => {
1198
+ this.logoRegistration.registerTenants(tenants);
1199
+ this.allTenants.set(tenants);
1200
+ if (tenants.length > 0) {
1201
+ this.selectedTenant.set(tenants[0]);
1202
+ }
1203
+ this.isLoading.set(false);
1204
+ });
1205
+ }
1206
+ //#endregion
1207
+ //#region ---- UI Handlers ----
1208
+ filterTenants(tenants) {
1209
+ const query = this.searchValue().toLowerCase();
1210
+ return tenants.filter((tenant) => tenant.title.toLowerCase().includes(query));
1211
+ }
1212
+ handleSearch(event) {
1213
+ if (event !== undefined) {
1214
+ this.searchValue.set(event);
1215
+ }
1216
+ }
1217
+ showAllTenants() {
1218
+ this.showAll.update((value) => !value);
1219
+ }
1220
+ handleTenantSelect(event) {
1221
+ if (!event.isUserInteraction) {
1222
+ return;
1223
+ }
1224
+ this.selectedTenant.set(event.component.selectedItems[0] ?? null);
1225
+ }
1226
+ handleListItemKeyDown(event, tenant) {
1227
+ if (event.key === 'Enter' || event.key === ' ') {
1228
+ event.preventDefault();
1229
+ void this.chooseTenant(tenant);
1230
+ }
1231
+ }
1232
+ handleSelectBoxKeyDown(event) {
1233
+ if (!(event instanceof KeyboardEvent)) {
1234
+ return;
1235
+ }
1236
+ if (event.key !== 'Enter' && event.key !== 'NumpadEnter') {
1237
+ return;
1238
+ }
1239
+ if (this.isSelectBoxOpen()) {
1240
+ return;
1241
+ }
1242
+ event.preventDefault();
1243
+ event.stopPropagation();
1244
+ if (this.canApply()) {
1245
+ this.applySelection();
1246
+ }
1247
+ }
1248
+ applySelection() {
1249
+ const tenant = this.selectedTenant();
1250
+ if (tenant) {
1251
+ void this.chooseTenant(tenant);
1252
+ }
1253
+ }
1254
+ async chooseTenant(item) {
1255
+ if (this.strategy) {
1256
+ const updateContext = {
1257
+ tenant: item,
1258
+ user: this.sessionService.user,
1259
+ };
1260
+ await this.sessionService.updateToken(updateContext);
1261
+ }
1262
+ this.tenantChosen.emit();
1263
+ return this.router.navigate(['/auth/account/app-chooser']);
1264
+ }
1265
+ //#endregion
1266
+ //#region ---- Utility Methods ----
1267
+ focusFirstListItem() {
1268
+ const nav = this.listNav();
1269
+ const items = this.listItems();
1270
+ const tenants = this.allTenants();
1271
+ const currentIndex = tenants.findIndex((tenant) => this.isTenantSelected(tenant));
1272
+ if (nav && items.length > 0) {
1273
+ nav.navigateTo(items[currentIndex >= 0 ? currentIndex : 0]);
1274
+ }
1275
+ }
1276
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTenantChooserListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1277
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPTenantChooserListComponent, isStandalone: true, selector: "axp-tenant-chooser-list", inputs: { simple: { classPropertyName: "simple", publicName: "simple", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tenantChosen: "tenantChosen" }, viewQueries: [{ propertyName: "listNav", first: true, predicate: AXListNavigationDirective, descendants: true, isSignal: true }, { propertyName: "listItems", predicate: AXListNavigationItemDirective, descendants: true, isSignal: true }], ngImport: i0, template: "@if (isLoading()) {\n <div class=\"w-full flex flex-col gap-3\">\n @for (item of skeletonItems; track item) {\n <div class=\"flex gap-2 items-center p-2\">\n <ax-skeleton [animated]=\"true\" class=\"w-12 h-12 rounded-full shrink-0\"></ax-skeleton>\n <ax-skeleton [animated]=\"true\" class=\"flex-1 h-6 rounded-md\"></ax-skeleton>\n </div>\n }\n </div>\n} @else if (simple()) {\n <div class=\"w-full overflow-auto max-h-[300px] pe-2\">\n @for (tenant of allTenants(); track tenant.id) {\n <div\n class=\"flex gap-2 justify-between border-b last:border-b-0 py-3 items-start cursor-pointer\"\n (click)=\"chooseTenant(tenant)\"\n >\n <div class=\"flex flex-1 gap-2 items-center font-medium\">\n <div\n class=\"w-12 h-12 flex justify-center items-center rounded-full bg-neutral-100 m-auto\"\n >\n <axp-logo\n [logoKey]=\"tenantLogoKey(tenant)\"\n appearance=\"colored\"\n [size]=\"40\"\n [attr.alt]=\"tenant.title\"\n class=\"flex! items-center justify-center text-sm! mx-auto w-10 h-10 bg-neutral-200 rounded-full\"\n ></axp-logo>\n </div>\n <span class=\"flex-1 text-start md:text-base text-sm\">{{ tenant.title }}</span>\n </div>\n </div>\n }\n </div>\n} @else if (useSelectBox()) {\n <div class=\"w-full flex flex-col gap-4\" (keydown.capture)=\"handleSelectBoxKeyDown($event)\">\n <ax-select-box\n class=\"w-full\"\n [dataSource]=\"allTenants()\"\n textField=\"title\"\n valueField=\"id\"\n [value]=\"selectedTenant()\"\n [itemHeight]=\"56\"\n [placeholder]=\"('@auth:tenant-chooser.select-placeholder' | translate | async) ?? ''\"\n [itemTemplate]=\"tenantItemTemplate\"\n (onOpened)=\"isSelectBoxOpen.set(true)\"\n (onClosed)=\"isSelectBoxOpen.set(false)\"\n (onValueChanged)=\"handleTenantSelect($event)\"\n >\n <ax-search-box look=\"fill\"></ax-search-box>\n </ax-select-box>\n <ax-button\n class=\"w-full\"\n color=\"primary\"\n [disabled]=\"!canApply()\"\n [text]=\"('@general:actions.apply.title' | translate | async) ?? ''\"\n (onClick)=\"applySelection()\"\n ></ax-button>\n </div>\n} @else {\n @if (showSearchAndViewAll()) {\n <ax-search-box\n [placeholder]=\"'@general:search.title' | translate | async\"\n [ngModel]=\"searchValue()\"\n (ngModelChange)=\"handleSearch($event)\"\n class=\"w-full\"\n >\n <ax-clear-button></ax-clear-button>\n </ax-search-box>\n }\n <div\n axListNavigation\n #listNav=\"axListNavigation\"\n class=\"w-full overflow-auto max-h-[300px] flex flex-col gap-1\"\n role=\"listbox\"\n >\n @for (tenant of displayedTenants(); track tenant.id) {\n <div\n axListNavigationItem\n #navItem=\"axListNavigationItem\"\n tabindex=\"0\"\n role=\"option\"\n [attr.aria-selected]=\"isTenantSelected(tenant)\"\n class=\"flex gap-2 items-center p-2 cursor-pointer rounded-md transition-colors outline-none hover:bg-primary-lightest hover:dark:bg-primary-dark\"\n [class.bg-primary-lighter]=\"isTenantSelected(tenant)\"\n [class.dark:bg-primary-darker]=\"isTenantSelected(tenant)\"\n [class.text-primary-on-lighter]=\"isTenantSelected(tenant)\"\n [class.dark:text-primary-on-darker]=\"isTenantSelected(tenant)\"\n [class.ring-2]=\"navItem.isActive()\"\n [class.ring-primary]=\"navItem.isActive()\"\n (click)=\"chooseTenant(tenant)\"\n (keydown)=\"handleListItemKeyDown($event, tenant)\"\n >\n <div class=\"flex flex-1 gap-2 items-center min-w-0\">\n <div\n class=\"shrink-0 flex justify-center items-center rounded-full bg-neutral-100\"\n [class.w-10]=\"simple()\"\n [class.h-10]=\"simple()\"\n [class.w-12]=\"!simple()\"\n [class.h-12]=\"!simple()\"\n >\n <axp-logo\n [logoKey]=\"tenantLogoKey(tenant)\"\n appearance=\"colored\"\n [size]=\"40\"\n [attr.alt]=\"tenant.title\"\n class=\"flex! items-center justify-center text-sm! bg-neutral-200 dark:bg-neutral-800 rounded-full\"\n [class.w-8]=\"simple()\"\n [class.h-8]=\"simple()\"\n [class.w-10]=\"!simple()\"\n [class.h-10]=\"!simple()\"\n ></axp-logo>\n </div>\n <span class=\"flex-1 truncate text-start md:text-base text-sm\">{{ tenant.title }}</span>\n </div>\n </div>\n }\n </div>\n @if (showSearchAndViewAll()) {\n <div class=\"mt-6 w-full flex justify-center\">\n <ax-button\n (onClick)=\"showAllTenants()\"\n class=\"w-full\"\n look=\"outline\"\n [text]=\"\n showAll()\n ? ('@general:actions.view-less.title' | translate | async)\n : ('@general:actions.view-all.title' | translate | async)\n \"\n ></ax-button>\n </div>\n }\n}\n\n<ng-template #tenantItemTemplate let-item>\n <div class=\"flex gap-2 items-center p-2\">\n <div\n class=\"w-10 h-10 flex justify-center items-center rounded-full bg-neutral-100 shrink-0\"\n >\n <axp-logo\n [logoKey]=\"tenantLogoKey(item.data)\"\n appearance=\"colored\"\n [size]=\"32\"\n [attr.alt]=\"item.data.title\"\n class=\"flex! items-center justify-center text-sm! w-8 h-8 bg-neutral-200 rounded-full\"\n ></axp-logo>\n </div>\n <span class=\"text-start md:text-base text-sm\">{{ item.data.title }}</span>\n </div>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorClearButtonComponent, selector: "ax-clear-button", inputs: ["icon"] }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i2.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "ngmodule", type: AXSelectBoxModule }, { kind: "component", type: i3.AXSelectBoxComponent, selector: "ax-select-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "minValue", "maxValue", "value", "state", "name", "id", "type", "look", "multiple", "valueField", "textField", "disabledField", "textTemplate", "selectedItems", "isItemTruncated", "showItemTooltip", "itemHeight", "maxVisibleItems", "dataSource", "minRecordsForSearch", "caption", "itemTemplate", "selectedTemplate", "emptyTemplate", "loadingTemplate", "dropdownWidth", "searchBoxAutoFocus"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onOpened", "onClosed", "onItemSelected", "onItemClick"] }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i4.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXListNavigationModule }, { kind: "directive", type: i5.AXListNavigationDirective, selector: "[axListNavigation]", inputs: ["orientation"], outputs: ["onNavigationChanged", "onKeypress"], exportAs: ["axListNavigation"] }, { kind: "directive", type: i5.AXListNavigationItemDirective, selector: "[axListNavigationItem]", outputs: ["onKeypress"], exportAs: ["axListNavigationItem"] }, { kind: "component", type: AXPLogoComponent, selector: "axp-logo", inputs: ["logoKey", "appearance", "size", "format"] }, { kind: "pipe", type: i4$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1278
+ }
1279
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTenantChooserListComponent, decorators: [{
1280
+ type: Component,
1281
+ args: [{ selector: 'axp-tenant-chooser-list', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
1282
+ CommonModule,
1283
+ FormsModule,
1284
+ AXTranslationModule,
1285
+ AXButtonModule,
1286
+ AXDecoratorModule,
1287
+ AXSkeletonModule,
1288
+ AXSelectBoxModule,
1289
+ AXSearchBoxModule,
1290
+ AXListNavigationModule,
1291
+ AXPLogoComponent,
1292
+ ], template: "@if (isLoading()) {\n <div class=\"w-full flex flex-col gap-3\">\n @for (item of skeletonItems; track item) {\n <div class=\"flex gap-2 items-center p-2\">\n <ax-skeleton [animated]=\"true\" class=\"w-12 h-12 rounded-full shrink-0\"></ax-skeleton>\n <ax-skeleton [animated]=\"true\" class=\"flex-1 h-6 rounded-md\"></ax-skeleton>\n </div>\n }\n </div>\n} @else if (simple()) {\n <div class=\"w-full overflow-auto max-h-[300px] pe-2\">\n @for (tenant of allTenants(); track tenant.id) {\n <div\n class=\"flex gap-2 justify-between border-b last:border-b-0 py-3 items-start cursor-pointer\"\n (click)=\"chooseTenant(tenant)\"\n >\n <div class=\"flex flex-1 gap-2 items-center font-medium\">\n <div\n class=\"w-12 h-12 flex justify-center items-center rounded-full bg-neutral-100 m-auto\"\n >\n <axp-logo\n [logoKey]=\"tenantLogoKey(tenant)\"\n appearance=\"colored\"\n [size]=\"40\"\n [attr.alt]=\"tenant.title\"\n class=\"flex! items-center justify-center text-sm! mx-auto w-10 h-10 bg-neutral-200 rounded-full\"\n ></axp-logo>\n </div>\n <span class=\"flex-1 text-start md:text-base text-sm\">{{ tenant.title }}</span>\n </div>\n </div>\n }\n </div>\n} @else if (useSelectBox()) {\n <div class=\"w-full flex flex-col gap-4\" (keydown.capture)=\"handleSelectBoxKeyDown($event)\">\n <ax-select-box\n class=\"w-full\"\n [dataSource]=\"allTenants()\"\n textField=\"title\"\n valueField=\"id\"\n [value]=\"selectedTenant()\"\n [itemHeight]=\"56\"\n [placeholder]=\"('@auth:tenant-chooser.select-placeholder' | translate | async) ?? ''\"\n [itemTemplate]=\"tenantItemTemplate\"\n (onOpened)=\"isSelectBoxOpen.set(true)\"\n (onClosed)=\"isSelectBoxOpen.set(false)\"\n (onValueChanged)=\"handleTenantSelect($event)\"\n >\n <ax-search-box look=\"fill\"></ax-search-box>\n </ax-select-box>\n <ax-button\n class=\"w-full\"\n color=\"primary\"\n [disabled]=\"!canApply()\"\n [text]=\"('@general:actions.apply.title' | translate | async) ?? ''\"\n (onClick)=\"applySelection()\"\n ></ax-button>\n </div>\n} @else {\n @if (showSearchAndViewAll()) {\n <ax-search-box\n [placeholder]=\"'@general:search.title' | translate | async\"\n [ngModel]=\"searchValue()\"\n (ngModelChange)=\"handleSearch($event)\"\n class=\"w-full\"\n >\n <ax-clear-button></ax-clear-button>\n </ax-search-box>\n }\n <div\n axListNavigation\n #listNav=\"axListNavigation\"\n class=\"w-full overflow-auto max-h-[300px] flex flex-col gap-1\"\n role=\"listbox\"\n >\n @for (tenant of displayedTenants(); track tenant.id) {\n <div\n axListNavigationItem\n #navItem=\"axListNavigationItem\"\n tabindex=\"0\"\n role=\"option\"\n [attr.aria-selected]=\"isTenantSelected(tenant)\"\n class=\"flex gap-2 items-center p-2 cursor-pointer rounded-md transition-colors outline-none hover:bg-primary-lightest hover:dark:bg-primary-dark\"\n [class.bg-primary-lighter]=\"isTenantSelected(tenant)\"\n [class.dark:bg-primary-darker]=\"isTenantSelected(tenant)\"\n [class.text-primary-on-lighter]=\"isTenantSelected(tenant)\"\n [class.dark:text-primary-on-darker]=\"isTenantSelected(tenant)\"\n [class.ring-2]=\"navItem.isActive()\"\n [class.ring-primary]=\"navItem.isActive()\"\n (click)=\"chooseTenant(tenant)\"\n (keydown)=\"handleListItemKeyDown($event, tenant)\"\n >\n <div class=\"flex flex-1 gap-2 items-center min-w-0\">\n <div\n class=\"shrink-0 flex justify-center items-center rounded-full bg-neutral-100\"\n [class.w-10]=\"simple()\"\n [class.h-10]=\"simple()\"\n [class.w-12]=\"!simple()\"\n [class.h-12]=\"!simple()\"\n >\n <axp-logo\n [logoKey]=\"tenantLogoKey(tenant)\"\n appearance=\"colored\"\n [size]=\"40\"\n [attr.alt]=\"tenant.title\"\n class=\"flex! items-center justify-center text-sm! bg-neutral-200 dark:bg-neutral-800 rounded-full\"\n [class.w-8]=\"simple()\"\n [class.h-8]=\"simple()\"\n [class.w-10]=\"!simple()\"\n [class.h-10]=\"!simple()\"\n ></axp-logo>\n </div>\n <span class=\"flex-1 truncate text-start md:text-base text-sm\">{{ tenant.title }}</span>\n </div>\n </div>\n }\n </div>\n @if (showSearchAndViewAll()) {\n <div class=\"mt-6 w-full flex justify-center\">\n <ax-button\n (onClick)=\"showAllTenants()\"\n class=\"w-full\"\n look=\"outline\"\n [text]=\"\n showAll()\n ? ('@general:actions.view-less.title' | translate | async)\n : ('@general:actions.view-all.title' | translate | async)\n \"\n ></ax-button>\n </div>\n }\n}\n\n<ng-template #tenantItemTemplate let-item>\n <div class=\"flex gap-2 items-center p-2\">\n <div\n class=\"w-10 h-10 flex justify-center items-center rounded-full bg-neutral-100 shrink-0\"\n >\n <axp-logo\n [logoKey]=\"tenantLogoKey(item.data)\"\n appearance=\"colored\"\n [size]=\"32\"\n [attr.alt]=\"item.data.title\"\n class=\"flex! items-center justify-center text-sm! w-8 h-8 bg-neutral-200 rounded-full\"\n ></axp-logo>\n </div>\n <span class=\"text-start md:text-base text-sm\">{{ item.data.title }}</span>\n </div>\n</ng-template>\n" }]
1293
+ }], ctorParameters: () => [], propDecorators: { simple: [{ type: i0.Input, args: [{ isSignal: true, alias: "simple", required: false }] }], tenantChosen: [{ type: i0.Output, args: ["tenantChosen"] }], listNav: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXListNavigationDirective), { isSignal: true }] }], listItems: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => AXListNavigationItemDirective), { isSignal: true }] }] } });
1294
+
1295
+ class AXPTenantChooserDropdownComponent {
1296
+ constructor() {
1297
+ this.sessionService = inject(AXPSessionService);
1298
+ this.tenants$ = this.sessionService.tenants$;
1299
+ this.tenant = this.sessionService.tenant;
1300
+ this.tenantPopover = viewChild('tenantPopover', ...(ngDevMode ? [{ debugName: "tenantPopover" }] : /* istanbul ignore next */ []));
1301
+ }
1302
+ closePopover() {
1303
+ this.tenantPopover()?.close();
1304
+ }
1305
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTenantChooserDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1306
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPTenantChooserDropdownComponent, isStandalone: true, selector: "axp-tenant-chooser-dropdown", viewQueries: [{ propertyName: "tenantPopover", first: true, predicate: ["tenantPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (tenants$ | async; as tenants) {\n @if (tenants.length > 1) {\n <ax-button\n [text]=\"tenant?.title ?? ''\"\n class=\"ax-sm\"\n look=\"blank\"\n #tenantChooser\n >\n <ax-suffix>\n <ax-icon class=\"fa-solid fa-caret-down\"></ax-icon>\n </ax-suffix>\n </ax-button>\n <ax-popover\n [target]=\"tenantChooser\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n [adaptivityEnabled]=\"true\"\n #tenantPopover\n >\n <div\n class=\"bg-lightest border overflow-hidden rounded-md shadow-md w-full min-w-64 p-1\"\n >\n <axp-tenant-chooser-list\n [simple]=\"true\"\n (tenantChosen)=\"closePopover()\"\n ></axp-tenant-chooser-list>\n </div>\n </ax-popover>\n } @else {\n <ax-button\n [text]=\"tenant?.title ?? ''\"\n class=\"ax-sm\"\n look=\"blank\"\n ></ax-button>\n }\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2$2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "component", type: AXPTenantChooserListComponent, selector: "axp-tenant-chooser-list", inputs: ["simple"], outputs: ["tenantChosen"] }, { kind: "pipe", type: i4$1.AsyncPipe, name: "async" }] }); }
1307
+ }
1308
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTenantChooserDropdownComponent, decorators: [{
1309
+ type: Component,
1310
+ args: [{ selector: 'axp-tenant-chooser-dropdown', imports: [CommonModule, AXButtonModule, AXPopoverModule, AXDecoratorModule, AXPTenantChooserListComponent], template: "@if (tenants$ | async; as tenants) {\n @if (tenants.length > 1) {\n <ax-button\n [text]=\"tenant?.title ?? ''\"\n class=\"ax-sm\"\n look=\"blank\"\n #tenantChooser\n >\n <ax-suffix>\n <ax-icon class=\"fa-solid fa-caret-down\"></ax-icon>\n </ax-suffix>\n </ax-button>\n <ax-popover\n [target]=\"tenantChooser\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n [adaptivityEnabled]=\"true\"\n #tenantPopover\n >\n <div\n class=\"bg-lightest border overflow-hidden rounded-md shadow-md w-full min-w-64 p-1\"\n >\n <axp-tenant-chooser-list\n [simple]=\"true\"\n (tenantChosen)=\"closePopover()\"\n ></axp-tenant-chooser-list>\n </div>\n </ax-popover>\n } @else {\n <ax-button\n [text]=\"tenant?.title ?? ''\"\n class=\"ax-sm\"\n look=\"blank\"\n ></ax-button>\n }\n}\n" }]
1311
+ }], propDecorators: { tenantPopover: [{ type: i0.ViewChild, args: ['tenantPopover', { isSignal: true }] }] } });
1312
+
1313
+ //#region ---- Profile slot menu hook ----
1314
+ const PROFILE_SLOT_MENU_ITEMS_HOOK = 'profile-slot.menu-items';
1315
+ function hasProfileSlotMenuBreakBefore(breakOptions) {
1316
+ return breakOptions?.before === true;
1317
+ }
1318
+ function hasProfileSlotMenuBreakAfter(breakOptions) {
1319
+ return breakOptions?.after === true;
1320
+ }
1321
+ //#endregion
1322
+
1323
+ class AXMAuthProfileSlotComponent {
1324
+ constructor() {
1325
+ //#region ---- Services & Dependencies ----
1326
+ this.router = inject(Router);
1327
+ this.sessionService = inject(AXPSessionService);
1328
+ this.themeService = inject(AXPLayoutThemeService);
1329
+ this.hooks = inject(AXPHookService);
1330
+ this.commandService = inject(AXPCommandService);
1331
+ this.shortcutRegistry = inject(AXPKeyboardShortcutRegistry);
1332
+ this.settingsService = inject(AXPSettingsService);
1333
+ this.destroyRef = inject(DestroyRef);
1334
+ //#endregion
1335
+ //#region ---- State ----
1336
+ this.isDarkMode = this.themeService.isDarkMode;
1337
+ this.user$ = this.sessionService.user$;
1338
+ this.tenant$ = this.sessionService.tenant$;
1339
+ this.isAuthenticated$ = this.sessionService.isAuthenticated$;
1340
+ this.isAuthenticated = toSignal(this.sessionService.isAuthenticated$, { initialValue: false });
1341
+ this.menuItems = signal([], ...(ngDevMode ? [{ debugName: "menuItems" }] : /* istanbul ignore next */ []));
1342
+ //#endregion
1343
+ //#region ---- UI Handlers ----
1344
+ this.appName = () => this.sessionService.application?.name;
1345
+ this.hasProfileSlotMenuBreakBefore = hasProfileSlotMenuBreakBefore;
1346
+ this.hasProfileSlotMenuBreakAfter = hasProfileSlotMenuBreakAfter;
1347
+ }
1348
+ //#endregion
1349
+ //#region ---- Lifecycle Methods ----
1350
+ async ngOnInit() {
1351
+ await this.loadMenuItems();
1352
+ this.bindLockMenuSettingsReload();
1353
+ }
1354
+ menuItemClass(item) {
1355
+ return {
1356
+ 'text-danger-500': item.color === 'danger',
1357
+ };
1358
+ }
1359
+ shortcutKeys(chord) {
1360
+ return chordToKbdItemKeys(chord);
1361
+ }
1362
+ primaryShortcut(item) {
1363
+ return getPrimaryKeyboardShortcutChord(item.shortcuts);
1364
+ }
1365
+ async handleMenuItemClick(item, popover) {
1366
+ popover.close();
1367
+ if (item.command) {
1368
+ await this.executeCommand(item.command);
1369
+ }
1370
+ }
1371
+ handleSignIn() {
1372
+ this.router.navigate(['/auth/login']);
1373
+ }
1374
+ //#endregion
1375
+ //#region ---- Private Methods ----
1376
+ bindLockMenuSettingsReload() {
1377
+ this.settingsService.onChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
1378
+ if (!event.keys.includes(AXPAuthSettings.LockWhenIdleEnabled)) {
1379
+ return;
1380
+ }
1381
+ void this.loadMenuItems();
1382
+ });
1383
+ this.settingsService.onLoaded.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
1384
+ void this.loadMenuItems();
1385
+ });
1386
+ }
1387
+ async loadMenuItems() {
1388
+ const payload = await this.hooks.runAsync(PROFILE_SLOT_MENU_ITEMS_HOOK, {
1389
+ items: [],
1390
+ });
1391
+ const items = payload.items ?? [];
1392
+ this.menuItems.set(items);
1393
+ this.registerMenuShortcuts(items);
1394
+ }
1395
+ registerMenuShortcuts(items) {
1396
+ const shortcuts = [];
1397
+ for (const item of items) {
1398
+ if (item.visible === false || !item.command || !item.shortcuts?.length) {
1399
+ continue;
1400
+ }
1401
+ const command = item.command;
1402
+ for (const shortcut of normalizeKeyboardShortcuts(item.shortcuts)) {
1403
+ for (const chord of shortcut.keys) {
1404
+ shortcuts.push({
1405
+ keys: [chord],
1406
+ title: item.title,
1407
+ group: '@general:keyboard-shortcuts.groups.global',
1408
+ allowInEditableFields: shortcut.allowInEditableFields,
1409
+ directionBehavior: shortcut.directionBehavior,
1410
+ when: () => this.isAuthenticated() && (shortcut.when?.() ?? true),
1411
+ handler: () => {
1412
+ void this.executeCommand(command);
1413
+ },
1414
+ });
1415
+ }
1416
+ }
1417
+ }
1418
+ if (shortcuts.length === 0) {
1419
+ return;
1420
+ }
1421
+ this.shortcutRegistry.register({
1422
+ owner: this.destroyRef,
1423
+ id: 'profile-slot-menu',
1424
+ priority: AXPKeyboardShortcutPriority.Global,
1425
+ group: '@general:keyboard-shortcuts.groups.global',
1426
+ when: () => this.isAuthenticated(),
1427
+ shortcuts,
1428
+ });
1429
+ }
1430
+ async executeCommand(command) {
1431
+ if (this.commandService.exists(command.name)) {
1432
+ await this.commandService.execute(command.name, command.options ?? {});
1433
+ }
1434
+ }
1435
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthProfileSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1436
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXMAuthProfileSlotComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "@if (isAuthenticated$ | async) {\n <div class=\"size-fit\">\n <div #avatar>\n <axp-user-avatar\n [size]=\"36\"\n [userId]=\"(user$ | async)?.id!\"\n ></axp-user-avatar>\n </div>\n\n <ax-popover [target]=\"avatar\" [openOn]=\"'toggle'\" [closeOn]=\"'clickOut'\" #p>\n <div\n class=\"bg-lightest border border-lightest rounded-md shadow-md w-full min-w-64\"\n >\n <div class=\"flex flex-col items-center p-4 border-b\">\n <axp-user-avatar\n [size]=\"64\"\n [userId]=\"(user$ | async)?.id!\"\n ></axp-user-avatar>\n\n <div class=\"flex flex-col items-center justify-center mt-2\">\n <div class=\"text-base font-bold\">{{ (user$ | async)?.title }}</div>\n <axp-tenant-chooser-dropdown></axp-tenant-chooser-dropdown>\n </div>\n </div>\n <div class=\"axp-profile-slot-menus\">\n <ul>\n @for (item of menuItems(); track item.name ?? item.title) {\n @if (item.visible != false) {\n @if (hasProfileSlotMenuBreakBefore(item.break)) {\n <li class=\"axp-profile-slot-menu-break\" role=\"separator\"></li>\n }\n <li [class]=\"menuItemClass(item)\">\n <a (click)=\"handleMenuItemClick(item, p)\">\n <i [class]=\"item.icon\"></i>\n <span class=\"flex-1\">{{\n item.title | translate | async\n }}</span>\n </a>\n </li>\n @if (hasProfileSlotMenuBreakAfter(item.break)) {\n <li class=\"axp-profile-slot-menu-break\" role=\"separator\"></li>\n }\n }\n }\n </ul>\n </div>\n </div>\n </ax-popover>\n </div>\n} @else {\n <ax-button\n color=\"default\"\n look=\"blank\"\n text=\"Sign Up / Sign In\"\n (click)=\"handleSignIn()\"\n >\n <ax-icon>\n <i class=\"fa-solid fa-user text-slate-400 dark:text-slate-200\"></i>\n </ax-icon>\n </ax-button>\n}\n", styles: ["@layer properties;.axp-profile-slot-menus ul{margin-top:calc(var(--spacing, .25rem) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-border-default);padding-inline:calc(var(--spacing, .25rem) * 2)}.axp-profile-slot-menus ul:last-child{border-style:var(--tw-border-style);border-width:0px}.axp-profile-slot-menus ul li{display:flex;cursor:pointer;border-radius:var(--radius-md, .375rem)}.axp-profile-slot-menus ul li:last-child{margin-bottom:calc(var(--spacing, .25rem) * 2)}.axp-profile-slot-menus ul li:hover{background-color:rgba(var(--ax-sys-color-dark-surface))}.axp-profile-slot-menus ul li.axp-profile-slot-menu-break{pointer-events:none;margin-inline:calc(var(--spacing, .25rem) * 2);margin-block:var(--spacing, .25rem);height:0;min-height:0;cursor:default;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-border-default);padding:0}.axp-profile-slot-menus ul li.axp-profile-slot-menu-break:hover{background-color:transparent}.axp-profile-slot-menus ul li i{margin-inline-end:calc(var(--spacing, .25rem) * 3);display:grid;height:calc(var(--spacing, .25rem) * 6);width:calc(var(--spacing, .25rem) * 6);place-items:center;font-size:var(--text-xl, 1.25rem);line-height:var(--tw-leading, var(--text-xl--line-height, calc(1.75 / 1.25)))}.axp-profile-slot-menus ul li a{display:flex;height:100%;width:100%;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding:calc(var(--spacing, .25rem) * 2)}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid}}}\n/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type:
1437
+ //
1438
+ AXImageModule }, { kind: "ngmodule", type: AXAvatarModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2$2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type:
1439
+ // AXKBDComponent,
1440
+ // AXKBDItemComponent,
1441
+ AXLoadingModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPUserAvatarComponent, selector: "axp-user-avatar", inputs: ["size", "userId"] }, { kind: "component", type:
1442
+ //
1443
+ AXPTenantChooserDropdownComponent, selector: "axp-tenant-chooser-dropdown" }, { kind: "pipe", type: i4$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1444
+ }
1445
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthProfileSlotComponent, decorators: [{
1446
+ type: Component,
1447
+ args: [{ imports: [
1448
+ CommonModule,
1449
+ RouterModule,
1450
+ //
1451
+ AXImageModule,
1452
+ AXAvatarModule,
1453
+ AXPopoverModule,
1454
+ AXButtonModule,
1455
+ AXDecoratorModule,
1456
+ // AXKBDComponent,
1457
+ // AXKBDItemComponent,
1458
+ AXLoadingModule,
1459
+ AXTranslationModule,
1460
+ AXPUserAvatarComponent,
1461
+ //
1462
+ AXPTenantChooserDropdownComponent,
1463
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if (isAuthenticated$ | async) {\n <div class=\"size-fit\">\n <div #avatar>\n <axp-user-avatar\n [size]=\"36\"\n [userId]=\"(user$ | async)?.id!\"\n ></axp-user-avatar>\n </div>\n\n <ax-popover [target]=\"avatar\" [openOn]=\"'toggle'\" [closeOn]=\"'clickOut'\" #p>\n <div\n class=\"bg-lightest border border-lightest rounded-md shadow-md w-full min-w-64\"\n >\n <div class=\"flex flex-col items-center p-4 border-b\">\n <axp-user-avatar\n [size]=\"64\"\n [userId]=\"(user$ | async)?.id!\"\n ></axp-user-avatar>\n\n <div class=\"flex flex-col items-center justify-center mt-2\">\n <div class=\"text-base font-bold\">{{ (user$ | async)?.title }}</div>\n <axp-tenant-chooser-dropdown></axp-tenant-chooser-dropdown>\n </div>\n </div>\n <div class=\"axp-profile-slot-menus\">\n <ul>\n @for (item of menuItems(); track item.name ?? item.title) {\n @if (item.visible != false) {\n @if (hasProfileSlotMenuBreakBefore(item.break)) {\n <li class=\"axp-profile-slot-menu-break\" role=\"separator\"></li>\n }\n <li [class]=\"menuItemClass(item)\">\n <a (click)=\"handleMenuItemClick(item, p)\">\n <i [class]=\"item.icon\"></i>\n <span class=\"flex-1\">{{\n item.title | translate | async\n }}</span>\n </a>\n </li>\n @if (hasProfileSlotMenuBreakAfter(item.break)) {\n <li class=\"axp-profile-slot-menu-break\" role=\"separator\"></li>\n }\n }\n }\n </ul>\n </div>\n </div>\n </ax-popover>\n </div>\n} @else {\n <ax-button\n color=\"default\"\n look=\"blank\"\n text=\"Sign Up / Sign In\"\n (click)=\"handleSignIn()\"\n >\n <ax-icon>\n <i class=\"fa-solid fa-user text-slate-400 dark:text-slate-200\"></i>\n </ax-icon>\n </ax-button>\n}\n", styles: ["@layer properties;.axp-profile-slot-menus ul{margin-top:calc(var(--spacing, .25rem) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-border-default);padding-inline:calc(var(--spacing, .25rem) * 2)}.axp-profile-slot-menus ul:last-child{border-style:var(--tw-border-style);border-width:0px}.axp-profile-slot-menus ul li{display:flex;cursor:pointer;border-radius:var(--radius-md, .375rem)}.axp-profile-slot-menus ul li:last-child{margin-bottom:calc(var(--spacing, .25rem) * 2)}.axp-profile-slot-menus ul li:hover{background-color:rgba(var(--ax-sys-color-dark-surface))}.axp-profile-slot-menus ul li.axp-profile-slot-menu-break{pointer-events:none;margin-inline:calc(var(--spacing, .25rem) * 2);margin-block:var(--spacing, .25rem);height:0;min-height:0;cursor:default;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-border-default);padding:0}.axp-profile-slot-menus ul li.axp-profile-slot-menu-break:hover{background-color:transparent}.axp-profile-slot-menus ul li i{margin-inline-end:calc(var(--spacing, .25rem) * 3);display:grid;height:calc(var(--spacing, .25rem) * 6);width:calc(var(--spacing, .25rem) * 6);place-items:center;font-size:var(--text-xl, 1.25rem);line-height:var(--tw-leading, var(--text-xl--line-height, calc(1.75 / 1.25)))}.axp-profile-slot-menus ul li a{display:flex;height:100%;width:100%;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding:calc(var(--spacing, .25rem) * 2)}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid}}}\n/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */\n"] }]
1464
+ }] });
1465
+
1466
+ class AXMUserProfileCardSlotComponent {
1467
+ constructor() {
1468
+ this.router = inject(Router);
1469
+ this.sessionService = inject(AXPSessionService);
1470
+ this.dataService = inject(AXPDataProvider, { optional: true });
1471
+ this.themeStore = inject(AXPLayoutThemeService);
1472
+ this.deviceService = inject(AXPDeviceService);
1473
+ this.isCompactMode = computed(() => {
1474
+ this.deviceService.screenSize();
1475
+ return this.themeStore.effectiveMenuVerticalMode() === AXPMenuVerticalMode.Compact;
1476
+ }, ...(ngDevMode ? [{ debugName: "isCompactMode" }] : /* istanbul ignore next */ []));
1477
+ this.hasPicture = signal(true, ...(ngDevMode ? [{ debugName: "hasPicture" }] : /* istanbul ignore next */ []));
1478
+ this.avatarText = signal('', ...(ngDevMode ? [{ debugName: "avatarText" }] : /* istanbul ignore next */ []));
1479
+ this.user$ = this.sessionService.user$;
1480
+ this.tenant$ = this.sessionService.tenant$;
1481
+ this.isAuthenticated$ = this.sessionService.isAuthenticated$;
1482
+ this.appName = () => this.sessionService.application?.name;
1483
+ this.getAvatarText = () => {
1484
+ this.user$.pipe(first$1()).subscribe((user) => {
1485
+ const names = user?.title.split(' ');
1486
+ if (names?.length && names?.length > 1) {
1487
+ this.avatarText.set(`${names[0][0].toUpperCase()}${names[names.length - 1][0].toUpperCase()}`);
1488
+ }
1489
+ else
1490
+ return this.avatarText.set('AC');
1491
+ });
1492
+ };
1493
+ this.loadImage();
1494
+ this.getAvatarText();
1495
+ }
1496
+ editProfile() {
1497
+ this.router.navigate([`/${this.appName()}/m/securityManagement/e/profile/0/view`]);
1498
+ }
1499
+ async logOut() {
1500
+ await this.sessionService.signout();
1501
+ }
1502
+ toggleProfileMenu() {
1503
+ this.profilePopover.open();
1504
+ }
1505
+ handleSignIn() {
1506
+ this.router.navigate(['/auth/login']);
1507
+ }
1508
+ onImageError(event) {
1509
+ this.hasPicture.set(false);
1510
+ }
1511
+ async loadImage() {
1512
+ try {
1513
+ const url = this.sessionService.user?.avatar;
1514
+ if (url?.toLowerCase().startsWith('http')) {
1515
+ this.src = url;
1516
+ }
1517
+ else if (this.sessionService.user?.avatar != 'null' && this.dataService) {
1518
+ const blob = await this.dataService.viewFile(url, '');
1519
+ this.src = URL.createObjectURL(blob);
1520
+ }
1521
+ else {
1522
+ //this.src = this.mockerService.avatar();
1523
+ }
1524
+ }
1525
+ catch (error) {
1526
+ this.hasPicture.set(false);
1527
+ console.error('Failed to load image by ID:', error);
1528
+ this.src = '';
1529
+ }
1530
+ }
1531
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMUserProfileCardSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1532
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXMUserProfileCardSlotComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "profilePopover", first: true, predicate: ["profilePopover"], descendants: true }], ngImport: i0, template: "@if((isAuthenticated$ | async)) {\n<div class=\"p-3 flex items-center gap-1 border-b border-lighter\" [class.justify-center]=\"isCompactMode()\">\n <div #avatar>\n <axp-user-avatar [size]=\"48\" [userId]=\"(user$ | async)?.id!\"></axp-user-avatar>\n </div>\n @if (!isCompactMode()) {\n <div class=\"flex flex-col\">\n <ax-title class=\"text-sm font-medium px-3\">{{ (user$ | async)?.title }}</ax-title>\n <axp-tenant-chooser-dropdown />\n </div>\n }\n</div>\n}\n", dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type:
1533
+ //
1534
+ AXImageModule }, { kind: "ngmodule", type: AXAvatarModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type:
1535
+ //
1536
+ AXPTenantChooserDropdownComponent, selector: "axp-tenant-chooser-dropdown" }, { kind: "component", type: AXPUserAvatarComponent, selector: "axp-user-avatar", inputs: ["size", "userId"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
1537
+ }
1538
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMUserProfileCardSlotComponent, decorators: [{
1539
+ type: Component,
1540
+ args: [{ imports: [
1541
+ RouterModule,
1542
+ //
1543
+ AXImageModule,
1544
+ AXAvatarModule,
1545
+ AXPopoverModule,
1546
+ AXButtonModule,
1547
+ AXDecoratorModule,
1548
+ AXLoadingModule,
1549
+ AXTranslationModule,
1550
+ //
1551
+ AXPTenantChooserDropdownComponent,
1552
+ AsyncPipe,
1553
+ AXPUserAvatarComponent,
1554
+ ], template: "@if((isAuthenticated$ | async)) {\n<div class=\"p-3 flex items-center gap-1 border-b border-lighter\" [class.justify-center]=\"isCompactMode()\">\n <div #avatar>\n <axp-user-avatar [size]=\"48\" [userId]=\"(user$ | async)?.id!\"></axp-user-avatar>\n </div>\n @if (!isCompactMode()) {\n <div class=\"flex flex-col\">\n <ax-title class=\"text-sm font-medium px-3\">{{ (user$ | async)?.title }}</ax-title>\n <axp-tenant-chooser-dropdown />\n </div>\n }\n</div>\n}\n" }]
1555
+ }], ctorParameters: () => [], propDecorators: { profilePopover: [{
1556
+ type: ViewChild,
1557
+ args: ['profilePopover']
1558
+ }] } });
1559
+
1560
+ class AXMUserDeviceSessionsService {
1561
+ }
1562
+ class AXMUserDeviceSessionsServiceImp extends AXMUserDeviceSessionsService {
1563
+ constructor() {
1564
+ super(...arguments);
1565
+ //#region ---- Services & Dependencies ----
1566
+ this.sessionService = inject(AXPSessionService);
1567
+ this.entityService = inject(AXPEntityService);
1568
+ this.deviceSessionsDataAccessor = this.entityService
1569
+ .withEntity(RootConfig$1.module.name, RootConfig$1.entities.deviceSessions.name)
1570
+ .data();
1571
+ this.deviceSessionsService = inject(AXMDeviceSessionsService);
1572
+ this.settingsService = inject(AXPSettingsService);
1573
+ this.toastService = inject(AXToastService);
1574
+ this.translationService = inject(AXTranslationService);
1575
+ }
1576
+ //#endregion
1577
+ async getSessions() {
1578
+ const userId = this.sessionService.user?.id;
1579
+ if (!userId) {
1580
+ return [];
1581
+ }
1582
+ const response = await this.deviceSessionsDataAccessor.query();
1583
+ return response.items.filter((item) => item.userId === userId &&
1584
+ String(item.status).toLowerCase() === 'active' &&
1585
+ !item.isActiveSession);
1586
+ }
1587
+ async getCurrentActiveSessions() {
1588
+ const userId = this.sessionService.user?.id;
1589
+ if (!userId) {
1590
+ throw new Error('No user found in session');
1591
+ }
1592
+ const response = await this.deviceSessionsDataAccessor.query();
1593
+ const userSessions = response.items.filter((item) => item.userId === userId && String(item.status).toLowerCase() === 'active');
1594
+ const activeSession = userSessions.find((item) => item.isActiveSession);
1595
+ if (activeSession) {
1596
+ return activeSession;
1597
+ }
1598
+ const mostRecentSession = orderBy(userSessions, [(session) => new Date(session.lastLoginDate).getTime()], ['desc'])[0];
1599
+ if (!mostRecentSession) {
1600
+ throw new Error('No active session found for current user');
1601
+ }
1602
+ return mostRecentSession;
1603
+ }
1604
+ async terminateSession(sessionId) {
1605
+ await this.deviceSessionsDataAccessor.update(sessionId, { status: 'revoked' });
1606
+ await this.showTerminateSuccessToast();
1607
+ }
1608
+ async terminateAllSession() {
1609
+ await this.deviceSessionsService.terminateAllCurrentUserSessions();
1610
+ await this.showTerminateSuccessToast();
1611
+ }
1612
+ async setInactiveSessionTermination(value) {
1613
+ await this.settingsService
1614
+ .scope(AXPPlatformScope.User)
1615
+ .set(AXPAuthSettings.InactiveSessionTermination, value);
1616
+ }
1617
+ async showTerminateSuccessToast() {
1618
+ const showResult = await this.settingsService.get(AXPCommonSettings.EnableOperationToasts);
1619
+ if (!showResult) {
1620
+ return;
1621
+ }
1622
+ this.toastService.show({
1623
+ color: 'success',
1624
+ title: await this.translationService.translateAsync('workflow.session-terminated'),
1625
+ content: await this.translationService.translateAsync('workflow.session-terminated-body'),
1626
+ closeButton: true,
1627
+ timeOut: 3000,
1628
+ timeOutProgress: true,
1629
+ });
1630
+ }
1631
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMUserDeviceSessionsServiceImp, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1632
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMUserDeviceSessionsServiceImp }); }
1633
+ }
1634
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMUserDeviceSessionsServiceImp, decorators: [{
1635
+ type: Injectable
1636
+ }] });
1637
+
1638
+ const canActivateTenantChooser = async (route, state) => {
1639
+ const sessionService = inject(AXPSessionService);
1640
+ const authStrategyRegistry = inject(AXPAuthStrategyRegistryService);
1641
+ const router = inject(Router);
1642
+ const strategyName = sessionService.getSessionData()?.strategy;
1643
+ const strategy = authStrategyRegistry.get(strategyName);
1644
+ if (!sessionService.user?.id) {
1645
+ return router.createUrlTree(['/auth/login']);
1646
+ }
1647
+ const tenants = await firstValueFrom(sessionService.tenants$);
1648
+ if (tenants.length === 0) {
1649
+ await sessionService.signout();
1650
+ window.alert('You do not have any tenant assigned to your account and cannot use the site. Please contact your administrator.');
1651
+ return router.createUrlTree(['/auth/login']);
1652
+ }
1653
+ if (tenants.length === 1) {
1654
+ if (strategy && typeof strategy.updateToken === 'function') {
1655
+ const updateContext = {
1656
+ user: sessionService.user,
1657
+ tenant: tenants[0],
1658
+ };
1659
+ await sessionService.updateToken(updateContext);
1660
+ return router.createUrlTree(['/auth/account/app-chooser']);
1661
+ }
1662
+ return false;
1663
+ }
1664
+ return true;
1665
+ };
1666
+
1667
+ const canActivateAppChooser = async (route, state) => {
1668
+ const sessionService = inject(AXPSessionService);
1669
+ const authStrategyRegistry = inject(AXPAuthStrategyRegistryService);
1670
+ const homePageService = inject(AXPHomePageService);
1671
+ const router = inject(Router);
1672
+ const strategyName = sessionService.getSessionData()?.strategy;
1673
+ const strategy = authStrategyRegistry.get(strategyName);
1674
+ const apps = await firstValueFrom(sessionService.applications$);
1675
+ if (apps.length === 0) {
1676
+ await router.navigate(['/auth/login']);
1677
+ return false;
1678
+ }
1679
+ if (apps.length === 1) {
1680
+ const tenantId = sessionService.tenant?.id;
1681
+ if (!tenantId) {
1682
+ throw new Error('Tenant ID is missing. Please contact support.');
1683
+ }
1684
+ if (strategy && typeof strategy.updateToken === 'function') {
1685
+ const updateContext = {
1686
+ user: sessionService.user,
1687
+ tenant: sessionService.tenant,
1688
+ application: apps[0],
1689
+ };
1690
+ await sessionService.updateToken(updateContext);
1691
+ return router.createUrlTree([homePageService.getCurrent().path]);
1692
+ }
1693
+ return false;
1694
+ }
1695
+ return true;
1696
+ };
1697
+
1698
+ function routesFacory() {
1699
+ let routes = [];
1700
+ routes = [
1701
+ {
1702
+ path: 'tenant-chooser',
1703
+ loadComponent: () => import('./acorex-platform-module-auth-client-tenant-chooser.component-ihsqCJKN.mjs').then((c) => c.AXPAuthTenantChooserComponent),
1704
+ canActivate: [AXPAuthGuard, canActivateTenantChooser],
1705
+ },
1706
+ {
1707
+ path: 'app-chooser',
1708
+ loadComponent: () => import('./acorex-platform-module-auth-client-app-chooser.component-Cm2ZagaI.mjs').then((c) => c.AXPAuthAppChooserComponent),
1709
+ canActivate: [AXPAuthGuard, canActivateAppChooser],
1710
+ },
1711
+ ];
1712
+ return routes;
1713
+ }
1714
+ class AXPAccountModule {
1715
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAccountModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1716
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPAccountModule, imports: [RouterModule, i1$3.AXPComponentSlotModule] }); }
1717
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAccountModule, providers: [
1718
+ {
1719
+ provide: ROUTES,
1720
+ multi: true,
1721
+ useFactory: routesFacory,
1722
+ },
1723
+ {
1724
+ provide: AXMUserDeviceSessionsService,
1725
+ useClass: AXMUserDeviceSessionsServiceImp,
1726
+ },
1727
+ ], imports: [RouterModule,
1728
+ //
1729
+ AXPComponentSlotModule.forChild({
1730
+ 'root-side-menu-header': [
1731
+ {
1732
+ priority: 1,
1733
+ name: 'user-card',
1734
+ component: AXMUserProfileCardSlotComponent,
1735
+ },
1736
+ ],
1737
+ 'root-header-end': [
1738
+ {
1739
+ priority: 98,
1740
+ name: 'app-choser',
1741
+ component: AXMAppChooserSlotComponent,
1742
+ },
1743
+ {
1744
+ priority: 99,
1745
+ name: 'profile',
1746
+ component: AXMAuthProfileSlotComponent,
1747
+ },
1748
+ ],
1749
+ })] }); }
1750
+ }
1751
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAccountModule, decorators: [{
1752
+ type: NgModule,
1753
+ args: [{
1754
+ declarations: [],
1755
+ imports: [
1756
+ RouterModule,
1757
+ //
1758
+ AXPComponentSlotModule.forChild({
1759
+ 'root-side-menu-header': [
1760
+ {
1761
+ priority: 1,
1762
+ name: 'user-card',
1763
+ component: AXMUserProfileCardSlotComponent,
1764
+ },
1765
+ ],
1766
+ 'root-header-end': [
1767
+ {
1768
+ priority: 98,
1769
+ name: 'app-choser',
1770
+ component: AXMAppChooserSlotComponent,
1771
+ },
1772
+ {
1773
+ priority: 99,
1774
+ name: 'profile',
1775
+ component: AXMAuthProfileSlotComponent,
1776
+ },
1777
+ ],
1778
+ }),
1779
+ ],
1780
+ exports: [],
1781
+ providers: [
1782
+ {
1783
+ provide: ROUTES,
1784
+ multi: true,
1785
+ useFactory: routesFacory,
1786
+ },
1787
+ {
1788
+ provide: AXMUserDeviceSessionsService,
1789
+ useClass: AXMUserDeviceSessionsServiceImp,
1790
+ },
1791
+ ],
1792
+ }]
1793
+ }] });
1794
+
1795
+ var account_module = /*#__PURE__*/Object.freeze({
1796
+ __proto__: null,
1797
+ AXPAccountModule: AXPAccountModule
1798
+ });
1799
+
1800
+ //#region ---- Idle lock service ----
1801
+ const IDLE_CHECK_INTERVAL_MS = 1_000;
1802
+ const MOUSE_MOVE_THROTTLE_MS = 1_000;
1803
+ const DEFAULT_LOCK_WHEN_IDLE_MINUTES = 15;
1804
+ const SECURITY_MANAGEMENT_MODULE = 'SecurityManagement';
1805
+ const SECURITY_MANAGEMENT_USER_ENTITY = 'User';
1806
+ const LOCK_WHEN_IDLE_SETTING_KEYS = [AXPAuthSettings.LockWhenIdleEnabled, AXPAuthSettings.LockWhenIdleMinutes];
1807
+ /**
1808
+ * Tracks user inactivity and locks the session overlay when idle threshold is exceeded.
1809
+ * Requires the Lock When Idle setting to be enabled; unlock uses PIN when configured, otherwise password.
1810
+ * Locked state is stored on {@link AXPSessionService.SESSION_KEY} and survives page refresh and browser restart.
1811
+ */
1812
+ class AXPIdleLockService {
1813
+ //#endregion
1814
+ //#region ---- Constructor ----
1815
+ constructor() {
1816
+ //#region ---- State ----
1817
+ this.locked = signal(false, ...(ngDevMode ? [{ debugName: "locked" }] : /* istanbul ignore next */ []));
1818
+ this.idleTimeoutMs = signal(DEFAULT_LOCK_WHEN_IDLE_MINUTES * 60_000, ...(ngDevMode ? [{ debugName: "idleTimeoutMs" }] : /* istanbul ignore next */ []));
1819
+ this.enabled = signal(false, ...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
1820
+ /** Whether manual or idle lock is available (Lock When Idle setting is enabled). */
1821
+ this.canLock = this.enabled.asReadonly();
1822
+ this.storedPinCode = signal('', ...(ngDevMode ? [{ debugName: "storedPinCode" }] : /* istanbul ignore next */ []));
1823
+ this.lastActivityAt = Date.now();
1824
+ this.lastMouseMoveAt = 0;
1825
+ this.idleCheckTimerId = null;
1826
+ this.overlayRef = null;
1827
+ this.overlayOpening = false;
1828
+ this.enableTask = Promise.resolve();
1829
+ //#endregion
1830
+ //#region ---- Services & Dependencies ----
1831
+ this.sessionService = inject(AXPSessionService);
1832
+ this.settingsService = inject(AXPSettingsService);
1833
+ this.entityService = inject(AXPEntityService);
1834
+ this.overlayService = inject(AXOverlayService);
1835
+ this.platform = inject(AXPlatform);
1836
+ this.destroyRef = inject(DestroyRef);
1837
+ this.injector = inject(Injector);
1838
+ //#region ---- Activity listeners ----
1839
+ this.onActivity = () => {
1840
+ this.recordActivity();
1841
+ };
1842
+ this.onMouseMove = () => {
1843
+ const now = Date.now();
1844
+ if (now - this.lastMouseMoveAt < MOUSE_MOVE_THROTTLE_MS) {
1845
+ return;
1846
+ }
1847
+ this.lastMouseMoveAt = now;
1848
+ this.recordActivity();
1849
+ };
1850
+ this.sessionService.status$
1851
+ .pipe(filter((status) => status === AXPSessionStatus.Authenticated ||
1852
+ status === AXPSessionStatus.Authorized ||
1853
+ status === AXPSessionStatus.SignedOut ||
1854
+ status === AXPSessionStatus.Expired), takeUntilDestroyed(this.destroyRef))
1855
+ .subscribe((status) => {
1856
+ const isActiveSession = status === AXPSessionStatus.Authenticated || status === AXPSessionStatus.Authorized;
1857
+ if (isActiveSession) {
1858
+ void this.scheduleEnable();
1859
+ return;
1860
+ }
1861
+ void this.handleInactiveSession(status);
1862
+ });
1863
+ afterNextRender(() => {
1864
+ if (this.sessionService.isLocked()) {
1865
+ void this.scheduleEnable();
1866
+ }
1867
+ }, { injector: this.injector });
1868
+ this.settingsService.onChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
1869
+ if (!LOCK_WHEN_IDLE_SETTING_KEYS.some((key) => event.keys.includes(key))) {
1870
+ return;
1871
+ }
1872
+ void this.applyLockWhenIdleSettingChange();
1873
+ });
1874
+ }
1875
+ //#endregion
1876
+ //#region ---- Public Methods ----
1877
+ hasPinCode() {
1878
+ return !isEmpty(this.storedPinCode());
1879
+ }
1880
+ verifyPin(pin) {
1881
+ const expected = this.storedPinCode();
1882
+ return !isEmpty(expected) && pin === expected;
1883
+ }
1884
+ async verifyPassword(password) {
1885
+ const userId = this.sessionService.user?.id;
1886
+ if (isNil(userId) || isEmpty(password)) {
1887
+ return false;
1888
+ }
1889
+ try {
1890
+ const user = await this.entityService
1891
+ .withEntity(SECURITY_MANAGEMENT_MODULE, SECURITY_MANAGEMENT_USER_ENTITY)
1892
+ .data()
1893
+ .byKey(userId);
1894
+ return await verifyPassword(password, get(user, 'password'));
1895
+ }
1896
+ catch (error) {
1897
+ console.error('Failed to verify password for session unlock', error);
1898
+ return false;
1899
+ }
1900
+ }
1901
+ unlock() {
1902
+ this.locked.set(false);
1903
+ this.sessionService.setLocked(false);
1904
+ this.closeLockOverlay();
1905
+ this.lastActivityAt = Date.now();
1906
+ }
1907
+ /** Locks the session overlay when Lock When Idle is enabled. */
1908
+ lockScreen() {
1909
+ this.lock();
1910
+ }
1911
+ async refreshPinState() {
1912
+ if (!this.isOperational()) {
1913
+ this.storedPinCode.set('');
1914
+ return;
1915
+ }
1916
+ const userId = this.sessionService.user?.id;
1917
+ if (isNil(userId)) {
1918
+ this.storedPinCode.set('');
1919
+ return;
1920
+ }
1921
+ try {
1922
+ const user = await this.entityService
1923
+ .withEntity(SECURITY_MANAGEMENT_MODULE, SECURITY_MANAGEMENT_USER_ENTITY)
1924
+ .data()
1925
+ .byKey(userId);
1926
+ const pinCode = defaultTo(get(user, 'pinCode'), '');
1927
+ this.storedPinCode.set(pinCode);
1928
+ }
1929
+ catch (error) {
1930
+ console.error('Failed to load security PIN for idle lock', error);
1931
+ this.storedPinCode.set('');
1932
+ }
1933
+ }
1934
+ /** Refreshes lock settings and PIN state for menu/commands. */
1935
+ async refreshLockState() {
1936
+ await this.refreshConfiguration();
1937
+ if (!this.isOperational()) {
1938
+ return;
1939
+ }
1940
+ await this.refreshPinState();
1941
+ }
1942
+ /** Restores the lock overlay when the persisted session is locked. */
1943
+ restoreFromPersistedSession() {
1944
+ if (!this.sessionService.isLocked()) {
1945
+ return Promise.resolve();
1946
+ }
1947
+ return this.scheduleEnable();
1948
+ }
1949
+ /** Re-evaluates idle lock after the user sets or changes their security PIN. */
1950
+ async notifyPinChanged() {
1951
+ await this.refreshConfiguration();
1952
+ if (!this.isOperational()) {
1953
+ return;
1954
+ }
1955
+ const status = await firstValueFrom(this.sessionService.status$);
1956
+ const isActiveSession = status === AXPSessionStatus.Authenticated || status === AXPSessionStatus.Authorized;
1957
+ if (isActiveSession) {
1958
+ await this.scheduleEnable();
1959
+ }
1960
+ }
1961
+ //#endregion
1962
+ //#region ---- Private Methods ----
1963
+ scheduleEnable() {
1964
+ this.enableTask = this.enableTask.then(() => this.runEnable());
1965
+ return this.enableTask;
1966
+ }
1967
+ async runEnable() {
1968
+ await this.refreshConfiguration();
1969
+ if (this.sessionService.isLocked()) {
1970
+ await this.refreshPinState();
1971
+ this.restoreLockedSession();
1972
+ return;
1973
+ }
1974
+ if (!this.enabled()) {
1975
+ this.disable(false);
1976
+ return;
1977
+ }
1978
+ await this.refreshPinState();
1979
+ this.resetUnlockedRuntimeState();
1980
+ this.attachActivityListeners();
1981
+ this.startIdleCheck();
1982
+ }
1983
+ /** Idle lock is active when enabled, or when a persisted lock must be restored/unlocked. */
1984
+ isOperational() {
1985
+ return this.enabled() || this.sessionService.isLocked();
1986
+ }
1987
+ restoreLockedSession() {
1988
+ this.locked.set(true);
1989
+ if (!this.overlayRef && !this.overlayOpening) {
1990
+ void this.openLockOverlay();
1991
+ }
1992
+ if (this.enabled()) {
1993
+ this.attachActivityListeners();
1994
+ this.startIdleCheck();
1995
+ }
1996
+ }
1997
+ async handleInactiveSession(status) {
1998
+ if (status !== AXPSessionStatus.SignedOut && status !== AXPSessionStatus.Expired) {
1999
+ return;
2000
+ }
2001
+ const isLoading = await firstValueFrom(this.sessionService.isLoading$);
2002
+ if (isLoading) {
2003
+ return;
2004
+ }
2005
+ this.disable(true);
2006
+ }
2007
+ disable(clearPersistedLock = false) {
2008
+ const preserveLockedSession = this.sessionService.isLocked() && !clearPersistedLock;
2009
+ this.enabled.set(false);
2010
+ if (!preserveLockedSession) {
2011
+ this.locked.set(false);
2012
+ this.closeLockOverlay();
2013
+ }
2014
+ if (clearPersistedLock) {
2015
+ this.sessionService.setLocked(false);
2016
+ }
2017
+ this.detachActivityListeners();
2018
+ this.stopIdleCheck();
2019
+ }
2020
+ async refreshConfiguration() {
2021
+ const lockWhenIdleEnabled = await this.settingsService.get(AXPAuthSettings.LockWhenIdleEnabled);
2022
+ const minutes = await this.settingsService.get(AXPAuthSettings.LockWhenIdleMinutes);
2023
+ const resolvedMinutes = typeof minutes === 'number' && minutes > 0 ? minutes : DEFAULT_LOCK_WHEN_IDLE_MINUTES;
2024
+ this.enabled.set(lockWhenIdleEnabled === true);
2025
+ this.idleTimeoutMs.set(resolvedMinutes * 60_000);
2026
+ }
2027
+ async applyLockWhenIdleSettingChange() {
2028
+ const status = await firstValueFrom(this.sessionService.status$);
2029
+ const isActiveSession = status === AXPSessionStatus.Authenticated || status === AXPSessionStatus.Authorized;
2030
+ if (!isActiveSession) {
2031
+ return;
2032
+ }
2033
+ await this.scheduleEnable();
2034
+ }
2035
+ recordActivity() {
2036
+ if (!this.enabled() || this.locked()) {
2037
+ return;
2038
+ }
2039
+ this.lastActivityAt = Date.now();
2040
+ }
2041
+ lock() {
2042
+ if (!this.enabled() || this.locked()) {
2043
+ return;
2044
+ }
2045
+ this.locked.set(true);
2046
+ this.sessionService.setLocked(true);
2047
+ void this.openLockOverlay();
2048
+ }
2049
+ openLockOverlay() {
2050
+ if (this.overlayRef || this.overlayOpening || !this.shouldShowLockOverlay()) {
2051
+ return;
2052
+ }
2053
+ afterNextRender(() => {
2054
+ void this.createLockOverlay();
2055
+ }, { injector: this.injector });
2056
+ }
2057
+ shouldShowLockOverlay() {
2058
+ return this.locked() || this.sessionService.isLocked();
2059
+ }
2060
+ async createLockOverlay() {
2061
+ if (this.overlayRef || this.overlayOpening || !this.shouldShowLockOverlay()) {
2062
+ return;
2063
+ }
2064
+ this.overlayOpening = true;
2065
+ this.locked.set(true);
2066
+ try {
2067
+ const { AXMSessionLockOverlayComponent: overlayComponent } = await import('./acorex-platform-module-auth-client-session-lock-overlay.component-DqXGuIgN.mjs');
2068
+ const panelClasses = ['axm-session-lock-overlay-shell'];
2069
+ if (this.platform.isDark()) {
2070
+ panelClasses.push('ax-dark');
2071
+ }
2072
+ const ref = await this.overlayService.create(overlayComponent, {
2073
+ centered: false,
2074
+ width: '100%',
2075
+ panelClass: panelClasses,
2076
+ backdrop: {
2077
+ enabled: false,
2078
+ background: false,
2079
+ closeOnClick: false,
2080
+ },
2081
+ onDispose: () => {
2082
+ this.overlayRef = null;
2083
+ },
2084
+ });
2085
+ if (!this.shouldShowLockOverlay()) {
2086
+ ref.dispose();
2087
+ return;
2088
+ }
2089
+ this.locked.set(true);
2090
+ this.overlayRef = ref;
2091
+ }
2092
+ catch (error) {
2093
+ console.error('Failed to open session lock overlay', error);
2094
+ if (this.shouldShowLockOverlay()) {
2095
+ window.setTimeout(() => this.openLockOverlay(), 250);
2096
+ }
2097
+ }
2098
+ finally {
2099
+ this.overlayOpening = false;
2100
+ }
2101
+ }
2102
+ closeLockOverlay() {
2103
+ if (this.overlayRef) {
2104
+ this.overlayRef.dispose();
2105
+ this.overlayRef = null;
2106
+ }
2107
+ }
2108
+ startIdleCheck() {
2109
+ this.stopIdleCheck();
2110
+ this.idleCheckTimerId = setInterval(() => {
2111
+ if (!this.enabled() || this.locked()) {
2112
+ return;
2113
+ }
2114
+ if (Date.now() - this.lastActivityAt >= this.idleTimeoutMs()) {
2115
+ this.lock();
2116
+ }
2117
+ }, IDLE_CHECK_INTERVAL_MS);
2118
+ }
2119
+ stopIdleCheck() {
2120
+ if (!isNil(this.idleCheckTimerId)) {
2121
+ clearInterval(this.idleCheckTimerId);
2122
+ this.idleCheckTimerId = null;
2123
+ }
2124
+ }
2125
+ resetUnlockedRuntimeState() {
2126
+ this.locked.set(false);
2127
+ this.closeLockOverlay();
2128
+ this.lastActivityAt = Date.now();
2129
+ }
2130
+ attachActivityListeners() {
2131
+ this.detachActivityListeners();
2132
+ const events = ['mousedown', 'keydown', 'wheel', 'touchstart', 'scroll', 'click'];
2133
+ events.forEach((eventName) => document.addEventListener(eventName, this.onActivity, true));
2134
+ document.addEventListener('mousemove', this.onMouseMove, true);
2135
+ }
2136
+ detachActivityListeners() {
2137
+ const events = ['mousedown', 'keydown', 'wheel', 'touchstart', 'scroll', 'click'];
2138
+ events.forEach((eventName) => document.removeEventListener(eventName, this.onActivity, true));
2139
+ document.removeEventListener('mousemove', this.onMouseMove, true);
2140
+ }
2141
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIdleLockService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2142
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIdleLockService, providedIn: 'root' }); }
2143
+ }
2144
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIdleLockService, decorators: [{
2145
+ type: Injectable,
2146
+ args: [{ providedIn: 'root' }]
2147
+ }], ctorParameters: () => [] });
2148
+
2149
+ //#region ---- Entity logo sync ----
2150
+ /** Registers tenant/application logo providers when the session is authorized. */
2151
+ class AXPLogoEntitySyncService {
2152
+ constructor() {
2153
+ this.session = inject(AXPSessionService);
2154
+ this.logoRegistration = inject(AXPEntityLogoRegistrationService);
2155
+ combineLatest([this.session.isAuthorized$, this.session.tenant$])
2156
+ .pipe(filter(([authorized]) => authorized), switchMap(() => from(this.syncEntityLogos())))
2157
+ .subscribe();
2158
+ }
2159
+ async syncEntityLogos() {
2160
+ const tenants = await firstValueFrom(this.session.tenants$);
2161
+ this.logoRegistration.registerTenants(tenants.map((tenant) => ({
2162
+ id: tenant.id,
2163
+ title: tenant.title,
2164
+ logo: tenant.logo,
2165
+ })));
2166
+ if (!this.session.tenant?.id) {
2167
+ return;
2168
+ }
2169
+ const applications = await firstValueFrom(this.session.applications$);
2170
+ this.logoRegistration.registerApplications(applications.map((application) => ({
2171
+ id: application.id,
2172
+ title: application.title,
2173
+ logoUrl: application.logoUrl,
2174
+ })));
2175
+ }
2176
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLogoEntitySyncService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2177
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLogoEntitySyncService, providedIn: 'root' }); }
2178
+ }
2179
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLogoEntitySyncService, decorators: [{
2180
+ type: Injectable,
2181
+ args: [{ providedIn: 'root' }]
2182
+ }], ctorParameters: () => [] });
2183
+
2184
+ //#region ---- Profile page component keys ----
2185
+ /** Full-tab activity heatmap on the profile Activity group. */
2186
+ const PROFILE_ACTIVITY_CHART_COMPONENT_KEY = 'auth.profile-activity-chart';
2187
+ /** Danger-zone block for the delete-account profile section. */
2188
+ const PROFILE_DELETE_ACCOUNT_DANGER_ZONE_COMPONENT_KEY = 'auth.profile-delete-account-danger-zone';
2189
+ //#endregion
2190
+
2191
+ //#region ---- Helpers ----
2192
+ const comingSoon = (messageKey) => ({
2193
+ name: AXMAuthCommandKeys.Profile.ComingSoon,
2194
+ options: { messageKey },
2195
+ });
2196
+ //#endregion
2197
+ //#region ---- Core profile groups ----
2198
+ /** Registers built-in profile page tab groups. */
2199
+ const CoreProfileGroupsHookProvider = {
2200
+ key: PROFILE_PAGE_GROUPS_HOOK,
2201
+ priority: 0,
2202
+ execute: (payload) => ({
2203
+ ...payload,
2204
+ groups: [
2205
+ ...payload.groups,
2206
+ {
2207
+ id: 'account',
2208
+ title: '@auth:profile-page.groups.account.title',
2209
+ icon: 'far fa-user',
2210
+ priority: 10,
2211
+ },
2212
+ {
2213
+ id: 'security',
2214
+ title: '@auth:profile-page.groups.security.title',
2215
+ icon: 'far fa-shield-halved',
2216
+ priority: 20,
2217
+ },
2218
+ {
2219
+ id: 'requests',
2220
+ title: '@auth:profile-page.groups.requests.title',
2221
+ icon: 'far fa-inbox',
2222
+ priority: 30,
2223
+ },
2224
+ {
2225
+ id: 'activity',
2226
+ title: '@auth:profile-page.groups.activity.title',
2227
+ icon: 'far fa-clock-rotate-left',
2228
+ priority: 40,
2229
+ content: {
2230
+ mode: 'component',
2231
+ componentKey: PROFILE_ACTIVITY_CHART_COMPONENT_KEY,
2232
+ },
2233
+ },
2234
+ ],
2235
+ }),
2236
+ };
2237
+ //#endregion
2238
+ //#region ---- Core profile sections ----
2239
+ /** Registers built-in profile hub section cards and actions. */
2240
+ const CoreProfileSectionsHookProvider = {
2241
+ key: PROFILE_PAGE_SECTIONS_HOOK,
2242
+ priority: 0,
2243
+ execute: (payload) => ({
2244
+ ...payload,
2245
+ sections: [
2246
+ ...payload.sections,
2247
+ {
2248
+ id: 'basic-info',
2249
+ groupId: 'account',
2250
+ title: '@auth:profile-page.sections.basic-info.title',
2251
+ description: '@auth:profile-page.sections.basic-info.description',
2252
+ icon: 'far fa-address-card',
2253
+ priority: 10,
2254
+ layout: { colSpan: 6 },
2255
+ actions: [
2256
+ {
2257
+ name: 'edit-basic-info',
2258
+ title: '@auth:profile-page.sections.basic-info.actions.edit',
2259
+ icon: 'far fa-pen',
2260
+ command: { name: AXMAuthCommandKeys.Profile.EditAccountInfo },
2261
+ },
2262
+ ],
2263
+ },
2264
+ {
2265
+ id: 'profile-photo',
2266
+ groupId: 'account',
2267
+ title: '@auth:profile-page.sections.profile-photo.title',
2268
+ description: '@auth:profile-page.sections.profile-photo.description',
2269
+ icon: 'far fa-camera',
2270
+ priority: 15,
2271
+ layout: { colSpan: 6 },
2272
+ actions: [
2273
+ {
2274
+ name: 'change-profile-photo',
2275
+ title: '@auth:profile-page.sections.profile-photo.actions.manage',
2276
+ icon: 'far fa-pen',
2277
+ command: { name: AXMAuthCommandKeys.Profile.ChangePhoto, options: { mode: 'manage' } },
2278
+ },
2279
+ ],
2280
+ },
2281
+ {
2282
+ id: 'change-password',
2283
+ groupId: 'security',
2284
+ title: '@auth:profile-page.sections.change-password.title',
2285
+ description: '@auth:profile-page.sections.change-password.description',
2286
+ icon: 'far fa-key',
2287
+ priority: 10,
2288
+ layout: { colSpan: 6 },
2289
+ actions: [
2290
+ {
2291
+ name: 'change-password',
2292
+ title: '@auth:profile-page.sections.change-password.actions.manage',
2293
+ icon: 'far fa-pen',
2294
+ command: { name: AXMAuthCommandKeys.Security.ChangePassword },
2295
+ },
2296
+ ],
2297
+ },
2298
+ {
2299
+ id: 'security-pin',
2300
+ groupId: 'security',
2301
+ title: '@auth:profile-page.sections.security-pin.title',
2302
+ description: '@auth:profile-page.sections.security-pin.description',
2303
+ icon: 'far fa-lock',
2304
+ priority: 15,
2305
+ layout: { colSpan: 6 },
2306
+ },
2307
+ {
2308
+ id: 'active-sessions',
2309
+ groupId: 'security',
2310
+ title: '@auth:profile-page.sections.active-sessions.title',
2311
+ description: '@auth:profile-page.sections.active-sessions.description',
2312
+ icon: 'far fa-laptop-mobile',
2313
+ priority: 20,
2314
+ layout: { colSpan: 6 },
2315
+ actions: [
2316
+ {
2317
+ name: 'active-sessions',
2318
+ title: '@auth:profile-page.sections.active-sessions.actions.manage',
2319
+ icon: 'far fa-arrow-right',
2320
+ command: { name: AXMAuthCommandKeys.Session.OpenSessions },
2321
+ },
2322
+ ],
2323
+ },
2324
+ {
2325
+ id: 'two-factor',
2326
+ groupId: 'security',
2327
+ title: '@auth:profile-page.sections.two-factor.title',
2328
+ description: '@auth:profile-page.sections.two-factor.description',
2329
+ icon: 'far fa-shield-keyhole',
2330
+ priority: 30,
2331
+ layout: { colSpan: 6 },
2332
+ actions: [
2333
+ {
2334
+ name: 'manage-two-factor',
2335
+ title: '@auth:profile-page.sections.two-factor.actions.manage',
2336
+ icon: 'far fa-pen',
2337
+ command: comingSoon('@auth:profile-page.commands.manage-two-factor.coming-soon'),
2338
+ },
2339
+ ],
2340
+ },
2341
+ {
2342
+ id: 'login-history',
2343
+ groupId: 'security',
2344
+ title: '@auth:profile-page.sections.login-history.title',
2345
+ description: '@auth:profile-page.sections.login-history.description',
2346
+ icon: 'far fa-right-to-bracket',
2347
+ priority: 40,
2348
+ layout: { colSpan: 6 },
2349
+ actions: [
2350
+ {
2351
+ name: 'view-login-history',
2352
+ title: '@auth:profile-page.sections.login-history.actions.manage',
2353
+ icon: 'far fa-arrow-right',
2354
+ command: { name: AXMAuthCommandKeys.Security.ViewLoginHistory },
2355
+ },
2356
+ ],
2357
+ },
2358
+ {
2359
+ id: 'delete-account',
2360
+ groupId: 'account',
2361
+ priority: 100,
2362
+ content: {
2363
+ mode: 'component',
2364
+ componentKey: PROFILE_DELETE_ACCOUNT_DANGER_ZONE_COMPONENT_KEY,
2365
+ },
2366
+ layout: { colSpan: 12 },
2367
+ },
2368
+ ],
2369
+ }),
2370
+ };
2371
+ //#endregion
2372
+
2373
+ //#region ---- Security PIN section hook ----
2374
+ const SECURITY_PIN_SECTION_ID = 'security-pin';
2375
+ /** Enriches the security PIN section with change/remove actions when a PIN exists. */
2376
+ const SecurityPinSectionHookProvider = {
2377
+ key: PROFILE_PAGE_SECTIONS_HOOK,
2378
+ priority: 10,
2379
+ execute: async (payload) => {
2380
+ const hasPinCode = await loadHasPinCode();
2381
+ return {
2382
+ ...payload,
2383
+ sections: payload.sections.map((section) => {
2384
+ if (section.id !== SECURITY_PIN_SECTION_ID) {
2385
+ return section;
2386
+ }
2387
+ return {
2388
+ ...section,
2389
+ actions: [
2390
+ {
2391
+ name: 'set-pin-code',
2392
+ title: hasPinCode
2393
+ ? '@auth:profile-page.sections.security-pin.actions.change'
2394
+ : '@auth:profile-page.sections.security-pin.actions.manage',
2395
+ icon: 'far fa-pen',
2396
+ default: true,
2397
+ command: { name: AXMAuthCommandKeys.Security.SetPinCode },
2398
+ },
2399
+ ...(hasPinCode
2400
+ ? [
2401
+ {
2402
+ name: 'remove-pin-code',
2403
+ title: '@auth:profile-page.sections.security-pin.actions.remove',
2404
+ icon: 'far fa-trash-can',
2405
+ color: 'danger',
2406
+ command: { name: AXMAuthCommandKeys.Security.RemovePinCode },
2407
+ },
2408
+ ]
2409
+ : []),
2410
+ ],
2411
+ };
2412
+ }),
2413
+ };
2414
+ },
2415
+ };
2416
+ async function loadHasPinCode() {
2417
+ const sessionService = inject(AXPSessionService);
2418
+ const entityService = inject(AXPEntityService);
2419
+ const userId = sessionService.user?.id;
2420
+ if (isNil(userId)) {
2421
+ return false;
2422
+ }
2423
+ try {
2424
+ const user = await entityService
2425
+ .withEntity(RootConfig$1.module.name, RootConfig$1.entities.users.name)
2426
+ .data()
2427
+ .byKey(userId);
2428
+ return !isEmpty(defaultTo(get(user, 'pinCode'), ''));
2429
+ }
2430
+ catch (error) {
2431
+ console.error('Failed to load security PIN for profile section', error);
2432
+ return false;
2433
+ }
2434
+ }
2435
+ //#endregion
2436
+
2437
+ //#region ---- Password change tracking service ----
2438
+ const STORAGE_KEY_PREFIX = 'axp-auth:last-password-change-at:';
2439
+ /** Persists the last self-service password change timestamp per user (client-side). */
2440
+ class AXPAuthPasswordChangeTrackingService {
2441
+ constructor() {
2442
+ //#region ---- Services & Dependencies ----
2443
+ this.sessionService = inject(AXPSessionService);
2444
+ }
2445
+ //#endregion
2446
+ //#region ---- Public API ----
2447
+ getLastChangedAt(userId) {
2448
+ const id = userId ?? this.sessionService.user?.id;
2449
+ if (isNil(id)) {
2450
+ return undefined;
2451
+ }
2452
+ const raw = localStorage.getItem(this.buildStorageKey(id));
2453
+ if (!raw) {
2454
+ return undefined;
2455
+ }
2456
+ const parsed = new Date(raw);
2457
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
2458
+ }
2459
+ recordChange(userId, changedAt = new Date()) {
2460
+ const id = userId ?? this.sessionService.user?.id;
2461
+ if (isNil(id)) {
2462
+ return;
2463
+ }
2464
+ localStorage.setItem(this.buildStorageKey(id), changedAt.toISOString());
2465
+ }
2466
+ //#endregion
2467
+ //#region ---- Private Methods ----
2468
+ buildStorageKey(userId) {
2469
+ return `${STORAGE_KEY_PREFIX}${userId}`;
2470
+ }
2471
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthPasswordChangeTrackingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2472
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthPasswordChangeTrackingService, providedIn: 'root' }); }
2473
+ }
2474
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAuthPasswordChangeTrackingService, decorators: [{
2475
+ type: Injectable,
2476
+ args: [{ providedIn: 'root' }]
2477
+ }] });
2478
+
2479
+ //#region ---- Section ids ----
2480
+ const CHANGE_PASSWORD_SECTION_ID = 'change-password';
2481
+ const ACTIVE_SESSIONS_SECTION_ID = 'active-sessions';
2482
+ const LOGIN_HISTORY_SECTION_ID = 'login-history';
2483
+ //#endregion
2484
+ //#region ---- Profile page security insights service ----
2485
+ /** Loads contextual security insights for profile section cards. */
2486
+ class AXPUserProfileSecurityInsightsService {
2487
+ constructor() {
2488
+ //#region ---- Services & Dependencies ----
2489
+ this.sessionService = inject(AXPSessionService);
2490
+ this.entityService = inject(AXPEntityService);
2491
+ this.translateService = inject(AXTranslationService);
2492
+ this.passwordChangeTrackingService = inject(AXPAuthPasswordChangeTrackingService);
2493
+ }
2494
+ //#endregion
2495
+ //#region ---- Public API ----
2496
+ async loadBySectionId() {
2497
+ const userId = this.sessionService.user?.id;
2498
+ if (!userId) {
2499
+ return {};
2500
+ }
2501
+ const sessions = await this.loadUserSessions(userId);
2502
+ const insightsBySectionId = {};
2503
+ const passwordInsights = this.buildPasswordInsights(userId);
2504
+ if (!isEmpty(passwordInsights)) {
2505
+ insightsBySectionId[CHANGE_PASSWORD_SECTION_ID] = passwordInsights;
2506
+ }
2507
+ const activeSessionInsights = await this.buildActiveSessionInsights(sessions);
2508
+ if (!isEmpty(activeSessionInsights)) {
2509
+ insightsBySectionId[ACTIVE_SESSIONS_SECTION_ID] = activeSessionInsights;
2510
+ }
2511
+ const loginHistoryInsights = this.buildLoginHistoryInsights(sessions);
2512
+ if (!isEmpty(loginHistoryInsights)) {
2513
+ insightsBySectionId[LOGIN_HISTORY_SECTION_ID] = loginHistoryInsights;
2514
+ }
2515
+ return insightsBySectionId;
2516
+ }
2517
+ //#endregion
2518
+ //#region ---- Insight builders ----
2519
+ buildPasswordInsights(userId) {
2520
+ const lastChangedAt = this.passwordChangeTrackingService.getLastChangedAt(userId);
2521
+ if (isNil(lastChangedAt)) {
2522
+ return [];
2523
+ }
2524
+ return [
2525
+ {
2526
+ id: 'last-password-change',
2527
+ icon: 'fa-light fa-fw fa-clock',
2528
+ date: lastChangedAt,
2529
+ datePrefix: '@auth:profile-page.sections.change-password.insights.last-changed',
2530
+ },
2531
+ ];
2532
+ }
2533
+ async buildActiveSessionInsights(sessions) {
2534
+ const activeSessions = sessions.filter((session) => String(session.status).toLowerCase() === 'active');
2535
+ const currentSession = activeSessions.find((session) => session.isActiveSession) ??
2536
+ orderBy(activeSessions, [(session) => this.parseValidDate(session.lastLoginDate)?.getTime() ?? 0], ['desc'])[0];
2537
+ if (isNil(currentSession)) {
2538
+ return [];
2539
+ }
2540
+ const deviceSummary = this.formatSessionSummary(currentSession);
2541
+ if (isEmpty(deviceSummary)) {
2542
+ return [];
2543
+ }
2544
+ const otherActiveCount = activeSessions.filter((session) => session.id !== currentSession.id).length;
2545
+ const insights = [
2546
+ {
2547
+ id: 'current-session',
2548
+ icon: 'fa-light fa-fw fa-laptop',
2549
+ text: deviceSummary,
2550
+ },
2551
+ ];
2552
+ if (otherActiveCount > 0) {
2553
+ insights.push({
2554
+ id: 'other-sessions',
2555
+ icon: 'fa-light fa-fw fa-mobile-screen',
2556
+ text: await this.translateService.translateAsync('@auth:profile-page.sections.active-sessions.insights.other-devices-value', { params: { count: otherActiveCount } }),
2557
+ });
2558
+ }
2559
+ return insights;
2560
+ }
2561
+ buildLoginHistoryInsights(sessions) {
2562
+ const latestSession = orderBy(sessions, [(session) => this.parseValidDate(session.lastLoginDate)?.getTime() ?? 0], ['desc'])[0];
2563
+ if (isNil(latestSession)) {
2564
+ return [];
2565
+ }
2566
+ const insights = [];
2567
+ const lastLoginDate = this.parseValidDate(latestSession.lastLoginDate);
2568
+ if (lastLoginDate) {
2569
+ insights.push({
2570
+ id: 'last-login',
2571
+ icon: 'fa-light fa-fw fa-clock-rotate-left',
2572
+ date: lastLoginDate,
2573
+ datePrefix: '@auth:profile-page.sections.login-history.insights.last-sign-in',
2574
+ });
2575
+ }
2576
+ const location = this.formatLoginLocation(latestSession);
2577
+ if (!isEmpty(location)) {
2578
+ insights.push({
2579
+ id: 'last-login-location',
2580
+ icon: 'fa-light fa-fw fa-location-dot',
2581
+ text: location,
2582
+ });
2583
+ }
2584
+ return insights;
2585
+ }
2586
+ //#endregion
2587
+ //#region ---- Data loading ----
2588
+ async loadUserSessions(userId) {
2589
+ const response = await this.entityService
2590
+ .withEntity(RootConfig$1.module.name, RootConfig$1.entities.deviceSessions.name)
2591
+ .data()
2592
+ .query({ skip: 0, take: 1000 });
2593
+ return (response.items ?? []).filter((item) => item.userId === userId);
2594
+ }
2595
+ //#endregion
2596
+ //#region ---- Formatting ----
2597
+ parseValidDate(value) {
2598
+ if (value instanceof Date) {
2599
+ return Number.isNaN(value.getTime()) ? undefined : value;
2600
+ }
2601
+ if (typeof value === 'string' || typeof value === 'number') {
2602
+ const parsed = new Date(value);
2603
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
2604
+ }
2605
+ return undefined;
2606
+ }
2607
+ formatSessionSummary(session) {
2608
+ const parts = [session.deviceName, session.browser, session.location]
2609
+ .map((part) => defaultTo(part, '').trim())
2610
+ .filter(Boolean);
2611
+ return parts.join(' · ');
2612
+ }
2613
+ formatLoginLocation(session) {
2614
+ const parts = [session.location, session.ipAddress]
2615
+ .map((part) => defaultTo(part, '').trim())
2616
+ .filter(Boolean);
2617
+ return parts.join(' · ');
2618
+ }
2619
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUserProfileSecurityInsightsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2620
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUserProfileSecurityInsightsService, providedIn: 'root' }); }
2621
+ }
2622
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUserProfileSecurityInsightsService, decorators: [{
2623
+ type: Injectable,
2624
+ args: [{ providedIn: 'root' }]
2625
+ }] });
2626
+
2627
+ //#region ---- Security sections insights hook ----
2628
+ /** Adds live security context (sessions, login, password) to profile section cards. */
2629
+ const SecuritySectionsInsightsHookProvider = {
2630
+ key: PROFILE_PAGE_SECTIONS_HOOK,
2631
+ priority: 15,
2632
+ execute: async (payload) => {
2633
+ const insightsService = inject(AXPUserProfileSecurityInsightsService);
2634
+ const insightsBySectionId = await insightsService.loadBySectionId();
2635
+ return {
2636
+ ...payload,
2637
+ sections: payload.sections.map((section) => {
2638
+ const insights = insightsBySectionId[section.id];
2639
+ if (isEmpty(insights)) {
2640
+ return section;
2641
+ }
2642
+ return {
2643
+ ...section,
2644
+ insights,
2645
+ };
2646
+ }),
2647
+ };
2648
+ },
2649
+ };
2650
+ //#endregion
2651
+
2652
+ //#region ---- Profile slot logout menu hook ----
2653
+ /** Registers the sign-out entry in the profile dropdown. */
2654
+ const ProfileSlotLogoutMenuHookProvider = {
2655
+ key: PROFILE_SLOT_MENU_ITEMS_HOOK,
2656
+ priority: 100,
2657
+ execute: (payload) => ({
2658
+ ...payload,
2659
+ items: [
2660
+ ...payload.items,
2661
+ {
2662
+ name: 'logout',
2663
+ title: '@auth:profile.logout',
2664
+ icon: 'fa-light fa-arrow-right-from-bracket',
2665
+ color: 'danger',
2666
+ shortcuts: [{ keys: 'ctrl+shift+q', allowInEditableFields: true }],
2667
+ command: { name: AXMAuthCommandKeys.Session.Signout },
2668
+ },
2669
+ ],
2670
+ }),
2671
+ };
2672
+ //#endregion
2673
+
2674
+ //#region ---- Profile slot my profile menu hook ----
2675
+ /** Registers the My Profile entry in the profile dropdown. */
2676
+ const ProfileSlotMyProfileMenuHookProvider = {
2677
+ key: PROFILE_SLOT_MENU_ITEMS_HOOK,
2678
+ priority: -10,
2679
+ execute: (payload) => {
2680
+ const sessionService = inject(AXPSessionService);
2681
+ const appName = sessionService.application?.name;
2682
+ if (!appName) {
2683
+ return payload;
2684
+ }
2685
+ return {
2686
+ ...payload,
2687
+ items: [
2688
+ ...payload.items,
2689
+ {
2690
+ name: 'my-profile',
2691
+ title: '@auth:profile-page.profile-slot.my-profile',
2692
+ icon: 'fa-light fa-user',
2693
+ shortcuts: [{ keys: 'ctrl+shift+p', allowInEditableFields: true }],
2694
+ command: {
2695
+ name: 'navigate',
2696
+ options: {
2697
+ path: `/${appName}/profile/account`,
2698
+ },
2699
+ },
2700
+ },
2701
+ ],
2702
+ };
2703
+ },
2704
+ };
2705
+ //#endregion
2706
+
2707
+ //#region ---- Profile slot lock menu hook ----
2708
+ /** Registers the manual lock entry when Lock When Idle is enabled. Profile menu reloads on {@link AXPAuthSettings.LockWhenIdleEnabled} changes. */
2709
+ const ProfileSlotLockMenuHookProvider = {
2710
+ key: PROFILE_SLOT_MENU_ITEMS_HOOK,
2711
+ priority: 50,
2712
+ execute: async (payload) => {
2713
+ const idleLockService = inject(AXPIdleLockService);
2714
+ await idleLockService.refreshLockState();
2715
+ if (!idleLockService.canLock()) {
2716
+ return payload;
2717
+ }
2718
+ return {
2719
+ ...payload,
2720
+ items: [
2721
+ ...payload.items,
2722
+ {
2723
+ name: 'lock-screen',
2724
+ title: '@auth:profile.lock',
2725
+ icon: 'fa-light fa-lock',
2726
+ break: { before: true },
2727
+ shortcuts: [{ keys: 'ctrl+alt+l', allowInEditableFields: true }],
2728
+ command: { name: AXMAuthCommandKeys.Session.LockScreen },
2729
+ },
2730
+ ],
2731
+ };
2732
+ },
2733
+ };
2734
+ //#endregion
2735
+
2736
+ //#region ---- User avatar client middleware ----
2737
+ const USER_ENTITY = `${RootConfig$1.module.name}.${RootConfig$1.entities.users.name}`;
2738
+ const PERSON_ENTITY = `${RootConfig.module.name}.${RootConfig.entities.person.name}`;
2739
+ function collectUserRows(result) {
2740
+ if (Array.isArray(result)) {
2741
+ return result;
2742
+ }
2743
+ if (result && typeof result === 'object' && Array.isArray(result.items)) {
2744
+ return result.items ?? [];
2745
+ }
2746
+ if (result && typeof result === 'object') {
2747
+ return [result];
2748
+ }
2749
+ return [];
2750
+ }
2751
+ async function loadPersonImagesByUserIds(ctx, userIds) {
2752
+ const imageByUserId = new Map();
2753
+ if (!userIds.length) {
2754
+ return imageByUserId;
2755
+ }
2756
+ try {
2757
+ const response = await ctx.backend.query(PERSON_ENTITY, {
2758
+ skip: 0,
2759
+ take: userIds.length,
2760
+ filter: { field: 'userId', operator: { type: 'in' }, value: userIds },
2761
+ });
2762
+ for (const item of response?.items ?? []) {
2763
+ const person = item;
2764
+ const userId = get(person, 'userId');
2765
+ const image = normalizeAvatarRef(person['image']);
2766
+ if (userId != null && image) {
2767
+ imageByUserId.set(String(userId), image);
2768
+ }
2769
+ }
2770
+ }
2771
+ catch {
2772
+ /* ignore hydration failures */
2773
+ }
2774
+ return imageByUserId;
2775
+ }
2776
+ /** Hydrates User.avatar from linked Person.image on read. */
2777
+ const userAvatarHydrateMiddleware = {
2778
+ target: { ops: ['getOne', 'getAll', 'query'], order: 13 },
2779
+ execute: async (ctx, next) => {
2780
+ await next();
2781
+ if (ctx.entityName !== USER_ENTITY) {
2782
+ return;
2783
+ }
2784
+ const rows = collectUserRows(ctx.result);
2785
+ const userIds = Array.from(new Set(rows.map((row) => get(row, 'id')).filter((id) => id != null).map(String)));
2786
+ const imageByUserId = await loadPersonImagesByUserIds(ctx, userIds);
2787
+ for (const row of rows) {
2788
+ const userId = get(row, 'id');
2789
+ if (userId == null) {
2790
+ continue;
2791
+ }
2792
+ const personImage = imageByUserId.get(String(userId));
2793
+ if (personImage) {
2794
+ row['avatar'] = personImage;
2795
+ }
2796
+ }
2797
+ },
2798
+ };
2799
+ /** Refreshes avatar cache/session after user detail saves avatar. */
2800
+ const userAvatarCacheRefreshMiddleware = {
2801
+ target: { ops: ['update'], order: 95 },
2802
+ execute: async (ctx, next) => {
2803
+ const avatarSync = inject(AXPUserAvatarSyncService);
2804
+ if (ctx.entityName !== USER_ENTITY) {
2805
+ await next();
2806
+ return;
2807
+ }
2808
+ const data = (ctx.data ?? {});
2809
+ const avatarTouched = Object.prototype.hasOwnProperty.call(data, 'avatar');
2810
+ await next();
2811
+ if (!avatarTouched) {
2812
+ return;
2813
+ }
2814
+ const userId = (ctx.id ?? get(ctx.result, 'id'));
2815
+ if (!userId) {
2816
+ return;
2817
+ }
2818
+ const avatar = normalizeAvatarRef(get(ctx.result, 'avatar') ?? data['avatar']);
2819
+ await avatarSync.notifyAvatarChanged(userId, avatar);
2820
+ },
2821
+ };
2822
+ //#endregion
2823
+
2824
+ function profileRoutesFactory() {
2825
+ return [
2826
+ {
2827
+ path: ':app/profile',
2828
+ canActivate: [...AXP_PROTECTED_ROUTE_GUARDS],
2829
+ data: { tab: true, rootLayout: true },
2830
+ loadComponent: () => AXPRootLayoutComponent,
2831
+ children: [
2832
+ {
2833
+ path: '',
2834
+ redirectTo: 'account',
2835
+ pathMatch: 'full',
2836
+ },
2837
+ {
2838
+ path: ':groupId',
2839
+ loadComponent: () => import('./acorex-platform-module-auth-client-profile-page.component-tbhf7K0V.mjs').then((c) => c.AXMUserProfilePageComponent),
2840
+ data: { reuse: true },
2841
+ },
2842
+ ],
2843
+ },
2844
+ ];
2845
+ }
2846
+ class AXMAuthModule {
2847
+ constructor() {
2848
+ inject(AXPIdleLockService);
2849
+ inject(AXPLogoEntitySyncService);
2850
+ }
2851
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
2852
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthModule, imports: [CommonModule, i1$4.AXPCommonModule, i1$5.RouterModule, AXPAccountModule,
2853
+ AXMAuthValidationRulesModule] }); }
2854
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthModule, providers: [
2855
+ provideAppInitializer(() => {
2856
+ const sessionService = inject(AXPSessionService);
2857
+ const idleLockService = inject(AXPIdleLockService);
2858
+ return (async () => {
2859
+ await firstValueFrom(sessionService.isLoading$.pipe(filter((loading) => !loading)));
2860
+ if (sessionService.isLocked()) {
2861
+ await idleLockService.restoreFromPersistedSession();
2862
+ }
2863
+ })();
2864
+ }),
2865
+ AXMAuthErrorHandler,
2866
+ provideLazyProvider(AXP_MENU_PROVIDER, () => import('./acorex-platform-module-auth-client-menu.provider-Bz-kRip4.mjs').then(m => m.AXMSecurityManagementModuleMenuProvider)),
2867
+ provideLazyProvider(AXP_SETTING_DEFINITION_PROVIDER, () => import('./acorex-platform-module-auth-client-settings.provider-n1qbs6e1.mjs').then(m => m.AXMSettingProvider)),
2868
+ provideLazyProvider(AXP_PAGE_COMPONENT_PROVIDER, () => import('./acorex-platform-module-auth-client-profile-page-component.provider-B-Qtd4_j.mjs').then((module) => module.AXMProfilePageComponentProvider)),
2869
+ provideLazyProvider(AXP_DATASOURCE_DEFINITION_PROVIDER, () => Promise.resolve().then(function () { return datasource_provider; }).then((m) => m.AXMAuthDataSourceProvider)),
2870
+ {
2871
+ provide: ROUTES,
2872
+ multi: true,
2873
+ useFactory: profileRoutesFactory,
2874
+ },
2875
+ {
2876
+ provide: AXP_ENTITY_STORAGE_MIDDLEWARE,
2877
+ multi: true,
2878
+ useValue: userAvatarHydrateMiddleware,
2879
+ },
2880
+ {
2881
+ provide: AXP_ENTITY_STORAGE_MIDDLEWARE,
2882
+ multi: true,
2883
+ useValue: userAvatarCacheRefreshMiddleware,
2884
+ },
2885
+ provideCommandSetups([
2886
+ {
2887
+ key: AXMAuthCommandKeys.Profile.Show,
2888
+ command: () => import('./acorex-platform-module-auth-client-show-profile-command-CxMz1yy-.mjs').then((c) => c.AXPShowProfileCommand),
2889
+ },
2890
+ {
2891
+ key: AXMAuthCommandKeys.Session.Signout,
2892
+ command: () => import('./acorex-platform-module-auth-client-signout-command-DqqrP5pk.mjs').then((c) => c.AXPSignoutCommand),
2893
+ },
2894
+ {
2895
+ key: AXMAuthCommandKeys.Session.OpenSessions,
2896
+ command: () => import('./acorex-platform-module-auth-client-open-sessions.command-CXSGuqYp.mjs').then((c) => c.AXPOpenSessionsCommand),
2897
+ },
2898
+ {
2899
+ key: AXMAuthCommandKeys.Session.LockScreen,
2900
+ command: () => import('./acorex-platform-module-auth-client-lock-screen.command-41QnG1TQ.mjs').then((c) => c.AXPLockScreenCommand),
2901
+ },
2902
+ {
2903
+ key: AXMAuthCommandKeys.Profile.ChangePhoto,
2904
+ command: () => import('./acorex-platform-module-auth-client-change-photo.command-BC8lw--F.mjs').then((c) => c.AXPChangePhotoCommand),
2905
+ },
2906
+ {
2907
+ key: AXMAuthCommandKeys.Profile.EditAccountInfo,
2908
+ command: () => import('./acorex-platform-module-auth-client-edit-account-info.command-B-mVlXxg.mjs').then((c) => c.AXPEditAccountInfoCommand),
2909
+ },
2910
+ {
2911
+ key: AXMAuthCommandKeys.Security.ChangePassword,
2912
+ command: () => import('./acorex-platform-module-auth-client-change-password.command-ZyXAjUKw.mjs').then((c) => c.AXPChangePasswordCommand),
2913
+ },
2914
+ {
2915
+ key: AXMAuthCommandKeys.Security.SetPinCode,
2916
+ command: () => import('./acorex-platform-module-auth-client-set-pin-code.command-DXTVbrS0.mjs').then((c) => c.AXPSetPinCodeCommand),
2917
+ },
2918
+ {
2919
+ key: AXMAuthCommandKeys.Security.RemovePinCode,
2920
+ command: () => import('./acorex-platform-module-auth-client-remove-pin-code.command-zUZooOtv.mjs').then((c) => c.AXPRemovePinCodeCommand),
2921
+ },
2922
+ {
2923
+ key: AXMAuthCommandKeys.Security.ViewLoginHistory,
2924
+ command: () => import('./acorex-platform-module-auth-client-view-login-history.command-Dl0os5lD.mjs').then((c) => c.AXPViewLoginHistoryCommand),
2925
+ },
2926
+ {
2927
+ key: AXMAuthCommandKeys.Security.EditSignature,
2928
+ command: () => import('./acorex-platform-module-auth-client-edit-signature.command-CcTJJwAX.mjs').then((c) => c.AXPEditSignatureCommand),
2929
+ },
2930
+ {
2931
+ key: AXMAuthCommandKeys.Profile.ComingSoon,
2932
+ command: () => import('./acorex-platform-module-auth-client-profile-coming-soon.command-DBbFbzAx.mjs').then((c) => c.AXPProfileComingSoonCommand),
2933
+ },
2934
+ ]),
2935
+ {
2936
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
2937
+ useValue: ProfileSlotLogoutMenuHookProvider,
2938
+ multi: true,
2939
+ },
2940
+ {
2941
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
2942
+ useValue: ProfileSlotMyProfileMenuHookProvider,
2943
+ multi: true,
2944
+ },
2945
+ {
2946
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
2947
+ useValue: ProfileSlotLockMenuHookProvider,
2948
+ multi: true,
2949
+ },
2950
+ {
2951
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
2952
+ useValue: CoreProfileGroupsHookProvider,
2953
+ multi: true,
2954
+ },
2955
+ {
2956
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
2957
+ useValue: CoreProfileSectionsHookProvider,
2958
+ multi: true,
2959
+ },
2960
+ {
2961
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
2962
+ useValue: SecurityPinSectionHookProvider,
2963
+ multi: true,
2964
+ },
2965
+ {
2966
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
2967
+ useValue: SecuritySectionsInsightsHookProvider,
2968
+ multi: true,
2969
+ },
2970
+ ], imports: [CommonModule,
2971
+ AXPCommonModule.forChild({ errorHandlers: [AXMAuthErrorHandler] }),
2972
+ RouterModule.forChild(routes),
2973
+ AXPAccountModule,
2974
+ AXMAuthValidationRulesModule] }); }
2975
+ }
2976
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMAuthModule, decorators: [{
2977
+ type: NgModule,
2978
+ args: [{
2979
+ imports: [
2980
+ CommonModule,
2981
+ AXPCommonModule.forChild({ errorHandlers: [AXMAuthErrorHandler] }),
2982
+ RouterModule.forChild(routes),
2983
+ AXPAccountModule,
2984
+ AXMAuthValidationRulesModule,
2985
+ ],
2986
+ providers: [
2987
+ provideAppInitializer(() => {
2988
+ const sessionService = inject(AXPSessionService);
2989
+ const idleLockService = inject(AXPIdleLockService);
2990
+ return (async () => {
2991
+ await firstValueFrom(sessionService.isLoading$.pipe(filter((loading) => !loading)));
2992
+ if (sessionService.isLocked()) {
2993
+ await idleLockService.restoreFromPersistedSession();
2994
+ }
2995
+ })();
2996
+ }),
2997
+ AXMAuthErrorHandler,
2998
+ provideLazyProvider(AXP_MENU_PROVIDER, () => import('./acorex-platform-module-auth-client-menu.provider-Bz-kRip4.mjs').then(m => m.AXMSecurityManagementModuleMenuProvider)),
2999
+ provideLazyProvider(AXP_SETTING_DEFINITION_PROVIDER, () => import('./acorex-platform-module-auth-client-settings.provider-n1qbs6e1.mjs').then(m => m.AXMSettingProvider)),
3000
+ provideLazyProvider(AXP_PAGE_COMPONENT_PROVIDER, () => import('./acorex-platform-module-auth-client-profile-page-component.provider-B-Qtd4_j.mjs').then((module) => module.AXMProfilePageComponentProvider)),
3001
+ provideLazyProvider(AXP_DATASOURCE_DEFINITION_PROVIDER, () => Promise.resolve().then(function () { return datasource_provider; }).then((m) => m.AXMAuthDataSourceProvider)),
3002
+ {
3003
+ provide: ROUTES,
3004
+ multi: true,
3005
+ useFactory: profileRoutesFactory,
3006
+ },
3007
+ {
3008
+ provide: AXP_ENTITY_STORAGE_MIDDLEWARE,
3009
+ multi: true,
3010
+ useValue: userAvatarHydrateMiddleware,
3011
+ },
3012
+ {
3013
+ provide: AXP_ENTITY_STORAGE_MIDDLEWARE,
3014
+ multi: true,
3015
+ useValue: userAvatarCacheRefreshMiddleware,
3016
+ },
3017
+ provideCommandSetups([
3018
+ {
3019
+ key: AXMAuthCommandKeys.Profile.Show,
3020
+ command: () => import('./acorex-platform-module-auth-client-show-profile-command-CxMz1yy-.mjs').then((c) => c.AXPShowProfileCommand),
3021
+ },
3022
+ {
3023
+ key: AXMAuthCommandKeys.Session.Signout,
3024
+ command: () => import('./acorex-platform-module-auth-client-signout-command-DqqrP5pk.mjs').then((c) => c.AXPSignoutCommand),
3025
+ },
3026
+ {
3027
+ key: AXMAuthCommandKeys.Session.OpenSessions,
3028
+ command: () => import('./acorex-platform-module-auth-client-open-sessions.command-CXSGuqYp.mjs').then((c) => c.AXPOpenSessionsCommand),
3029
+ },
3030
+ {
3031
+ key: AXMAuthCommandKeys.Session.LockScreen,
3032
+ command: () => import('./acorex-platform-module-auth-client-lock-screen.command-41QnG1TQ.mjs').then((c) => c.AXPLockScreenCommand),
3033
+ },
3034
+ {
3035
+ key: AXMAuthCommandKeys.Profile.ChangePhoto,
3036
+ command: () => import('./acorex-platform-module-auth-client-change-photo.command-BC8lw--F.mjs').then((c) => c.AXPChangePhotoCommand),
3037
+ },
3038
+ {
3039
+ key: AXMAuthCommandKeys.Profile.EditAccountInfo,
3040
+ command: () => import('./acorex-platform-module-auth-client-edit-account-info.command-B-mVlXxg.mjs').then((c) => c.AXPEditAccountInfoCommand),
3041
+ },
3042
+ {
3043
+ key: AXMAuthCommandKeys.Security.ChangePassword,
3044
+ command: () => import('./acorex-platform-module-auth-client-change-password.command-ZyXAjUKw.mjs').then((c) => c.AXPChangePasswordCommand),
3045
+ },
3046
+ {
3047
+ key: AXMAuthCommandKeys.Security.SetPinCode,
3048
+ command: () => import('./acorex-platform-module-auth-client-set-pin-code.command-DXTVbrS0.mjs').then((c) => c.AXPSetPinCodeCommand),
3049
+ },
3050
+ {
3051
+ key: AXMAuthCommandKeys.Security.RemovePinCode,
3052
+ command: () => import('./acorex-platform-module-auth-client-remove-pin-code.command-zUZooOtv.mjs').then((c) => c.AXPRemovePinCodeCommand),
3053
+ },
3054
+ {
3055
+ key: AXMAuthCommandKeys.Security.ViewLoginHistory,
3056
+ command: () => import('./acorex-platform-module-auth-client-view-login-history.command-Dl0os5lD.mjs').then((c) => c.AXPViewLoginHistoryCommand),
3057
+ },
3058
+ {
3059
+ key: AXMAuthCommandKeys.Security.EditSignature,
3060
+ command: () => import('./acorex-platform-module-auth-client-edit-signature.command-CcTJJwAX.mjs').then((c) => c.AXPEditSignatureCommand),
3061
+ },
3062
+ {
3063
+ key: AXMAuthCommandKeys.Profile.ComingSoon,
3064
+ command: () => import('./acorex-platform-module-auth-client-profile-coming-soon.command-DBbFbzAx.mjs').then((c) => c.AXPProfileComingSoonCommand),
3065
+ },
3066
+ ]),
3067
+ {
3068
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
3069
+ useValue: ProfileSlotLogoutMenuHookProvider,
3070
+ multi: true,
3071
+ },
3072
+ {
3073
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
3074
+ useValue: ProfileSlotMyProfileMenuHookProvider,
3075
+ multi: true,
3076
+ },
3077
+ {
3078
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
3079
+ useValue: ProfileSlotLockMenuHookProvider,
3080
+ multi: true,
3081
+ },
3082
+ {
3083
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
3084
+ useValue: CoreProfileGroupsHookProvider,
3085
+ multi: true,
3086
+ },
3087
+ {
3088
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
3089
+ useValue: CoreProfileSectionsHookProvider,
3090
+ multi: true,
3091
+ },
3092
+ {
3093
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
3094
+ useValue: SecurityPinSectionHookProvider,
3095
+ multi: true,
3096
+ },
3097
+ {
3098
+ provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
3099
+ useValue: SecuritySectionsInsightsHookProvider,
3100
+ multi: true,
3101
+ },
3102
+ ],
3103
+ }]
3104
+ }], ctorParameters: () => [] });
3105
+
3106
+ //#region ---- Login external provider slot ----
3107
+ /** Slot name for optional extension-provided login buttons (e.g. Google platform login). */
3108
+ const AUTH_LOGIN_EXTERNAL_PROVIDERS_SLOT = 'auth-login-external-providers';
3109
+ //#endregion
3110
+
3111
+ class AXPLoginExternalProvidersComponent {
3112
+ constructor() {
3113
+ this.slotName = AUTH_LOGIN_EXTERNAL_PROVIDERS_SLOT;
3114
+ this.slot = viewChild(AXPComponentSlotDirective, ...(ngDevMode ? [{ debugName: "slot" }] : /* istanbul ignore next */ []));
3115
+ }
3116
+ hasContent() {
3117
+ const slot = this.slot();
3118
+ return !!slot && !slot.isEmpty();
3119
+ }
3120
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLoginExternalProvidersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3121
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.9", type: AXPLoginExternalProvidersComponent, isStandalone: true, selector: "axm-login-external-providers", viewQueries: [{ propertyName: "slot", first: true, predicate: AXPComponentSlotDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<axp-component-slot\n class=\"flex flex-col gap-2 w-full mt-6\"\n #slot=\"slot\"\n [name]=\"slotName\"\n [host]=\"this\"\n></axp-component-slot>\n", dependencies: [{ kind: "ngmodule", type: AXPComponentSlotModule }, { kind: "directive", type: i1$3.AXPComponentSlotDirective, selector: "axp-component-slot", inputs: ["name", "host", "context"], exportAs: ["slot"] }] }); }
3122
+ }
3123
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLoginExternalProvidersComponent, decorators: [{
3124
+ type: Component,
3125
+ args: [{ selector: 'axm-login-external-providers', imports: [AXPComponentSlotModule], template: "<axp-component-slot\n class=\"flex flex-col gap-2 w-full mt-6\"\n #slot=\"slot\"\n [name]=\"slotName\"\n [host]=\"this\"\n></axp-component-slot>\n" }]
3126
+ }], propDecorators: { slot: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPComponentSlotDirective), { isSignal: true }] }] } });
3127
+
3128
+ //#endregion
3129
+ //#region ---- Login history service ----
3130
+ /** Loads sign-in records for the current user from device session storage. */
3131
+ class AXPLoginHistoryService {
3132
+ constructor() {
3133
+ //#region ---- Services & Dependencies ----
3134
+ this.sessionService = inject(AXPSessionService);
3135
+ this.entityService = inject(AXPEntityService);
3136
+ }
3137
+ //#endregion
3138
+ //#region ---- Public API ----
3139
+ async getEntries() {
3140
+ const userId = this.sessionService.user?.id;
3141
+ if (!userId) {
3142
+ return [];
3143
+ }
3144
+ const response = await this.entityService
3145
+ .withEntity(RootConfig$1.module.name, RootConfig$1.entities.deviceSessions.name)
3146
+ .data()
3147
+ .query({ skip: 0, take: 1000 });
3148
+ const items = response.items ?? [];
3149
+ return orderBy(items
3150
+ .filter((item) => item.userId === userId)
3151
+ .map((item) => ({
3152
+ id: item.id,
3153
+ device: item.deviceName ?? item.operatingSystem ?? '—',
3154
+ location: item.location ?? '—',
3155
+ ipAddress: item.ipAddress ?? '—',
3156
+ browser: item.browser ?? '—',
3157
+ status: String(item.status ?? 'unknown').toLowerCase(),
3158
+ lastLoginDate: item.lastLoginDate,
3159
+ })), [(entry) => new Date(entry.lastLoginDate).getTime()], ['desc']);
3160
+ }
3161
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLoginHistoryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3162
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLoginHistoryService, providedIn: 'root' }); }
3163
+ }
3164
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLoginHistoryService, decorators: [{
3165
+ type: Injectable,
3166
+ args: [{ providedIn: 'root' }]
3167
+ }] });
3168
+
3169
+ /**
3170
+ * Generated bundle index. Do not edit.
3171
+ */
3172
+
3173
+ export { AXMUserDeviceSessionsService as A, AXMAuthDataSourceProvider as B, AXMAuthErrorHandler as C, AXMAuthModule as D, AXMDefaultAuthConfigs as E, AXMUserActivityDataSourceDefinition as F, aggregateDailySessionCounts as G, buildUserActivityDataSourceItem as H, buildUserActivityWeeks as I, canActivateLogin as J, computeUserActivityStats as K, configAuthModule as L, countToActivityLevel as M, extractUserActivityEndDate as N, extractUserActivityStartDate as O, PROFILE_PAGE_GROUPS_HOOK as P, extractUserActivityUserId as Q, isUserProfileGroupComponent as R, PROFILE_PAGE_SECTIONS_HOOK as a, PROFILE_PAGE_HEADER_META_HOOK as b, resolveUserProfileSectionContent as c, PROFILE_ACTIVITY_CHART_COMPONENT_KEY as d, PROFILE_DELETE_ACCOUNT_DANGER_ZONE_COMPONENT_KEY as e, AXPIdleLockService as f, AXPUserAvatarSyncService as g, AXPAuthPasswordComplexityService as h, AXPAuthPasswordChangeTrackingService as i, getPasswordComplexityHintKey as j, getPasswordComplexityValidationMessageKey as k, validatePasswordAgainstComplexity as l, AXPLoginHistoryService as m, AXM_AUTH_CONFIG_TOKEN as n, AXM_LOGIN_GUARD_TOKEN as o, AXMAuthenticationTypes as p, AXPTenantChooserListComponent as q, resolveDefaultUserProfileSectionAction as r, AXPTenantChooserDropdownComponent as s, AXPAppChooserListComponent as t, resolveDefaultUserActivityDateRange as u, visibleUserProfileSectionActions as v, formatCompactActivityCount as w, AXPLoginExternalProvidersComponent as x, AUTH_LOGIN_EXTERNAL_PROVIDERS_SLOT as y, AUTH_USER_ACTIVITY_DEFAULT_WEEKS as z };
3174
+ //# sourceMappingURL=acorex-platform-module-auth-client-acorex-platform-module-auth-client-B7tIKOw6.mjs.map