@flusys/ng-auth 9.1.0 → 9.1.2

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 (37) hide show
  1. package/README.md +2 -0
  2. package/fesm2022/flusys-ng-auth-administration-page.component-DmN4KqiR.mjs +146 -0
  3. package/fesm2022/flusys-ng-auth-administration-page.component-DmN4KqiR.mjs.map +1 -0
  4. package/fesm2022/flusys-ng-auth-branch-form.component-Df_sMC2G.mjs +743 -0
  5. package/fesm2022/flusys-ng-auth-branch-form.component-Df_sMC2G.mjs.map +1 -0
  6. package/fesm2022/flusys-ng-auth-branch-list.component-CWat6QWN.mjs +775 -0
  7. package/fesm2022/flusys-ng-auth-branch-list.component-CWat6QWN.mjs.map +1 -0
  8. package/fesm2022/flusys-ng-auth-company-form.component-BvZ4Pn8j.mjs +198 -0
  9. package/fesm2022/flusys-ng-auth-company-form.component-BvZ4Pn8j.mjs.map +1 -0
  10. package/fesm2022/flusys-ng-auth-company-list.component-Corh9AIh.mjs +241 -0
  11. package/fesm2022/flusys-ng-auth-company-list.component-Corh9AIh.mjs.map +1 -0
  12. package/fesm2022/flusys-ng-auth-extension-order.util-2XWSLqii.mjs +6 -0
  13. package/fesm2022/flusys-ng-auth-extension-order.util-2XWSLqii.mjs.map +1 -0
  14. package/fesm2022/flusys-ng-auth-forgot-password-page.component-BRTVZPX6.mjs +384 -0
  15. package/fesm2022/flusys-ng-auth-forgot-password-page.component-BRTVZPX6.mjs.map +1 -0
  16. package/fesm2022/flusys-ng-auth-login-page.component-DQl52Qau.mjs +1074 -0
  17. package/fesm2022/flusys-ng-auth-login-page.component-DQl52Qau.mjs.map +1 -0
  18. package/fesm2022/flusys-ng-auth-profile-page.component-b-8rPEXQ.mjs +1792 -0
  19. package/fesm2022/flusys-ng-auth-profile-page.component-b-8rPEXQ.mjs.map +1 -0
  20. package/fesm2022/flusys-ng-auth-register-page.component-D99y1jiA.mjs +1513 -0
  21. package/fesm2022/flusys-ng-auth-register-page.component-D99y1jiA.mjs.map +1 -0
  22. package/fesm2022/flusys-ng-auth-reset-password-page.component-C0tN_AW1.mjs +538 -0
  23. package/fesm2022/flusys-ng-auth-reset-password-page.component-C0tN_AW1.mjs.map +1 -0
  24. package/fesm2022/flusys-ng-auth-social-callback-page.component-gTD7_LJY.mjs +217 -0
  25. package/fesm2022/flusys-ng-auth-social-callback-page.component-gTD7_LJY.mjs.map +1 -0
  26. package/fesm2022/flusys-ng-auth-social-config-page.component-DpGmcb9n.mjs +541 -0
  27. package/fesm2022/flusys-ng-auth-social-config-page.component-DpGmcb9n.mjs.map +1 -0
  28. package/fesm2022/flusys-ng-auth-user-form.component-CM9UYc0q.mjs +1171 -0
  29. package/fesm2022/flusys-ng-auth-user-form.component-CM9UYc0q.mjs.map +1 -0
  30. package/fesm2022/flusys-ng-auth-user-list.component-Bew7gz-H.mjs +1046 -0
  31. package/fesm2022/flusys-ng-auth-user-list.component-Bew7gz-H.mjs.map +1 -0
  32. package/fesm2022/flusys-ng-auth-verify-email-page.component-DB9iKvt3.mjs +545 -0
  33. package/fesm2022/flusys-ng-auth-verify-email-page.component-DB9iKvt3.mjs.map +1 -0
  34. package/fesm2022/flusys-ng-auth.mjs +3030 -10735
  35. package/fesm2022/flusys-ng-auth.mjs.map +1 -1
  36. package/package.json +5 -5
  37. package/types/flusys-ng-auth.d.ts +978 -1558
@@ -0,0 +1,1792 @@
1
+ import { AUTH_PROFILE_EXTENSION, AUTH_USER_FORM_EXTENSION, AuthApiService, AuthStateService, PROFILE_FIELD_CONTEXT, PasswordPolicyService, UserApiService } from "./flusys-ng-auth.mjs";
2
+ import { t as sortByOrder } from "./flusys-ng-auth-extension-order.util-2XWSLqii.mjs";
3
+ import * as i0 from "@angular/core";
4
+ import { Component, DestroyRef, Injector, computed, effect, inject, signal } from "@angular/core";
5
+ import { APP_CONFIG, getPermissionMode, isCompanyFeatureEnabled, isServiceEnabled } from "@flusys/ng-core";
6
+ import { catchError, finalize, forkJoin, of } from "rxjs";
7
+ import { FILE_PROVIDER, FileUploaderComponent, FileUrlService, HasPermissionDirective, PROFILE_PERMISSION_PROVIDER, TranslatePipe, TranslateService } from "@flusys/ng-shared";
8
+ import { NgComponentOutlet, NgTemplateOutlet } from "@angular/common";
9
+ import { AvatarComponent, CheckboxComponent, FButtonDirective, InputTextDirective, MessageService, PasswordComponent, SelectComponent, SkeletonComponent, TagComponent, TextareaDirective, UiIconComponent } from "@flusys/ng-ui";
10
+ import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
11
+ import * as i1 from "@angular/forms";
12
+ import { FormsModule } from "@angular/forms";
13
+ import { FormField, form, required } from "@angular/forms/signals";
14
+ var ProfilePageComponent = class ProfilePageComponent {
15
+ destroyRef = inject(DestroyRef);
16
+ appConfig = inject(APP_CONFIG);
17
+ messageService = inject(MessageService);
18
+ authState = inject(AuthStateService);
19
+ userService = inject(UserApiService);
20
+ authApiService = inject(AuthApiService);
21
+ passwordPolicy = inject(PasswordPolicyService);
22
+ fileUrlService = inject(FileUrlService);
23
+ fileProvider = inject(FILE_PROVIDER, { optional: true });
24
+ permissionProvider = inject(PROFILE_PERMISSION_PROVIDER, { optional: true });
25
+ profileExtension = inject(AUTH_PROFILE_EXTENSION, { optional: true });
26
+ userFormExtension = inject(AUTH_USER_FORM_EXTENSION, { optional: true });
27
+ translateAdapter = inject(TranslateService, { optional: true });
28
+ translate(key, vars) {
29
+ return this.translateAdapter?.translate(key, vars) ?? key;
30
+ }
31
+ isLoading = signal(false, ...ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []);
32
+ isLoadingPermissions = signal(false, ...ngDevMode ? [{ debugName: "isLoadingPermissions" }] : /* istanbul ignore next */ []);
33
+ isLoadingExtraFields = signal(false, ...ngDevMode ? [{ debugName: "isLoadingExtraFields" }] : /* istanbul ignore next */ []);
34
+ _profilePictureUrl = signal(null, ...ngDevMode ? [{ debugName: "_profilePictureUrl" }] : /* istanbul ignore next */ []);
35
+ profilePictureUrl = this._profilePictureUrl.asReadonly();
36
+ _userRoles = signal([], ...ngDevMode ? [{ debugName: "_userRoles" }] : /* istanbul ignore next */ []);
37
+ userRoles = this._userRoles.asReadonly();
38
+ _userActions = signal([], ...ngDevMode ? [{ debugName: "_userActions" }] : /* istanbul ignore next */ []);
39
+ userActions = this._userActions.asReadonly();
40
+ extraSections = computed(() => {
41
+ if (!this.profileExtension?.getExtraSections) return [];
42
+ return sortByOrder(this.profileExtension.getExtraSections());
43
+ }, ...ngDevMode ? [{ debugName: "extraSections" }] : /* istanbul ignore next */ []);
44
+ editableFields = computed(() => {
45
+ if (!this.profileExtension?.getExtraFields) return [];
46
+ return sortByOrder(this.profileExtension.getExtraFields());
47
+ }, ...ngDevMode ? [{ debugName: "editableFields" }] : /* istanbul ignore next */ []);
48
+ viewOnlyFields = computed(() => {
49
+ if (!this.userFormExtension?.getExtraFields) return [];
50
+ return sortByOrder(this.userFormExtension.getExtraFields());
51
+ }, ...ngDevMode ? [{ debugName: "viewOnlyFields" }] : /* istanbul ignore next */ []);
52
+ hasExtraContent = computed(() => this.extraSections().length > 0 || this.editableFields().length > 0 || this.viewOnlyFields().length > 0, ...ngDevMode ? [{ debugName: "hasExtraContent" }] : /* istanbul ignore next */ []);
53
+ extraFieldsModel = signal({}, ...ngDevMode ? [{ debugName: "extraFieldsModel" }] : /* istanbul ignore next */ []);
54
+ originalExtraFieldsModel = signal({}, ...ngDevMode ? [{ debugName: "originalExtraFieldsModel" }] : /* istanbul ignore next */ []);
55
+ adminFieldsModel = signal({}, ...ngDevMode ? [{ debugName: "adminFieldsModel" }] : /* istanbul ignore next */ []);
56
+ isExtraFieldsDirty = computed(() => {
57
+ const current = this.extraFieldsModel();
58
+ const original = this.originalExtraFieldsModel();
59
+ return JSON.stringify(current) !== JSON.stringify(original);
60
+ }, ...ngDevMode ? [{ debugName: "isExtraFieldsDirty" }] : /* istanbul ignore next */ []);
61
+ extraFieldsValidationError = computed(() => {
62
+ if (!this.profileExtension?.validateExtraFields) return null;
63
+ return this.profileExtension.validateExtraFields(this.extraFieldsModel());
64
+ }, ...ngDevMode ? [{ debugName: "extraFieldsValidationError" }] : /* istanbul ignore next */ []);
65
+ userEmail = computed(() => this.authState.user()?.email ?? "", ...ngDevMode ? [{ debugName: "userEmail" }] : /* istanbul ignore next */ []);
66
+ userName = computed(() => this.authState.userName(), ...ngDevMode ? [{ debugName: "userName" }] : /* istanbul ignore next */ []);
67
+ userInitials = computed(() => {
68
+ return (this.authState.user()?.name ?? "").split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2);
69
+ }, ...ngDevMode ? [{ debugName: "userInitials" }] : /* istanbul ignore next */ []);
70
+ companyName = computed(() => this.authState.companyName(), ...ngDevMode ? [{ debugName: "companyName" }] : /* istanbul ignore next */ []);
71
+ branchName = computed(() => this.authState.branchName(), ...ngDevMode ? [{ debugName: "branchName" }] : /* istanbul ignore next */ []);
72
+ showUploadSection = computed(() => isServiceEnabled(this.appConfig, "storage") && this.fileProvider !== null, ...ngDevMode ? [{ debugName: "showUploadSection" }] : /* istanbul ignore next */ []);
73
+ showCompanySection = computed(() => isCompanyFeatureEnabled(this.appConfig), ...ngDevMode ? [{ debugName: "showCompanySection" }] : /* istanbul ignore next */ []);
74
+ showPermissionsSection = computed(() => isServiceEnabled(this.appConfig, "iam") && this.permissionProvider !== null, ...ngDevMode ? [{ debugName: "showPermissionsSection" }] : /* istanbul ignore next */ []);
75
+ permissionMode = computed(() => getPermissionMode(this.appConfig), ...ngDevMode ? [{ debugName: "permissionMode" }] : /* istanbul ignore next */ []);
76
+ showRoles = computed(() => {
77
+ const mode = this.permissionMode();
78
+ return mode === "rbac" || mode === "full";
79
+ }, ...ngDevMode ? [{ debugName: "showRoles" }] : /* istanbul ignore next */ []);
80
+ showActions = computed(() => {
81
+ const mode = this.permissionMode();
82
+ return mode === "direct" || mode === "full";
83
+ }, ...ngDevMode ? [{ debugName: "showActions" }] : /* istanbul ignore next */ []);
84
+ showBottomRow = computed(() => this.showCompanySection() || this.showPermissionsSection(), ...ngDevMode ? [{ debugName: "showBottomRow" }] : /* istanbul ignore next */ []);
85
+ originalFormModel = signal({
86
+ name: "",
87
+ phone: ""
88
+ }, ...ngDevMode ? [{ debugName: "originalFormModel" }] : /* istanbul ignore next */ []);
89
+ formModel = signal({
90
+ name: "",
91
+ phone: ""
92
+ }, ...ngDevMode ? [{ debugName: "formModel" }] : /* istanbul ignore next */ []);
93
+ formTree = form(this.formModel, (f) => {
94
+ required(f.name, { message: this.translate("shared.name.required") });
95
+ });
96
+ isFormValid = computed(() => this.formModel().name.trim().length > 0, ...ngDevMode ? [{ debugName: "isFormValid" }] : /* istanbul ignore next */ []);
97
+ isFormDirty = computed(() => {
98
+ const current = this.formModel();
99
+ const original = this.originalFormModel();
100
+ return current.name !== original.name || current.phone !== original.phone || this.isExtraFieldsDirty();
101
+ }, ...ngDevMode ? [{ debugName: "isFormDirty" }] : /* istanbul ignore next */ []);
102
+ updateExtraField(key, value) {
103
+ this.extraFieldsModel.update((model) => ({
104
+ ...model,
105
+ [key]: value
106
+ }));
107
+ }
108
+ getExtraFieldValue(key) {
109
+ return this.extraFieldsModel()[key];
110
+ }
111
+ getAdminFieldValue(key) {
112
+ return this.adminFieldsModel()[key];
113
+ }
114
+ getSelectLabel(field, value) {
115
+ if (!value || !field.options) return "-";
116
+ return field.options.find((opt) => opt.value === value)?.label ?? String(value);
117
+ }
118
+ getFieldTemplateContext(field) {
119
+ return {
120
+ field,
121
+ value: this.extraFieldsModel()[field.key],
122
+ updateValue: (value) => this.updateExtraField(field.key, value)
123
+ };
124
+ }
125
+ parentInjector = inject(Injector);
126
+ fieldInjectors = /* @__PURE__ */ new Map();
127
+ getFieldInjector(field) {
128
+ const cached = this.fieldInjectors.get(field.key);
129
+ if (cached) {
130
+ cached.setField(field);
131
+ return cached.injector;
132
+ }
133
+ let currentField = field;
134
+ const extraFieldsModel = this.extraFieldsModel;
135
+ const injector = Injector.create({
136
+ providers: [{
137
+ provide: PROFILE_FIELD_CONTEXT,
138
+ useValue: {
139
+ get field() {
140
+ return currentField;
141
+ },
142
+ get value() {
143
+ return extraFieldsModel()[currentField.key];
144
+ },
145
+ updateValue: (value) => this.updateExtraField(currentField.key, value)
146
+ }
147
+ }],
148
+ parent: field.injector ?? this.parentInjector
149
+ });
150
+ this.fieldInjectors.set(field.key, {
151
+ injector,
152
+ setField: (next) => currentField = next
153
+ });
154
+ return injector;
155
+ }
156
+ passwordModel = signal({
157
+ currentPassword: "",
158
+ newPassword: "",
159
+ confirmPassword: ""
160
+ }, ...ngDevMode ? [{ debugName: "passwordModel" }] : /* istanbul ignore next */ []);
161
+ passwordForm = form(this.passwordModel, (f) => {
162
+ required(f.currentPassword, { message: this.translate("auth.password.change.current.required") });
163
+ required(f.newPassword, { message: this.translate("auth.validation.password.required") });
164
+ });
165
+ isPasswordLoading = signal(false, ...ngDevMode ? [{ debugName: "isPasswordLoading" }] : /* istanbul ignore next */ []);
166
+ newPasswordError = computed(() => this.passwordPolicy.validate(this.passwordModel().newPassword), ...ngDevMode ? [{ debugName: "newPasswordError" }] : /* istanbul ignore next */ []);
167
+ isPasswordFormValid = computed(() => {
168
+ const model = this.passwordModel();
169
+ return model.currentPassword.length > 0 && model.newPassword.length > 0 && !this.newPasswordError() && model.newPassword === model.confirmPassword;
170
+ }, ...ngDevMode ? [{ debugName: "isPasswordFormValid" }] : /* istanbul ignore next */ []);
171
+ passwordsMatch = computed(() => {
172
+ const model = this.passwordModel();
173
+ return model.confirmPassword.length === 0 || model.newPassword === model.confirmPassword;
174
+ }, ...ngDevMode ? [{ debugName: "passwordsMatch" }] : /* istanbul ignore next */ []);
175
+ profileUploadOptions = {
176
+ folderPath: "profile-pictures",
177
+ compress: true,
178
+ maxWidth: 400,
179
+ maxHeight: 400,
180
+ isPrivate: true
181
+ };
182
+ constructor() {
183
+ this.loadUserData();
184
+ effect(() => {
185
+ if (this.showPermissionsSection()) this.loadPermissions();
186
+ });
187
+ effect(() => {
188
+ if (this.authState.user() && this.hasExtraContent()) this.loadExtraFields();
189
+ });
190
+ }
191
+ loadUserData() {
192
+ const user = this.authState.user();
193
+ if (user) {
194
+ const initialValues = {
195
+ name: user.name ?? "",
196
+ phone: user.phone ?? ""
197
+ };
198
+ this.originalFormModel.set(initialValues);
199
+ this.formModel.set(initialValues);
200
+ if (user.profilePictureId && isServiceEnabled(this.appConfig, "storage")) this.fileUrlService.fetchSingleFileUrl(user.profilePictureId).pipe(takeUntilDestroyed(this.destroyRef)).subscribe((file) => this._profilePictureUrl.set(file?.url ?? null));
201
+ }
202
+ }
203
+ loadPermissions() {
204
+ const user = this.authState.user();
205
+ const branch = this.authState.branch();
206
+ if (!user || !this.permissionProvider) return;
207
+ const loadRoles = this.showRoles();
208
+ const loadActions = this.showActions();
209
+ if (!loadRoles && !loadActions) return;
210
+ this.isLoadingPermissions.set(true);
211
+ const roles$ = loadRoles ? this.permissionProvider.getUserRoles(user.id, branch?.id).pipe(catchError(() => of({ data: [] }))) : of({ data: [] });
212
+ const actions$ = loadActions ? this.permissionProvider.getUserActions(user.id, branch?.id).pipe(catchError(() => of({ data: [] }))) : of({ data: [] });
213
+ forkJoin([roles$, actions$]).pipe(takeUntilDestroyed(this.destroyRef), finalize(() => this.isLoadingPermissions.set(false))).subscribe(([rolesResponse, actionsResponse]) => {
214
+ this._userRoles.set(rolesResponse.data ?? []);
215
+ this._userActions.set(actionsResponse.data ?? []);
216
+ });
217
+ }
218
+ loadExtraFields() {
219
+ const user = this.authState.user();
220
+ if (!user) return;
221
+ const hasProfileFields = !!this.profileExtension?.getExtraFieldValues;
222
+ const hasAdminFields = !!this.userFormExtension?.getExtraFieldValues;
223
+ if (!hasProfileFields && !hasAdminFields) return;
224
+ this.isLoadingExtraFields.set(true);
225
+ const profileFields$ = hasProfileFields ? this.profileExtension.getExtraFieldValues(user.id).pipe(catchError(() => {
226
+ const defaults = {};
227
+ for (const field of this.editableFields()) if (field.key) defaults[field.key] = void 0;
228
+ return of(defaults);
229
+ })) : of({});
230
+ const adminFields$ = hasAdminFields ? this.userFormExtension.getExtraFieldValues(user.id).pipe(catchError(() => {
231
+ const defaults = {};
232
+ for (const field of this.viewOnlyFields()) if (field.key) defaults[field.key] = void 0;
233
+ return of(defaults);
234
+ })) : of({});
235
+ forkJoin([profileFields$, adminFields$]).pipe(takeUntilDestroyed(this.destroyRef), finalize(() => this.isLoadingExtraFields.set(false))).subscribe(([profileValues, adminValues]) => {
236
+ this.extraFieldsModel.set(profileValues);
237
+ this.originalExtraFieldsModel.set(profileValues);
238
+ this.adminFieldsModel.set(adminValues);
239
+ });
240
+ }
241
+ onProfilePictureUploaded(file) {
242
+ const user = this.authState.user();
243
+ if (!user || !file.id) return;
244
+ const fileManagerId = file.id;
245
+ this.userService.updateProfile({
246
+ id: user.id,
247
+ profilePictureId: fileManagerId
248
+ }).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
249
+ next: (response) => {
250
+ if (response.success && response.data) {
251
+ this.authState.setUser({
252
+ ...user,
253
+ profilePictureId: fileManagerId
254
+ });
255
+ this.fileUrlService.fetchSingleFileUrl(fileManagerId, true).pipe(takeUntilDestroyed(this.destroyRef)).subscribe((fileInfo) => this._profilePictureUrl.set(fileInfo?.url ?? null));
256
+ this.messageService.add({
257
+ severity: "success",
258
+ summary: this.translate("shared.success"),
259
+ detail: response.messageKey ? this.translate(response.messageKey, response.messageVariables) : response.message
260
+ });
261
+ }
262
+ },
263
+ error: () => {}
264
+ });
265
+ }
266
+ onSubmit() {
267
+ if (!this.isFormValid()) return;
268
+ const extraFieldsError = this.extraFieldsValidationError();
269
+ if (extraFieldsError) {
270
+ this.messageService.add({
271
+ severity: "error",
272
+ summary: this.translate("shared.validation.error"),
273
+ detail: extraFieldsError
274
+ });
275
+ return;
276
+ }
277
+ const user = this.authState.user();
278
+ if (!user) return;
279
+ this.isLoading.set(true);
280
+ const model = this.formModel();
281
+ const updateData = {
282
+ id: user.id,
283
+ name: model.name,
284
+ phone: model.phone || void 0
285
+ };
286
+ const profileUpdate$ = this.userService.updateProfile(updateData);
287
+ const extraFieldsUpdate$ = this.isExtraFieldsDirty() && this.profileExtension?.saveExtraFieldValues ? this.profileExtension.saveExtraFieldValues(user.id, this.extraFieldsModel()) : of(void 0);
288
+ forkJoin([profileUpdate$, extraFieldsUpdate$]).pipe(takeUntilDestroyed(this.destroyRef), finalize(() => this.isLoading.set(false))).subscribe({
289
+ next: ([response]) => {
290
+ if (response.success && response.data) {
291
+ this.authState.setUser({
292
+ ...user,
293
+ name: response.data.name,
294
+ phone: response.data.phone ?? null
295
+ });
296
+ this.originalFormModel.set({
297
+ name: response.data.name,
298
+ phone: response.data.phone ?? ""
299
+ });
300
+ this.originalExtraFieldsModel.set({ ...this.extraFieldsModel() });
301
+ this.messageService.add({
302
+ severity: "success",
303
+ summary: this.translate("shared.success"),
304
+ detail: response.messageKey ? this.translate(response.messageKey, response.messageVariables) : response.message
305
+ });
306
+ }
307
+ },
308
+ error: () => {}
309
+ });
310
+ }
311
+ updatePasswordField(field, value) {
312
+ this.passwordModel.update((model) => ({
313
+ ...model,
314
+ [field]: value
315
+ }));
316
+ }
317
+ onChangePassword() {
318
+ if (!this.isPasswordFormValid()) return;
319
+ this.isPasswordLoading.set(true);
320
+ const model = this.passwordModel();
321
+ this.authApiService.changePassword({
322
+ currentPassword: model.currentPassword,
323
+ newPassword: model.newPassword
324
+ }).pipe(takeUntilDestroyed(this.destroyRef), finalize(() => this.isPasswordLoading.set(false))).subscribe({
325
+ next: (response) => {
326
+ this.messageService.add({
327
+ severity: "success",
328
+ summary: this.translate("shared.success"),
329
+ detail: response?.messageKey ? this.translate(response.messageKey, response.messageVariables) : response?.message
330
+ });
331
+ this.passwordModel.set({
332
+ currentPassword: "",
333
+ newPassword: "",
334
+ confirmPassword: ""
335
+ });
336
+ },
337
+ error: () => {}
338
+ });
339
+ }
340
+ static ɵfac = i0.ɵɵngDeclareFactory({
341
+ minVersion: "12.0.0",
342
+ version: "22.2.1",
343
+ ngImport: i0,
344
+ type: ProfilePageComponent,
345
+ deps: [],
346
+ target: i0.ɵɵFactoryTarget.Component
347
+ });
348
+ static ɵcmp = i0.ɵɵngDeclareComponent({
349
+ minVersion: "17.0.0",
350
+ version: "22.2.1",
351
+ type: ProfilePageComponent,
352
+ isStandalone: true,
353
+ selector: "lib-profile-page",
354
+ ngImport: i0,
355
+ template: `
356
+ <div class="mb-4 md:mb-6">
357
+ <h1 class="text-2xl font-bold text-color mb-2">
358
+ {{ 'profile.title' | translate }}
359
+ </h1>
360
+ <p class="text-muted-color">{{ 'profile.subtitle' | translate }}</p>
361
+ </div>
362
+
363
+ <div class="card mb-2 md:mb-4">
364
+ <div class="flex flex-col md:flex-row gap-4 md:gap-6">
365
+ <div
366
+ class="flex flex-col items-center gap-4 md:w-72 lg:w-80 md:border-r md:border-surface md:pr-6"
367
+ >
368
+ @if (profilePictureUrl()) {
369
+ <f-avatar
370
+ [image]="profilePictureUrl()!"
371
+ size="xlarge"
372
+ shape="circle"
373
+ styleClass="w-28 h-28"
374
+ />
375
+ } @else {
376
+ <f-avatar
377
+ [label]="userInitials()"
378
+ size="xlarge"
379
+ shape="circle"
380
+ styleClass="w-28 h-28 text-3xl bg-primary text-primary-contrast"
381
+ />
382
+ }
383
+ <div class="text-center">
384
+ <div class="font-semibold text-color">{{ userName() }}</div>
385
+ <div class="text-muted-color text-sm">{{ userEmail() }}</div>
386
+ </div>
387
+ @if (showUploadSection()) {
388
+ <lib-file-uploader
389
+ [acceptTypes]="['image/*']"
390
+ [multiple]="false"
391
+ [maxSizeMb]="5"
392
+ [showPreview]="false"
393
+ [uploadOptions]="profileUploadOptions"
394
+ (fileUploaded)="onProfilePictureUploaded($event)"
395
+ class="w-full"
396
+ />
397
+ } @else {
398
+ <div class="w-full p-3 bg-emphasis rounded-lg text-center">
399
+ <ui-icon
400
+ name="info-circle"
401
+ class="text-muted-color mr-2"
402
+ ></ui-icon>
403
+ <span class="text-sm text-muted-color">{{
404
+ 'profile.storage.not.enabled' | translate
405
+ }}</span>
406
+ </div>
407
+ }
408
+ </div>
409
+
410
+ <div class="flex-1 min-w-0">
411
+ <form (ngSubmit)="onSubmit()" class="flex flex-col gap-4">
412
+ <div class="flex flex-col gap-2">
413
+ <label class="font-medium text-color">{{
414
+ 'auth.email.label' | translate
415
+ }}</label>
416
+ <input
417
+ fInputText
418
+ [value]="userEmail()"
419
+ disabled
420
+ class="bg-(--f-form-field-filled-background)"
421
+ [placeholder]="'auth.email.placeholder' | translate"
422
+ />
423
+ </div>
424
+
425
+ <div class="flex flex-col gap-2">
426
+ <label for="name" class="font-medium text-color"
427
+ >{{ 'profile.name.label' | translate }} *</label
428
+ >
429
+ <input
430
+ fInputText
431
+ id="name"
432
+ [formField]="formTree.name"
433
+ [placeholder]="'profile.name.placeholder' | translate"
434
+ />
435
+ </div>
436
+
437
+ <div class="flex flex-col gap-2">
438
+ <label for="phone" class="font-medium text-color">{{
439
+ 'profile.phone.label' | translate
440
+ }}</label>
441
+ <input
442
+ fInputText
443
+ id="phone"
444
+ [formField]="formTree.phone"
445
+ [placeholder]="'profile.phone.placeholder' | translate"
446
+ />
447
+ </div>
448
+
449
+ @if (editableFields().length > 0) {
450
+ <div class="border-t border-surface pt-4 mt-2">
451
+ <div class="text-sm font-medium text-muted-color mb-4">
452
+ <ui-icon name="pencil" class="mr-2"></ui-icon
453
+ >{{ 'profile.additional.info' | translate }}
454
+ </div>
455
+ @if (isLoadingExtraFields()) {
456
+ <f-skeleton height="2rem" class="mb-2" />
457
+ <f-skeleton height="2rem" />
458
+ } @else {
459
+ <div class="flex flex-col gap-4">
460
+ @for (field of editableFields(); track field.key) {
461
+ <div class="flex flex-col gap-2">
462
+ <label [for]="field.key" class="font-medium text-color">
463
+ {{ field.labelKey | translate: null : field.label
464
+ }}{{ field.required ? ' *' : '' }}
465
+ </label>
466
+ @if (field.component) {
467
+ <ng-container
468
+ [ngComponentOutlet]="field.component"
469
+ [ngComponentOutletInjector]="getFieldInjector(field)"
470
+ />
471
+ } @else if (field.template) {
472
+ <ng-container
473
+ [ngTemplateOutlet]="field.template"
474
+ [ngTemplateOutletContext]="
475
+ getFieldTemplateContext(field)
476
+ "
477
+ />
478
+ } @else {
479
+ <ng-container
480
+ [ngTemplateOutlet]="fieldTemplate"
481
+ [ngTemplateOutletContext]="{ field: field }"
482
+ />
483
+ }
484
+ </div>
485
+ }
486
+ </div>
487
+ @if (extraFieldsValidationError()) {
488
+ <small class="text-danger block mt-2">{{
489
+ extraFieldsValidationError()
490
+ }}</small>
491
+ }
492
+ }
493
+ </div>
494
+ }
495
+
496
+ @if (viewOnlyFields().length > 0) {
497
+ <div class="border-t border-surface pt-4 mt-2">
498
+ <div class="flex items-center justify-between mb-4">
499
+ <div class="text-sm font-medium text-muted-color">
500
+ <ui-icon name="eye" class="mr-2"></ui-icon
501
+ >{{ 'profile.employment.info' | translate }}
502
+ </div>
503
+ <f-tag
504
+ [value]="'profile.managed.by.admin' | translate"
505
+ severity="secondary"
506
+ />
507
+ </div>
508
+ @if (isLoadingExtraFields()) {
509
+ <f-skeleton height="2rem" class="mb-2" />
510
+ <f-skeleton height="2rem" />
511
+ } @else {
512
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
513
+ @for (field of viewOnlyFields(); track field.key) {
514
+ <div class="flex flex-col gap-1">
515
+ <label class="text-sm text-muted-color">{{
516
+ field.labelKey | translate: null : field.label
517
+ }}</label>
518
+ <span class="text-color font-medium">
519
+ @if (field.type === 'select') {
520
+ {{
521
+ getSelectLabel(
522
+ field,
523
+ getAdminFieldValue(field.key)
524
+ )
525
+ }}
526
+ } @else {
527
+ {{ getAdminFieldValue(field.key) || '-' }}
528
+ }
529
+ </span>
530
+ </div>
531
+ }
532
+ </div>
533
+ }
534
+ </div>
535
+ }
536
+
537
+ <ng-template #fieldTemplate let-field="field">
538
+ @switch (field.type) {
539
+ @case ('text') {
540
+ <input
541
+ fInputText
542
+ [id]="field.key"
543
+ [placeholder]="
544
+ field.placeholderKey
545
+ | translate: null : field.placeholder ?? ''
546
+ "
547
+ [value]="getExtraFieldValue(field.key) ?? ''"
548
+ [disabled]="field.editable === false"
549
+ (input)="
550
+ updateExtraField(field.key, $any($event.target).value)
551
+ "
552
+ class="w-full"
553
+ />
554
+ }
555
+ @case ('number') {
556
+ <input
557
+ fInputText
558
+ type="number"
559
+ [id]="field.key"
560
+ [placeholder]="
561
+ field.placeholderKey
562
+ | translate: null : field.placeholder ?? ''
563
+ "
564
+ [value]="getExtraFieldValue(field.key) ?? ''"
565
+ [disabled]="field.editable === false"
566
+ (input)="
567
+ updateExtraField(
568
+ field.key,
569
+ $any($event.target).valueAsNumber
570
+ )
571
+ "
572
+ class="w-full"
573
+ />
574
+ }
575
+ @case ('textarea') {
576
+ <textarea
577
+ fTextarea
578
+ [id]="field.key"
579
+ [placeholder]="
580
+ field.placeholderKey
581
+ | translate: null : field.placeholder ?? ''
582
+ "
583
+ [value]="getExtraFieldValue(field.key) ?? ''"
584
+ [disabled]="field.editable === false"
585
+ (input)="
586
+ updateExtraField(field.key, $any($event.target).value)
587
+ "
588
+ class="w-full"
589
+ rows="3"
590
+ ></textarea>
591
+ }
592
+ @case ('select') {
593
+ <f-select
594
+ [id]="field.key"
595
+ class="w-full"
596
+ [options]="field.options ?? []"
597
+ optionValue="value"
598
+ [disabled]="field.editable === false"
599
+ [ngModel]="getExtraFieldValue(field.key) ?? ''"
600
+ [ngModelOptions]="{ standalone: true }"
601
+ (ngModelChange)="updateExtraField(field.key, $event)"
602
+ [placeholder]="
603
+ field.placeholderKey
604
+ | translate
605
+ : null
606
+ : field.placeholder ?? ('shared.select' | translate)
607
+ "
608
+ >
609
+ <ng-template #item let-opt>{{
610
+ opt.labelKey | translate: null : opt.label
611
+ }}</ng-template>
612
+ <ng-template #selectedItem let-opt>{{
613
+ opt.labelKey | translate: null : opt.label
614
+ }}</ng-template>
615
+ </f-select>
616
+ }
617
+ @case ('checkbox') {
618
+ <f-checkbox
619
+ [inputId]="field.key"
620
+ [checked]="$any(getExtraFieldValue(field.key) ?? false)"
621
+ [disabled]="field.editable === false"
622
+ (onChange)="updateExtraField(field.key, $event.checked)"
623
+ [label]="
624
+ field.placeholderKey
625
+ | translate
626
+ : null
627
+ : field.placeholder ??
628
+ (field.labelKey | translate: null : field.label)
629
+ "
630
+ />
631
+ }
632
+ @case ('date') {
633
+ <input
634
+ fInputText
635
+ type="date"
636
+ [id]="field.key"
637
+ [value]="getExtraFieldValue(field.key) ?? ''"
638
+ [disabled]="field.editable === false"
639
+ (input)="
640
+ updateExtraField(field.key, $any($event.target).value)
641
+ "
642
+ class="w-full"
643
+ />
644
+ }
645
+ }
646
+ </ng-template>
647
+
648
+ <div class="flex justify-end pt-4">
649
+ <button
650
+ fButton
651
+ type="submit"
652
+ [disabled]="!isFormValid() || !isFormDirty() || isLoading()"
653
+ >
654
+ @if (isLoading()) {
655
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
656
+ } @else {
657
+ <ui-icon pButtonIcon name="check"></ui-icon>
658
+ }
659
+ <span pButtonLabel>{{
660
+ 'shared.save.changes' | translate
661
+ }}</span>
662
+ </button>
663
+ </div>
664
+ </form>
665
+ </div>
666
+ </div>
667
+ </div>
668
+
669
+ <div class="card mb-2 md:mb-4">
670
+ <div class="flex items-center gap-2 mb-4">
671
+ <ui-icon name="lock" class="text-primary"></ui-icon>
672
+ <span class="font-semibold text-color">{{
673
+ 'profile.security' | translate
674
+ }}</span>
675
+ </div>
676
+ <form (ngSubmit)="onChangePassword()" class="flex flex-col gap-4">
677
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
678
+ <div class="flex flex-col gap-2">
679
+ <label for="currentPassword" class="font-medium text-color"
680
+ >{{ 'profile.current.password' | translate }} *</label
681
+ >
682
+ <f-password
683
+ [inputId]="'currentPassword'"
684
+ [formField]="passwordForm.currentPassword"
685
+ [placeholder]="'profile.current.password.placeholder' | translate"
686
+ class="w-full"
687
+ autocomplete="current-password"
688
+ />
689
+ </div>
690
+
691
+ <div class="flex flex-col gap-2">
692
+ <label for="newPassword" class="font-medium text-color"
693
+ >{{ 'profile.new.password' | translate }} *</label
694
+ >
695
+ <f-password
696
+ [inputId]="'newPassword'"
697
+ [formField]="passwordForm.newPassword"
698
+ [placeholder]="'shared.min.characters' | translate: { count: passwordPolicy.minLength }"
699
+ class="w-full"
700
+ autocomplete="new-password"
701
+ />
702
+ @if (newPasswordError()) {
703
+ <small class="text-danger">{{ newPasswordError() }}</small>
704
+ }
705
+ </div>
706
+
707
+ <div class="flex flex-col gap-2">
708
+ <label for="confirmPassword" class="font-medium text-color"
709
+ >{{ 'profile.confirm.password' | translate }} *</label
710
+ >
711
+ <f-password
712
+ [inputId]="'confirmPassword'"
713
+ [formField]="passwordForm.confirmPassword"
714
+ [placeholder]="'profile.confirm.password.placeholder' | translate"
715
+ class="w-full"
716
+ autocomplete="new-password"
717
+ />
718
+ @if (!passwordsMatch()) {
719
+ <small class="text-danger">{{
720
+ 'auth.register.passwords.do.not.match' | translate
721
+ }}</small>
722
+ }
723
+ </div>
724
+ </div>
725
+
726
+ <div class="flex justify-end">
727
+ <button
728
+ fButton
729
+ type="submit"
730
+ [disabled]="!isPasswordFormValid() || isPasswordLoading()"
731
+ >
732
+ @if (isPasswordLoading()) {
733
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
734
+ } @else {
735
+ <ui-icon pButtonIcon name="key"></ui-icon>
736
+ }
737
+ <span pButtonLabel>{{
738
+ 'profile.change.password' | translate
739
+ }}</span>
740
+ </button>
741
+ </div>
742
+ </form>
743
+ </div>
744
+
745
+ @for (section of extraSections(); track section.key) {
746
+ @if (section.editPermission) {
747
+ <div *hasPermission="section.editPermission" class="card mb-2 md:mb-4">
748
+ <div class="flex items-center gap-2 mb-4">
749
+ @if (section.icon) {
750
+ <ui-icon [name]="section.icon" class="text-primary"></ui-icon>
751
+ }
752
+ <span class="font-semibold text-color">{{
753
+ section.titleKey | translate: null : section.title
754
+ }}</span>
755
+ </div>
756
+ <div class="section-content">
757
+ @if (section.component) {
758
+ <ng-container [ngComponentOutlet]="section.component" />
759
+ }
760
+ </div>
761
+ </div>
762
+ } @else {
763
+ <div class="card mb-2 md:mb-4">
764
+ <div class="flex items-center gap-2 mb-4">
765
+ @if (section.icon) {
766
+ <ui-icon [name]="section.icon" class="text-primary"></ui-icon>
767
+ }
768
+ <span class="font-semibold text-color">{{
769
+ section.titleKey | translate: null : section.title
770
+ }}</span>
771
+ </div>
772
+ <div class="section-content">
773
+ @if (section.component) {
774
+ <ng-container [ngComponentOutlet]="section.component" />
775
+ }
776
+ </div>
777
+ </div>
778
+ }
779
+ }
780
+
781
+ @if (showBottomRow()) {
782
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-2 md:gap-4 flex-1">
783
+ @if (showCompanySection()) {
784
+ <div class="card h-48 md:h-56 flex flex-col">
785
+ <div class="flex items-center gap-2 mb-4">
786
+ <ui-icon name="building" class="text-primary"></ui-icon>
787
+ <span class="font-semibold text-color">{{
788
+ 'profile.company.branch' | translate
789
+ }}</span>
790
+ </div>
791
+ <div class="flex flex-col gap-4 flex-1 overflow-auto">
792
+ <div class="flex items-center gap-3">
793
+ <div
794
+ class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0"
795
+ >
796
+ <ui-icon name="briefcase" class="text-primary"></ui-icon>
797
+ </div>
798
+ <div>
799
+ <div class="text-sm text-muted-color">
800
+ {{ 'shared.company' | translate }}
801
+ </div>
802
+ <div class="font-medium text-color">
803
+ {{ companyName() || ('shared.not.assigned' | translate) }}
804
+ </div>
805
+ </div>
806
+ </div>
807
+ <div class="flex items-center gap-3">
808
+ <div
809
+ class="w-10 h-10 rounded-lg bg-success/10 flex items-center justify-center flex-shrink-0"
810
+ >
811
+ <ui-icon name="map-marker" class="text-success"></ui-icon>
812
+ </div>
813
+ <div>
814
+ <div class="text-sm text-muted-color">
815
+ {{ 'profile.branch.label' | translate }}
816
+ </div>
817
+ <div class="font-medium text-color">
818
+ {{ branchName() || ('shared.not.assigned' | translate) }}
819
+ </div>
820
+ </div>
821
+ </div>
822
+ </div>
823
+ </div>
824
+ }
825
+
826
+ @if (showPermissionsSection()) {
827
+ <div class="card h-48 md:h-56 flex flex-col">
828
+ <div class="flex items-center justify-between mb-4">
829
+ <div class="flex items-center gap-2">
830
+ <ui-icon name="shield" class="text-primary"></ui-icon>
831
+ <span class="font-semibold text-color">{{
832
+ 'profile.permissions.label' | translate
833
+ }}</span>
834
+ </div>
835
+ <f-tag
836
+ [value]="'shared.read.only' | translate"
837
+ severity="secondary"
838
+ />
839
+ </div>
840
+ <div class="flex flex-col gap-4 flex-1 overflow-auto">
841
+ @if (showRoles()) {
842
+ <div>
843
+ <div
844
+ class="text-sm text-muted-color mb-2 flex items-center gap-2"
845
+ >
846
+ <ui-icon name="users"></ui-icon>
847
+ {{ 'profile.roles.label' | translate }}
848
+ </div>
849
+ @if (isLoadingPermissions()) {
850
+ <f-skeleton height="1.5rem" class="mb-2" />
851
+ } @else if (userRoles().length === 0) {
852
+ <span class="text-muted-color text-sm">{{
853
+ 'profile.roles.none.assigned' | translate
854
+ }}</span>
855
+ } @else {
856
+ <div class="flex flex-wrap gap-2">
857
+ @for (role of userRoles(); track role.id) {
858
+ <f-tag [value]="role.name" severity="info" />
859
+ }
860
+ </div>
861
+ }
862
+ </div>
863
+ }
864
+
865
+ @if (showActions()) {
866
+ <div>
867
+ <div
868
+ class="text-sm text-muted-color mb-2 flex items-center gap-2"
869
+ >
870
+ <ui-icon name="bolt"></ui-icon>
871
+ {{ 'profile.actions.label' | translate }}
872
+ </div>
873
+ @if (isLoadingPermissions()) {
874
+ <f-skeleton height="1.5rem" class="mb-2" />
875
+ } @else if (userActions().length === 0) {
876
+ <span class="text-muted-color text-sm">{{
877
+ 'profile.actions.none.assigned' | translate
878
+ }}</span>
879
+ } @else {
880
+ <div class="flex flex-wrap gap-2">
881
+ @for (action of userActions(); track action.id) {
882
+ <f-tag [value]="action.name" severity="success" />
883
+ }
884
+ </div>
885
+ }
886
+ </div>
887
+ }
888
+ </div>
889
+ </div>
890
+ }
891
+ </div>
892
+ }
893
+ `,
894
+ isInline: true,
895
+ dependencies: [
896
+ {
897
+ kind: "component",
898
+ type: AvatarComponent,
899
+ selector: "f-avatar",
900
+ inputs: [
901
+ "label",
902
+ "icon",
903
+ "image",
904
+ "shape",
905
+ "size",
906
+ "color",
907
+ "styleClass",
908
+ "ariaLabel",
909
+ "ariaLabelledBy"
910
+ ],
911
+ outputs: ["onImageError"]
912
+ },
913
+ {
914
+ kind: "component",
915
+ type: CheckboxComponent,
916
+ selector: "f-checkbox",
917
+ inputs: [
918
+ "label",
919
+ "binary",
920
+ "disabled",
921
+ "readonly",
922
+ "inputId",
923
+ "invalid",
924
+ "indeterminate",
925
+ "variant",
926
+ "size",
927
+ "touched",
928
+ "styleClass",
929
+ "style",
930
+ "checked"
931
+ ],
932
+ outputs: [
933
+ "onChange",
934
+ "onBlur",
935
+ "touch",
936
+ "checkedChange"
937
+ ]
938
+ },
939
+ {
940
+ kind: "directive",
941
+ type: FButtonDirective,
942
+ selector: "[fButton]",
943
+ inputs: [
944
+ "severity",
945
+ "text",
946
+ "outlined",
947
+ "link",
948
+ "variant",
949
+ "rounded",
950
+ "raised",
951
+ "fluid",
952
+ "disabled",
953
+ "loading",
954
+ "loadingIcon",
955
+ "size",
956
+ "label",
957
+ "icon",
958
+ "iconPos",
959
+ "ariaLabel",
960
+ "plain"
961
+ ]
962
+ },
963
+ {
964
+ kind: "directive",
965
+ type: InputTextDirective,
966
+ selector: "[fInputText]",
967
+ inputs: [
968
+ "invalid",
969
+ "touched",
970
+ "variant",
971
+ "size",
972
+ "fluid"
973
+ ]
974
+ },
975
+ {
976
+ kind: "component",
977
+ type: PasswordComponent,
978
+ selector: "f-password",
979
+ inputs: [
980
+ "toggleMask",
981
+ "feedback",
982
+ "disabled",
983
+ "placeholder",
984
+ "inputId",
985
+ "invalid",
986
+ "touched",
987
+ "inputStyleClass",
988
+ "styleClass",
989
+ "style",
990
+ "ariaDescribedby",
991
+ "autocomplete",
992
+ "variant",
993
+ "size",
994
+ "fluid",
995
+ "appendTo",
996
+ "promptLabel",
997
+ "weakLabel",
998
+ "mediumLabel",
999
+ "strongLabel",
1000
+ "mediumRegex",
1001
+ "strongRegex"
1002
+ ],
1003
+ outputs: ["onBlur", "onFocus"]
1004
+ },
1005
+ {
1006
+ kind: "component",
1007
+ type: SelectComponent,
1008
+ selector: "f-select",
1009
+ inputs: [
1010
+ "options",
1011
+ "optionLabel",
1012
+ "optionValue",
1013
+ "placeholder",
1014
+ "disabled",
1015
+ "filter",
1016
+ "filterBy",
1017
+ "filterMatchMode",
1018
+ "filterLocale",
1019
+ "filterPlaceholder",
1020
+ "resetFilterOnHide",
1021
+ "autofocusFilter",
1022
+ "emptyMessage",
1023
+ "emptyFilterMessage",
1024
+ "showClear",
1025
+ "loading",
1026
+ "loadingIcon",
1027
+ "dropdownIcon",
1028
+ "clearIcon",
1029
+ "inputId",
1030
+ "invalid",
1031
+ "touched",
1032
+ "styleClass",
1033
+ "style",
1034
+ "appendTo",
1035
+ "editable",
1036
+ "readonly",
1037
+ "autofocus",
1038
+ "checkmark",
1039
+ "group",
1040
+ "optionGroupLabel",
1041
+ "optionGroupChildren",
1042
+ "virtualScroll",
1043
+ "virtualScrollItemSize",
1044
+ "lazy",
1045
+ "selectedObject"
1046
+ ],
1047
+ outputs: [
1048
+ "onFilter",
1049
+ "onChange",
1050
+ "onShow",
1051
+ "onHide",
1052
+ "onBlur",
1053
+ "onFocus",
1054
+ "onClear",
1055
+ "onLazyLoad"
1056
+ ]
1057
+ },
1058
+ {
1059
+ kind: "component",
1060
+ type: SkeletonComponent,
1061
+ selector: "f-skeleton",
1062
+ inputs: [
1063
+ "width",
1064
+ "height",
1065
+ "size",
1066
+ "borderRadius",
1067
+ "shape",
1068
+ "animation",
1069
+ "styleClass"
1070
+ ]
1071
+ },
1072
+ {
1073
+ kind: "component",
1074
+ type: TagComponent,
1075
+ selector: "f-tag",
1076
+ inputs: [
1077
+ "value",
1078
+ "severity",
1079
+ "icon",
1080
+ "rounded",
1081
+ "color",
1082
+ "styleClass",
1083
+ "style"
1084
+ ]
1085
+ },
1086
+ {
1087
+ kind: "directive",
1088
+ type: TextareaDirective,
1089
+ selector: "[fTextarea]",
1090
+ inputs: [
1091
+ "invalid",
1092
+ "touched",
1093
+ "variant",
1094
+ "fluid",
1095
+ "autoResize"
1096
+ ],
1097
+ outputs: ["onResize"]
1098
+ },
1099
+ {
1100
+ kind: "component",
1101
+ type: UiIconComponent,
1102
+ selector: "ui-icon",
1103
+ inputs: [
1104
+ "name",
1105
+ "size",
1106
+ "spin",
1107
+ "fill",
1108
+ "severity"
1109
+ ]
1110
+ },
1111
+ {
1112
+ kind: "ngmodule",
1113
+ type: FormsModule
1114
+ },
1115
+ {
1116
+ kind: "directive",
1117
+ type: i1.ɵNgNoValidate,
1118
+ selector: "form:not([ngNoForm]):not([ngNativeValidate])"
1119
+ },
1120
+ {
1121
+ kind: "directive",
1122
+ type: i1.NgControlStatus,
1123
+ selector: "[formControlName],[ngModel],[formControl]"
1124
+ },
1125
+ {
1126
+ kind: "directive",
1127
+ type: i1.NgControlStatusGroup,
1128
+ selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
1129
+ },
1130
+ {
1131
+ kind: "directive",
1132
+ type: i1.NgModel,
1133
+ selector: "[ngModel]:not([formControlName]):not([formControl])",
1134
+ inputs: [
1135
+ "name",
1136
+ "disabled",
1137
+ "ngModel",
1138
+ "ngModelOptions"
1139
+ ],
1140
+ outputs: ["ngModelChange"],
1141
+ exportAs: ["ngModel"]
1142
+ },
1143
+ {
1144
+ kind: "directive",
1145
+ type: i1.NgForm,
1146
+ selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]",
1147
+ inputs: ["ngFormOptions"],
1148
+ outputs: ["ngSubmit"],
1149
+ exportAs: ["ngForm"]
1150
+ },
1151
+ {
1152
+ kind: "directive",
1153
+ type: NgComponentOutlet,
1154
+ selector: "[ngComponentOutlet]",
1155
+ inputs: [
1156
+ "ngComponentOutlet",
1157
+ "ngComponentOutletInputs",
1158
+ "ngComponentOutletInjector",
1159
+ "ngComponentOutletEnvironmentInjector",
1160
+ "ngComponentOutletContent",
1161
+ "ngComponentOutletNgModule"
1162
+ ],
1163
+ exportAs: ["ngComponentOutlet"]
1164
+ },
1165
+ {
1166
+ kind: "directive",
1167
+ type: NgTemplateOutlet,
1168
+ selector: "[ngTemplateOutlet]",
1169
+ inputs: [
1170
+ "ngTemplateOutletContext",
1171
+ "ngTemplateOutlet",
1172
+ "ngTemplateOutletInjector"
1173
+ ]
1174
+ },
1175
+ {
1176
+ kind: "directive",
1177
+ type: FormField,
1178
+ selector: "[formField]",
1179
+ inputs: ["formField"],
1180
+ exportAs: ["formField"]
1181
+ },
1182
+ {
1183
+ kind: "component",
1184
+ type: FileUploaderComponent,
1185
+ selector: "lib-file-uploader",
1186
+ inputs: [
1187
+ "uploadFile",
1188
+ "uploadMultipleFiles",
1189
+ "acceptTypes",
1190
+ "multiple",
1191
+ "maxFiles",
1192
+ "maxSizeMb",
1193
+ "uploadOptions",
1194
+ "disabled",
1195
+ "showPreview",
1196
+ "autoUpload"
1197
+ ],
1198
+ outputs: [
1199
+ "fileUploaded",
1200
+ "filesUploaded",
1201
+ "onError",
1202
+ "fileSelected"
1203
+ ]
1204
+ },
1205
+ {
1206
+ kind: "directive",
1207
+ type: HasPermissionDirective,
1208
+ selector: "[hasPermission]",
1209
+ inputs: ["hasPermission"]
1210
+ },
1211
+ {
1212
+ kind: "pipe",
1213
+ type: TranslatePipe,
1214
+ name: "translate"
1215
+ }
1216
+ ]
1217
+ });
1218
+ };
1219
+ i0.ɵɵngDeclareClassMetadata({
1220
+ minVersion: "12.0.0",
1221
+ version: "22.2.1",
1222
+ ngImport: i0,
1223
+ type: ProfilePageComponent,
1224
+ decorators: [{
1225
+ type: Component,
1226
+ args: [{
1227
+ selector: "lib-profile-page",
1228
+ imports: [
1229
+ AvatarComponent,
1230
+ CheckboxComponent,
1231
+ FButtonDirective,
1232
+ InputTextDirective,
1233
+ PasswordComponent,
1234
+ SelectComponent,
1235
+ SkeletonComponent,
1236
+ TagComponent,
1237
+ TextareaDirective,
1238
+ UiIconComponent,
1239
+ FormsModule,
1240
+ NgComponentOutlet,
1241
+ NgTemplateOutlet,
1242
+ FormField,
1243
+ FileUploaderComponent,
1244
+ HasPermissionDirective,
1245
+ TranslatePipe
1246
+ ],
1247
+ template: `
1248
+ <div class="mb-4 md:mb-6">
1249
+ <h1 class="text-2xl font-bold text-color mb-2">
1250
+ {{ 'profile.title' | translate }}
1251
+ </h1>
1252
+ <p class="text-muted-color">{{ 'profile.subtitle' | translate }}</p>
1253
+ </div>
1254
+
1255
+ <div class="card mb-2 md:mb-4">
1256
+ <div class="flex flex-col md:flex-row gap-4 md:gap-6">
1257
+ <div
1258
+ class="flex flex-col items-center gap-4 md:w-72 lg:w-80 md:border-r md:border-surface md:pr-6"
1259
+ >
1260
+ @if (profilePictureUrl()) {
1261
+ <f-avatar
1262
+ [image]="profilePictureUrl()!"
1263
+ size="xlarge"
1264
+ shape="circle"
1265
+ styleClass="w-28 h-28"
1266
+ />
1267
+ } @else {
1268
+ <f-avatar
1269
+ [label]="userInitials()"
1270
+ size="xlarge"
1271
+ shape="circle"
1272
+ styleClass="w-28 h-28 text-3xl bg-primary text-primary-contrast"
1273
+ />
1274
+ }
1275
+ <div class="text-center">
1276
+ <div class="font-semibold text-color">{{ userName() }}</div>
1277
+ <div class="text-muted-color text-sm">{{ userEmail() }}</div>
1278
+ </div>
1279
+ @if (showUploadSection()) {
1280
+ <lib-file-uploader
1281
+ [acceptTypes]="['image/*']"
1282
+ [multiple]="false"
1283
+ [maxSizeMb]="5"
1284
+ [showPreview]="false"
1285
+ [uploadOptions]="profileUploadOptions"
1286
+ (fileUploaded)="onProfilePictureUploaded($event)"
1287
+ class="w-full"
1288
+ />
1289
+ } @else {
1290
+ <div class="w-full p-3 bg-emphasis rounded-lg text-center">
1291
+ <ui-icon
1292
+ name="info-circle"
1293
+ class="text-muted-color mr-2"
1294
+ ></ui-icon>
1295
+ <span class="text-sm text-muted-color">{{
1296
+ 'profile.storage.not.enabled' | translate
1297
+ }}</span>
1298
+ </div>
1299
+ }
1300
+ </div>
1301
+
1302
+ <div class="flex-1 min-w-0">
1303
+ <form (ngSubmit)="onSubmit()" class="flex flex-col gap-4">
1304
+ <div class="flex flex-col gap-2">
1305
+ <label class="font-medium text-color">{{
1306
+ 'auth.email.label' | translate
1307
+ }}</label>
1308
+ <input
1309
+ fInputText
1310
+ [value]="userEmail()"
1311
+ disabled
1312
+ class="bg-(--f-form-field-filled-background)"
1313
+ [placeholder]="'auth.email.placeholder' | translate"
1314
+ />
1315
+ </div>
1316
+
1317
+ <div class="flex flex-col gap-2">
1318
+ <label for="name" class="font-medium text-color"
1319
+ >{{ 'profile.name.label' | translate }} *</label
1320
+ >
1321
+ <input
1322
+ fInputText
1323
+ id="name"
1324
+ [formField]="formTree.name"
1325
+ [placeholder]="'profile.name.placeholder' | translate"
1326
+ />
1327
+ </div>
1328
+
1329
+ <div class="flex flex-col gap-2">
1330
+ <label for="phone" class="font-medium text-color">{{
1331
+ 'profile.phone.label' | translate
1332
+ }}</label>
1333
+ <input
1334
+ fInputText
1335
+ id="phone"
1336
+ [formField]="formTree.phone"
1337
+ [placeholder]="'profile.phone.placeholder' | translate"
1338
+ />
1339
+ </div>
1340
+
1341
+ @if (editableFields().length > 0) {
1342
+ <div class="border-t border-surface pt-4 mt-2">
1343
+ <div class="text-sm font-medium text-muted-color mb-4">
1344
+ <ui-icon name="pencil" class="mr-2"></ui-icon
1345
+ >{{ 'profile.additional.info' | translate }}
1346
+ </div>
1347
+ @if (isLoadingExtraFields()) {
1348
+ <f-skeleton height="2rem" class="mb-2" />
1349
+ <f-skeleton height="2rem" />
1350
+ } @else {
1351
+ <div class="flex flex-col gap-4">
1352
+ @for (field of editableFields(); track field.key) {
1353
+ <div class="flex flex-col gap-2">
1354
+ <label [for]="field.key" class="font-medium text-color">
1355
+ {{ field.labelKey | translate: null : field.label
1356
+ }}{{ field.required ? ' *' : '' }}
1357
+ </label>
1358
+ @if (field.component) {
1359
+ <ng-container
1360
+ [ngComponentOutlet]="field.component"
1361
+ [ngComponentOutletInjector]="getFieldInjector(field)"
1362
+ />
1363
+ } @else if (field.template) {
1364
+ <ng-container
1365
+ [ngTemplateOutlet]="field.template"
1366
+ [ngTemplateOutletContext]="
1367
+ getFieldTemplateContext(field)
1368
+ "
1369
+ />
1370
+ } @else {
1371
+ <ng-container
1372
+ [ngTemplateOutlet]="fieldTemplate"
1373
+ [ngTemplateOutletContext]="{ field: field }"
1374
+ />
1375
+ }
1376
+ </div>
1377
+ }
1378
+ </div>
1379
+ @if (extraFieldsValidationError()) {
1380
+ <small class="text-danger block mt-2">{{
1381
+ extraFieldsValidationError()
1382
+ }}</small>
1383
+ }
1384
+ }
1385
+ </div>
1386
+ }
1387
+
1388
+ @if (viewOnlyFields().length > 0) {
1389
+ <div class="border-t border-surface pt-4 mt-2">
1390
+ <div class="flex items-center justify-between mb-4">
1391
+ <div class="text-sm font-medium text-muted-color">
1392
+ <ui-icon name="eye" class="mr-2"></ui-icon
1393
+ >{{ 'profile.employment.info' | translate }}
1394
+ </div>
1395
+ <f-tag
1396
+ [value]="'profile.managed.by.admin' | translate"
1397
+ severity="secondary"
1398
+ />
1399
+ </div>
1400
+ @if (isLoadingExtraFields()) {
1401
+ <f-skeleton height="2rem" class="mb-2" />
1402
+ <f-skeleton height="2rem" />
1403
+ } @else {
1404
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
1405
+ @for (field of viewOnlyFields(); track field.key) {
1406
+ <div class="flex flex-col gap-1">
1407
+ <label class="text-sm text-muted-color">{{
1408
+ field.labelKey | translate: null : field.label
1409
+ }}</label>
1410
+ <span class="text-color font-medium">
1411
+ @if (field.type === 'select') {
1412
+ {{
1413
+ getSelectLabel(
1414
+ field,
1415
+ getAdminFieldValue(field.key)
1416
+ )
1417
+ }}
1418
+ } @else {
1419
+ {{ getAdminFieldValue(field.key) || '-' }}
1420
+ }
1421
+ </span>
1422
+ </div>
1423
+ }
1424
+ </div>
1425
+ }
1426
+ </div>
1427
+ }
1428
+
1429
+ <ng-template #fieldTemplate let-field="field">
1430
+ @switch (field.type) {
1431
+ @case ('text') {
1432
+ <input
1433
+ fInputText
1434
+ [id]="field.key"
1435
+ [placeholder]="
1436
+ field.placeholderKey
1437
+ | translate: null : field.placeholder ?? ''
1438
+ "
1439
+ [value]="getExtraFieldValue(field.key) ?? ''"
1440
+ [disabled]="field.editable === false"
1441
+ (input)="
1442
+ updateExtraField(field.key, $any($event.target).value)
1443
+ "
1444
+ class="w-full"
1445
+ />
1446
+ }
1447
+ @case ('number') {
1448
+ <input
1449
+ fInputText
1450
+ type="number"
1451
+ [id]="field.key"
1452
+ [placeholder]="
1453
+ field.placeholderKey
1454
+ | translate: null : field.placeholder ?? ''
1455
+ "
1456
+ [value]="getExtraFieldValue(field.key) ?? ''"
1457
+ [disabled]="field.editable === false"
1458
+ (input)="
1459
+ updateExtraField(
1460
+ field.key,
1461
+ $any($event.target).valueAsNumber
1462
+ )
1463
+ "
1464
+ class="w-full"
1465
+ />
1466
+ }
1467
+ @case ('textarea') {
1468
+ <textarea
1469
+ fTextarea
1470
+ [id]="field.key"
1471
+ [placeholder]="
1472
+ field.placeholderKey
1473
+ | translate: null : field.placeholder ?? ''
1474
+ "
1475
+ [value]="getExtraFieldValue(field.key) ?? ''"
1476
+ [disabled]="field.editable === false"
1477
+ (input)="
1478
+ updateExtraField(field.key, $any($event.target).value)
1479
+ "
1480
+ class="w-full"
1481
+ rows="3"
1482
+ ></textarea>
1483
+ }
1484
+ @case ('select') {
1485
+ <f-select
1486
+ [id]="field.key"
1487
+ class="w-full"
1488
+ [options]="field.options ?? []"
1489
+ optionValue="value"
1490
+ [disabled]="field.editable === false"
1491
+ [ngModel]="getExtraFieldValue(field.key) ?? ''"
1492
+ [ngModelOptions]="{ standalone: true }"
1493
+ (ngModelChange)="updateExtraField(field.key, $event)"
1494
+ [placeholder]="
1495
+ field.placeholderKey
1496
+ | translate
1497
+ : null
1498
+ : field.placeholder ?? ('shared.select' | translate)
1499
+ "
1500
+ >
1501
+ <ng-template #item let-opt>{{
1502
+ opt.labelKey | translate: null : opt.label
1503
+ }}</ng-template>
1504
+ <ng-template #selectedItem let-opt>{{
1505
+ opt.labelKey | translate: null : opt.label
1506
+ }}</ng-template>
1507
+ </f-select>
1508
+ }
1509
+ @case ('checkbox') {
1510
+ <f-checkbox
1511
+ [inputId]="field.key"
1512
+ [checked]="$any(getExtraFieldValue(field.key) ?? false)"
1513
+ [disabled]="field.editable === false"
1514
+ (onChange)="updateExtraField(field.key, $event.checked)"
1515
+ [label]="
1516
+ field.placeholderKey
1517
+ | translate
1518
+ : null
1519
+ : field.placeholder ??
1520
+ (field.labelKey | translate: null : field.label)
1521
+ "
1522
+ />
1523
+ }
1524
+ @case ('date') {
1525
+ <input
1526
+ fInputText
1527
+ type="date"
1528
+ [id]="field.key"
1529
+ [value]="getExtraFieldValue(field.key) ?? ''"
1530
+ [disabled]="field.editable === false"
1531
+ (input)="
1532
+ updateExtraField(field.key, $any($event.target).value)
1533
+ "
1534
+ class="w-full"
1535
+ />
1536
+ }
1537
+ }
1538
+ </ng-template>
1539
+
1540
+ <div class="flex justify-end pt-4">
1541
+ <button
1542
+ fButton
1543
+ type="submit"
1544
+ [disabled]="!isFormValid() || !isFormDirty() || isLoading()"
1545
+ >
1546
+ @if (isLoading()) {
1547
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
1548
+ } @else {
1549
+ <ui-icon pButtonIcon name="check"></ui-icon>
1550
+ }
1551
+ <span pButtonLabel>{{
1552
+ 'shared.save.changes' | translate
1553
+ }}</span>
1554
+ </button>
1555
+ </div>
1556
+ </form>
1557
+ </div>
1558
+ </div>
1559
+ </div>
1560
+
1561
+ <div class="card mb-2 md:mb-4">
1562
+ <div class="flex items-center gap-2 mb-4">
1563
+ <ui-icon name="lock" class="text-primary"></ui-icon>
1564
+ <span class="font-semibold text-color">{{
1565
+ 'profile.security' | translate
1566
+ }}</span>
1567
+ </div>
1568
+ <form (ngSubmit)="onChangePassword()" class="flex flex-col gap-4">
1569
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
1570
+ <div class="flex flex-col gap-2">
1571
+ <label for="currentPassword" class="font-medium text-color"
1572
+ >{{ 'profile.current.password' | translate }} *</label
1573
+ >
1574
+ <f-password
1575
+ [inputId]="'currentPassword'"
1576
+ [formField]="passwordForm.currentPassword"
1577
+ [placeholder]="'profile.current.password.placeholder' | translate"
1578
+ class="w-full"
1579
+ autocomplete="current-password"
1580
+ />
1581
+ </div>
1582
+
1583
+ <div class="flex flex-col gap-2">
1584
+ <label for="newPassword" class="font-medium text-color"
1585
+ >{{ 'profile.new.password' | translate }} *</label
1586
+ >
1587
+ <f-password
1588
+ [inputId]="'newPassword'"
1589
+ [formField]="passwordForm.newPassword"
1590
+ [placeholder]="'shared.min.characters' | translate: { count: passwordPolicy.minLength }"
1591
+ class="w-full"
1592
+ autocomplete="new-password"
1593
+ />
1594
+ @if (newPasswordError()) {
1595
+ <small class="text-danger">{{ newPasswordError() }}</small>
1596
+ }
1597
+ </div>
1598
+
1599
+ <div class="flex flex-col gap-2">
1600
+ <label for="confirmPassword" class="font-medium text-color"
1601
+ >{{ 'profile.confirm.password' | translate }} *</label
1602
+ >
1603
+ <f-password
1604
+ [inputId]="'confirmPassword'"
1605
+ [formField]="passwordForm.confirmPassword"
1606
+ [placeholder]="'profile.confirm.password.placeholder' | translate"
1607
+ class="w-full"
1608
+ autocomplete="new-password"
1609
+ />
1610
+ @if (!passwordsMatch()) {
1611
+ <small class="text-danger">{{
1612
+ 'auth.register.passwords.do.not.match' | translate
1613
+ }}</small>
1614
+ }
1615
+ </div>
1616
+ </div>
1617
+
1618
+ <div class="flex justify-end">
1619
+ <button
1620
+ fButton
1621
+ type="submit"
1622
+ [disabled]="!isPasswordFormValid() || isPasswordLoading()"
1623
+ >
1624
+ @if (isPasswordLoading()) {
1625
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
1626
+ } @else {
1627
+ <ui-icon pButtonIcon name="key"></ui-icon>
1628
+ }
1629
+ <span pButtonLabel>{{
1630
+ 'profile.change.password' | translate
1631
+ }}</span>
1632
+ </button>
1633
+ </div>
1634
+ </form>
1635
+ </div>
1636
+
1637
+ @for (section of extraSections(); track section.key) {
1638
+ @if (section.editPermission) {
1639
+ <div *hasPermission="section.editPermission" class="card mb-2 md:mb-4">
1640
+ <div class="flex items-center gap-2 mb-4">
1641
+ @if (section.icon) {
1642
+ <ui-icon [name]="section.icon" class="text-primary"></ui-icon>
1643
+ }
1644
+ <span class="font-semibold text-color">{{
1645
+ section.titleKey | translate: null : section.title
1646
+ }}</span>
1647
+ </div>
1648
+ <div class="section-content">
1649
+ @if (section.component) {
1650
+ <ng-container [ngComponentOutlet]="section.component" />
1651
+ }
1652
+ </div>
1653
+ </div>
1654
+ } @else {
1655
+ <div class="card mb-2 md:mb-4">
1656
+ <div class="flex items-center gap-2 mb-4">
1657
+ @if (section.icon) {
1658
+ <ui-icon [name]="section.icon" class="text-primary"></ui-icon>
1659
+ }
1660
+ <span class="font-semibold text-color">{{
1661
+ section.titleKey | translate: null : section.title
1662
+ }}</span>
1663
+ </div>
1664
+ <div class="section-content">
1665
+ @if (section.component) {
1666
+ <ng-container [ngComponentOutlet]="section.component" />
1667
+ }
1668
+ </div>
1669
+ </div>
1670
+ }
1671
+ }
1672
+
1673
+ @if (showBottomRow()) {
1674
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-2 md:gap-4 flex-1">
1675
+ @if (showCompanySection()) {
1676
+ <div class="card h-48 md:h-56 flex flex-col">
1677
+ <div class="flex items-center gap-2 mb-4">
1678
+ <ui-icon name="building" class="text-primary"></ui-icon>
1679
+ <span class="font-semibold text-color">{{
1680
+ 'profile.company.branch' | translate
1681
+ }}</span>
1682
+ </div>
1683
+ <div class="flex flex-col gap-4 flex-1 overflow-auto">
1684
+ <div class="flex items-center gap-3">
1685
+ <div
1686
+ class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0"
1687
+ >
1688
+ <ui-icon name="briefcase" class="text-primary"></ui-icon>
1689
+ </div>
1690
+ <div>
1691
+ <div class="text-sm text-muted-color">
1692
+ {{ 'shared.company' | translate }}
1693
+ </div>
1694
+ <div class="font-medium text-color">
1695
+ {{ companyName() || ('shared.not.assigned' | translate) }}
1696
+ </div>
1697
+ </div>
1698
+ </div>
1699
+ <div class="flex items-center gap-3">
1700
+ <div
1701
+ class="w-10 h-10 rounded-lg bg-success/10 flex items-center justify-center flex-shrink-0"
1702
+ >
1703
+ <ui-icon name="map-marker" class="text-success"></ui-icon>
1704
+ </div>
1705
+ <div>
1706
+ <div class="text-sm text-muted-color">
1707
+ {{ 'profile.branch.label' | translate }}
1708
+ </div>
1709
+ <div class="font-medium text-color">
1710
+ {{ branchName() || ('shared.not.assigned' | translate) }}
1711
+ </div>
1712
+ </div>
1713
+ </div>
1714
+ </div>
1715
+ </div>
1716
+ }
1717
+
1718
+ @if (showPermissionsSection()) {
1719
+ <div class="card h-48 md:h-56 flex flex-col">
1720
+ <div class="flex items-center justify-between mb-4">
1721
+ <div class="flex items-center gap-2">
1722
+ <ui-icon name="shield" class="text-primary"></ui-icon>
1723
+ <span class="font-semibold text-color">{{
1724
+ 'profile.permissions.label' | translate
1725
+ }}</span>
1726
+ </div>
1727
+ <f-tag
1728
+ [value]="'shared.read.only' | translate"
1729
+ severity="secondary"
1730
+ />
1731
+ </div>
1732
+ <div class="flex flex-col gap-4 flex-1 overflow-auto">
1733
+ @if (showRoles()) {
1734
+ <div>
1735
+ <div
1736
+ class="text-sm text-muted-color mb-2 flex items-center gap-2"
1737
+ >
1738
+ <ui-icon name="users"></ui-icon>
1739
+ {{ 'profile.roles.label' | translate }}
1740
+ </div>
1741
+ @if (isLoadingPermissions()) {
1742
+ <f-skeleton height="1.5rem" class="mb-2" />
1743
+ } @else if (userRoles().length === 0) {
1744
+ <span class="text-muted-color text-sm">{{
1745
+ 'profile.roles.none.assigned' | translate
1746
+ }}</span>
1747
+ } @else {
1748
+ <div class="flex flex-wrap gap-2">
1749
+ @for (role of userRoles(); track role.id) {
1750
+ <f-tag [value]="role.name" severity="info" />
1751
+ }
1752
+ </div>
1753
+ }
1754
+ </div>
1755
+ }
1756
+
1757
+ @if (showActions()) {
1758
+ <div>
1759
+ <div
1760
+ class="text-sm text-muted-color mb-2 flex items-center gap-2"
1761
+ >
1762
+ <ui-icon name="bolt"></ui-icon>
1763
+ {{ 'profile.actions.label' | translate }}
1764
+ </div>
1765
+ @if (isLoadingPermissions()) {
1766
+ <f-skeleton height="1.5rem" class="mb-2" />
1767
+ } @else if (userActions().length === 0) {
1768
+ <span class="text-muted-color text-sm">{{
1769
+ 'profile.actions.none.assigned' | translate
1770
+ }}</span>
1771
+ } @else {
1772
+ <div class="flex flex-wrap gap-2">
1773
+ @for (action of userActions(); track action.id) {
1774
+ <f-tag [value]="action.name" severity="success" />
1775
+ }
1776
+ </div>
1777
+ }
1778
+ </div>
1779
+ }
1780
+ </div>
1781
+ </div>
1782
+ }
1783
+ </div>
1784
+ }
1785
+ `
1786
+ }]
1787
+ }],
1788
+ ctorParameters: () => []
1789
+ });
1790
+ export { ProfilePageComponent };
1791
+
1792
+ //# sourceMappingURL=flusys-ng-auth-profile-page.component-b-8rPEXQ.mjs.map