valtech-components 2.0.996 → 2.0.998
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/components/organisms/notification-preferences-view/notification-preferences-view.component.mjs +667 -0
- package/esm2022/lib/components/organisms/notification-preferences-view/notification-preferences-view.i18n.mjs +71 -0
- package/esm2022/lib/components/organisms/notification-preferences-view/notification-preferences.routes.mjs +31 -0
- package/esm2022/lib/components/organisms/notification-preferences-view/types.mjs +2 -0
- package/esm2022/lib/components/organisms/permissions-view/permissions-view.component.mjs +511 -0
- package/esm2022/lib/components/organisms/permissions-view/permissions-view.i18n.mjs +116 -0
- package/esm2022/lib/components/organisms/permissions-view/permissions.routes.mjs +29 -0
- package/esm2022/lib/components/organisms/permissions-view/types.mjs +2 -0
- package/esm2022/lib/components/organisms/settings-hub/settings.routes.mjs +12 -1
- package/esm2022/lib/services/org/permission-catalog.service.mjs +33 -0
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +8 -1
- package/fesm2022/valtech-components.mjs +1532 -111
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/organisms/notification-preferences-view/notification-preferences-view.component.d.ts +146 -0
- package/lib/components/organisms/notification-preferences-view/notification-preferences-view.i18n.d.ts +11 -0
- package/lib/components/organisms/notification-preferences-view/notification-preferences.routes.d.ts +26 -0
- package/lib/components/organisms/notification-preferences-view/types.d.ts +36 -0
- package/lib/components/organisms/permissions-view/permissions-view.component.d.ts +71 -0
- package/lib/components/organisms/permissions-view/permissions-view.i18n.d.ts +9 -0
- package/lib/components/organisms/permissions-view/permissions.routes.d.ts +24 -0
- package/lib/components/organisms/permissions-view/types.d.ts +26 -0
- package/lib/components/organisms/settings-hub/settings.routes.d.ts +15 -0
- package/lib/services/org/permission-catalog.service.d.ts +34 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +7 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, InjectionToken, makeEnvironmentProviders, APP_INITIALIZER, ErrorHandler, Injectable, DestroyRef, Inject, signal, computed, PLATFORM_ID, isDevMode, Optional, runInInjectionContext, effect, Pipe, TemplateRef, ViewContainerRef, input, Directive, EventEmitter, Component, Input, Output, ChangeDetectionStrategy, HostBinding, HostListener, NgZone, ViewChild, ChangeDetectorRef, output, ContentChild, ElementRef, viewChild, untracked, Injector, isSignal, ViewEncapsulation } from '@angular/core';
|
|
3
|
-
import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, race, timer,
|
|
3
|
+
import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, race, timer, forkJoin, shareReplay } from 'rxjs';
|
|
4
4
|
import { catchError, switchMap, finalize, filter, take, map as map$1, tap, distinctUntilChanged as distinctUntilChanged$1, retry, timeout, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
|
|
5
5
|
import * as i1$3 from '@angular/common/http';
|
|
6
6
|
import { provideHttpClient, withInterceptors, HttpClient, HttpErrorResponse } from '@angular/common/http';
|
|
@@ -54,7 +54,7 @@ import 'prismjs/components/prism-json';
|
|
|
54
54
|
* Current version of valtech-components.
|
|
55
55
|
* This is automatically updated during the publish process.
|
|
56
56
|
*/
|
|
57
|
-
const VERSION = '2.0.
|
|
57
|
+
const VERSION = '2.0.998';
|
|
58
58
|
|
|
59
59
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
60
60
|
let isRefreshing = false;
|
|
@@ -41750,7 +41750,7 @@ const PROFILE_VIEW_I18N = {
|
|
|
41750
41750
|
const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
|
|
41751
41751
|
const HANDLE_MIN = 3;
|
|
41752
41752
|
const HANDLE_MAX = 20;
|
|
41753
|
-
const DEFAULT_NAMESPACE$
|
|
41753
|
+
const DEFAULT_NAMESPACE$h = 'Settings.Profile';
|
|
41754
41754
|
/**
|
|
41755
41755
|
* `val-profile-view` — vista Perfil full-feature autocontenida (organism).
|
|
41756
41756
|
*
|
|
@@ -41800,7 +41800,7 @@ class ProfileViewComponent {
|
|
|
41800
41800
|
showPhone: merged.showPhone ?? true,
|
|
41801
41801
|
showShareCta: merged.showShareCta ?? true,
|
|
41802
41802
|
profileBaseUrl: merged.profileBaseUrl ?? '',
|
|
41803
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
41803
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$h,
|
|
41804
41804
|
onSaved: merged.onSaved,
|
|
41805
41805
|
onAvatarUploaded: merged.onAvatarUploaded,
|
|
41806
41806
|
};
|
|
@@ -42716,7 +42716,7 @@ const PREFERENCES_VIEW_I18N = {
|
|
|
42716
42716
|
},
|
|
42717
42717
|
};
|
|
42718
42718
|
|
|
42719
|
-
const DEFAULT_NAMESPACE$
|
|
42719
|
+
const DEFAULT_NAMESPACE$g = 'Settings.General';
|
|
42720
42720
|
const DEFAULT_LANGUAGES = ['es', 'en'];
|
|
42721
42721
|
/**
|
|
42722
42722
|
* `val-preferences-view` — vista de preferencias de UI cross-app (tema +
|
|
@@ -42753,7 +42753,7 @@ class PreferencesViewComponent {
|
|
|
42753
42753
|
showLanguage: merged.showLanguage ?? true,
|
|
42754
42754
|
showFontSize: merged.showFontSize ?? true,
|
|
42755
42755
|
supportedLanguages: merged.supportedLanguages ?? DEFAULT_LANGUAGES,
|
|
42756
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
42756
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$g,
|
|
42757
42757
|
onThemeChanged: merged.onThemeChanged,
|
|
42758
42758
|
onLanguageChanged: merged.onLanguageChanged,
|
|
42759
42759
|
onFontSizeChanged: merged.onFontSizeChanged,
|
|
@@ -43401,62 +43401,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
43401
43401
|
type: Input
|
|
43402
43402
|
}] } });
|
|
43403
43403
|
|
|
43404
|
-
/**
|
|
43405
|
-
* Helper para montar el bloque completo de Settings en una app del factory: el
|
|
43406
|
-
* HUB de navegación (`val-settings-hub`) en el index + las vistas promovidas
|
|
43407
|
-
* (Perfil, Preferencias) + las rutas propias de la app (`extraRoutes`).
|
|
43408
|
-
*
|
|
43409
|
-
* Pensado para apps nuevas (greenfield) que adoptan el set entero. showcase NO
|
|
43410
|
-
* lo usa hoy (preserva su `settings.routes.ts` por la semántica de sus muchas
|
|
43411
|
-
* secciones no promovidas — ver guía de promoción), pero el provider debe
|
|
43412
|
-
* existir y compilar para el resto de apps.
|
|
43413
|
-
*
|
|
43414
|
-
* @example
|
|
43415
|
-
* ```ts
|
|
43416
|
-
* // app.routes.ts
|
|
43417
|
-
* import { provideValtechSettingsRoutes } from 'valtech-components';
|
|
43418
|
-
*
|
|
43419
|
-
* export const routes: Routes = [
|
|
43420
|
-
* {
|
|
43421
|
-
* path: 'app',
|
|
43422
|
-
* children: [
|
|
43423
|
-
* ...provideValtechSettingsRoutes({
|
|
43424
|
-
* hubConfig: { onLogout: () => auth.logoutWithConfirmation() },
|
|
43425
|
-
* extraRoutes: [
|
|
43426
|
-
* { path: 'security', loadComponent: () => ... },
|
|
43427
|
-
* ],
|
|
43428
|
-
* }),
|
|
43429
|
-
* ],
|
|
43430
|
-
* },
|
|
43431
|
-
* ];
|
|
43432
|
-
* ```
|
|
43433
|
-
*/
|
|
43434
|
-
function provideValtechSettingsRoutes(opts) {
|
|
43435
|
-
const basePath = opts?.basePath ?? 'settings';
|
|
43436
|
-
const includeProfile = opts?.includeProfile ?? true;
|
|
43437
|
-
const includePreferences = opts?.includePreferences ?? true;
|
|
43438
|
-
return [
|
|
43439
|
-
{
|
|
43440
|
-
path: basePath,
|
|
43441
|
-
children: [
|
|
43442
|
-
{
|
|
43443
|
-
path: '',
|
|
43444
|
-
component: SettingsHubComponent,
|
|
43445
|
-
data: { settingsHubConfig: opts?.hubConfig },
|
|
43446
|
-
},
|
|
43447
|
-
...(includeProfile ? provideValtechProfileRoutes({ config: opts?.profileConfig }) : []),
|
|
43448
|
-
...(includePreferences
|
|
43449
|
-
? provideValtechPreferencesRoutes({
|
|
43450
|
-
path: opts?.preferencesPath ?? 'preferences',
|
|
43451
|
-
config: opts?.preferencesConfig,
|
|
43452
|
-
})
|
|
43453
|
-
: []),
|
|
43454
|
-
...(opts?.extraRoutes ?? []),
|
|
43455
|
-
],
|
|
43456
|
-
},
|
|
43457
|
-
];
|
|
43458
|
-
}
|
|
43459
|
-
|
|
43460
43404
|
addIcons({ shieldCheckmarkOutline, checkmarkCircleOutline });
|
|
43461
43405
|
/**
|
|
43462
43406
|
* `val-login-attempt-modal` — modal de confirmación de intento de inicio de sesión.
|
|
@@ -44565,7 +44509,7 @@ const SECURITY_VIEW_I18N = {
|
|
|
44565
44509
|
};
|
|
44566
44510
|
|
|
44567
44511
|
addIcons({ laptopOutline, phonePortraitOutline });
|
|
44568
|
-
const DEFAULT_NAMESPACE$
|
|
44512
|
+
const DEFAULT_NAMESPACE$f = 'Settings.Security';
|
|
44569
44513
|
/**
|
|
44570
44514
|
* `val-security-view` — vista Seguridad full-feature autocontenida (organism).
|
|
44571
44515
|
*
|
|
@@ -44614,7 +44558,7 @@ class SecurityViewComponent {
|
|
|
44614
44558
|
showChangeEmail: merged.showChangeEmail ?? true,
|
|
44615
44559
|
showMfa: merged.showMfa ?? true,
|
|
44616
44560
|
showPolicies: merged.showPolicies ?? true,
|
|
44617
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
44561
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$f,
|
|
44618
44562
|
handleDeepLinks: merged.handleDeepLinks ?? true,
|
|
44619
44563
|
homeRoute: merged.homeRoute ?? '/app/home',
|
|
44620
44564
|
onMfaCompleted: merged.onMfaCompleted,
|
|
@@ -45527,7 +45471,7 @@ const CREATE_ORG_MODAL_I18N = {
|
|
|
45527
45471
|
},
|
|
45528
45472
|
};
|
|
45529
45473
|
|
|
45530
|
-
const DEFAULT_NAMESPACE$
|
|
45474
|
+
const DEFAULT_NAMESPACE$e = 'CreateOrgModal';
|
|
45531
45475
|
/**
|
|
45532
45476
|
* `val-create-org-modal` — modal de creación de organización (organism
|
|
45533
45477
|
* compartido). Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por
|
|
@@ -45553,10 +45497,10 @@ class CreateOrgModalComponent {
|
|
|
45553
45497
|
this.toast = inject(ToastService);
|
|
45554
45498
|
this.errors = inject(ValtechErrorService);
|
|
45555
45499
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
45556
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
45500
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$e;
|
|
45557
45501
|
this._busy = signal(false);
|
|
45558
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
45559
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
45502
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$e)) {
|
|
45503
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$e, CREATE_ORG_MODAL_I18N);
|
|
45560
45504
|
}
|
|
45561
45505
|
this.formMeta = signal(this.buildFormMeta());
|
|
45562
45506
|
effect(() => {
|
|
@@ -45792,7 +45736,7 @@ const DELETE_ACCOUNT_MODAL_I18N = {
|
|
|
45792
45736
|
},
|
|
45793
45737
|
};
|
|
45794
45738
|
|
|
45795
|
-
const DEFAULT_NAMESPACE$
|
|
45739
|
+
const DEFAULT_NAMESPACE$d = 'Settings.DeleteAccount';
|
|
45796
45740
|
/**
|
|
45797
45741
|
* `val-delete-account-modal` — modal de eliminación de cuenta (organism,
|
|
45798
45742
|
* cuenta-específico). Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
@@ -45818,7 +45762,7 @@ class DeleteAccountModalComponent {
|
|
|
45818
45762
|
this.i18n = inject(I18nService);
|
|
45819
45763
|
this.toast = inject(ToastService);
|
|
45820
45764
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
45821
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
45765
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$d;
|
|
45822
45766
|
this.hasPassword = signal(null);
|
|
45823
45767
|
this.codeSent = signal(false);
|
|
45824
45768
|
this.formState = signal(ComponentStates.ENABLED);
|
|
@@ -45885,8 +45829,8 @@ class DeleteAccountModalComponent {
|
|
|
45885
45829
|
],
|
|
45886
45830
|
actions: this.submitBtn('delete-code-submit', 'confirmCta'),
|
|
45887
45831
|
}));
|
|
45888
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
45889
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
45832
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$d)) {
|
|
45833
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$d, DELETE_ACCOUNT_MODAL_I18N);
|
|
45890
45834
|
}
|
|
45891
45835
|
}
|
|
45892
45836
|
ngOnInit() {
|
|
@@ -46087,7 +46031,7 @@ const ORG_INFO_SHEET_I18N = {
|
|
|
46087
46031
|
},
|
|
46088
46032
|
};
|
|
46089
46033
|
|
|
46090
|
-
const DEFAULT_NAMESPACE$
|
|
46034
|
+
const DEFAULT_NAMESPACE$c = 'OrgInfoSheet';
|
|
46091
46035
|
/**
|
|
46092
46036
|
* `val-org-info-sheet` — sheet informativo "¿Qué son las organizaciones?".
|
|
46093
46037
|
* Extraído del `OrgInfoSheetComponent` inline de `AccountPage` bajo el proceso de
|
|
@@ -46132,15 +46076,15 @@ class OrgInfoSheetComponent {
|
|
|
46132
46076
|
text: this.closeProps.text || this.resolvedCloseLabel(),
|
|
46133
46077
|
}));
|
|
46134
46078
|
addIcons({ peopleOutline, swapHorizontalOutline, mailOutline });
|
|
46135
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
46136
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
46079
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$c)) {
|
|
46080
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$c, ORG_INFO_SHEET_I18N);
|
|
46137
46081
|
}
|
|
46138
46082
|
}
|
|
46139
46083
|
dismiss() {
|
|
46140
46084
|
this._modalRef?.dismiss(null, 'cancel');
|
|
46141
46085
|
}
|
|
46142
46086
|
tt(key) {
|
|
46143
|
-
return this.i18n.t(key, DEFAULT_NAMESPACE$
|
|
46087
|
+
return this.i18n.t(key, DEFAULT_NAMESPACE$c);
|
|
46144
46088
|
}
|
|
46145
46089
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgInfoSheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
46146
46090
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: OrgInfoSheetComponent, isStandalone: true, selector: "val-org-info-sheet", inputs: { title: "title", subtitle: "subtitle", body: "body", closeLabel: "closeLabel", closeProps: "closeProps", _modalRef: "_modalRef" }, ngImport: i0, template: `
|
|
@@ -46307,7 +46251,7 @@ const SWITCH_ORG_MODAL_I18N = {
|
|
|
46307
46251
|
},
|
|
46308
46252
|
};
|
|
46309
46253
|
|
|
46310
|
-
const DEFAULT_NAMESPACE$
|
|
46254
|
+
const DEFAULT_NAMESPACE$b = 'Settings.SwitchOrg';
|
|
46311
46255
|
addIcons({ businessOutline, checkmarkCircleOutline });
|
|
46312
46256
|
/**
|
|
46313
46257
|
* `val-switch-org-modal` — modal de cambio de organización activa (organism
|
|
@@ -46333,7 +46277,7 @@ class SwitchOrgModalComponent {
|
|
|
46333
46277
|
this.orgSwitch = inject(OrgSwitchService);
|
|
46334
46278
|
this.errors = inject(ValtechErrorService);
|
|
46335
46279
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
46336
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
46280
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$b;
|
|
46337
46281
|
this.orgs = signal([]);
|
|
46338
46282
|
this.loading = signal(false);
|
|
46339
46283
|
this.query = signal('');
|
|
@@ -46349,8 +46293,8 @@ class SwitchOrgModalComponent {
|
|
|
46349
46293
|
return list;
|
|
46350
46294
|
return list.filter(o => o.name.toLowerCase().includes(q));
|
|
46351
46295
|
});
|
|
46352
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
46353
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
46296
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$b)) {
|
|
46297
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$b, SWITCH_ORG_MODAL_I18N);
|
|
46354
46298
|
}
|
|
46355
46299
|
this.loadOrgs();
|
|
46356
46300
|
}
|
|
@@ -46668,7 +46612,7 @@ const ACCOUNT_VIEW_I18N = {
|
|
|
46668
46612
|
};
|
|
46669
46613
|
|
|
46670
46614
|
addIcons({ peopleOutline, businessOutline, chevronForwardOutline, swapHorizontalOutline, mailOutline });
|
|
46671
|
-
const DEFAULT_NAMESPACE$
|
|
46615
|
+
const DEFAULT_NAMESPACE$a = 'Settings.Account';
|
|
46672
46616
|
const DEFAULT_MANAGE_ORG_ROUTE = '/app/settings/organization';
|
|
46673
46617
|
/**
|
|
46674
46618
|
* `val-account-view` — vista Cuenta full-feature autocontenida (organism).
|
|
@@ -46709,7 +46653,7 @@ class AccountViewComponent {
|
|
|
46709
46653
|
showNewOrgCta: merged.showNewOrgCta ?? true,
|
|
46710
46654
|
showLogout: merged.showLogout ?? true,
|
|
46711
46655
|
showDeleteAccount: merged.showDeleteAccount ?? true,
|
|
46712
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
46656
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$a,
|
|
46713
46657
|
manageOrgRoute: merged.manageOrgRoute ?? DEFAULT_MANAGE_ORG_ROUTE,
|
|
46714
46658
|
onOrgSwitched: merged.onOrgSwitched,
|
|
46715
46659
|
onOrgCreated: merged.onOrgCreated,
|
|
@@ -47434,7 +47378,7 @@ const EDIT_ORG_MODAL_I18N = {
|
|
|
47434
47378
|
},
|
|
47435
47379
|
};
|
|
47436
47380
|
|
|
47437
|
-
const DEFAULT_NAMESPACE$
|
|
47381
|
+
const DEFAULT_NAMESPACE$9 = 'EditOrgModal';
|
|
47438
47382
|
/**
|
|
47439
47383
|
* `val-edit-org-modal` — modal de edición de organización (nombre + descripción).
|
|
47440
47384
|
* Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por la vista de
|
|
@@ -47461,10 +47405,10 @@ class EditOrgModalComponent {
|
|
|
47461
47405
|
/** Organización a editar (pasada por `componentProps`). */
|
|
47462
47406
|
this.org = null;
|
|
47463
47407
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
47464
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
47408
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$9;
|
|
47465
47409
|
this.saving = signal(false);
|
|
47466
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
47467
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
47410
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$9)) {
|
|
47411
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$9, EDIT_ORG_MODAL_I18N);
|
|
47468
47412
|
}
|
|
47469
47413
|
this.formMeta = signal(this.buildFormMeta());
|
|
47470
47414
|
effect(() => {
|
|
@@ -47702,7 +47646,7 @@ const INVITE_MEMBER_MODAL_I18N = {
|
|
|
47702
47646
|
},
|
|
47703
47647
|
};
|
|
47704
47648
|
|
|
47705
|
-
const DEFAULT_NAMESPACE$
|
|
47649
|
+
const DEFAULT_NAMESPACE$8 = 'InviteModal';
|
|
47706
47650
|
addIcons({ personOutline, mailOutline, closeOutline, checkmarkCircleOutline });
|
|
47707
47651
|
/**
|
|
47708
47652
|
* `val-invite-member-modal` — invitación de miembros a una organización.
|
|
@@ -47733,7 +47677,7 @@ class InviteMemberModalComponent {
|
|
|
47733
47677
|
/** Ids de miembros ya existentes (excluidos de los resultados). */
|
|
47734
47678
|
this.existingMemberIds = [];
|
|
47735
47679
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
47736
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
47680
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$8;
|
|
47737
47681
|
this.query = signal('');
|
|
47738
47682
|
this.searching = signal(false);
|
|
47739
47683
|
this.searchResults = signal([]);
|
|
@@ -47780,8 +47724,8 @@ class InviteMemberModalComponent {
|
|
|
47780
47724
|
state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
47781
47725
|
}));
|
|
47782
47726
|
this.searchSubject = new Subject();
|
|
47783
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
47784
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
47727
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$8)) {
|
|
47728
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$8, INVITE_MEMBER_MODAL_I18N);
|
|
47785
47729
|
}
|
|
47786
47730
|
this.loadRoles();
|
|
47787
47731
|
this.roleSub = this.roleControl.valueChanges.subscribe(v => {
|
|
@@ -48270,7 +48214,7 @@ const MEMBER_DETAIL_MODAL_I18N = {
|
|
|
48270
48214
|
},
|
|
48271
48215
|
};
|
|
48272
48216
|
|
|
48273
|
-
const DEFAULT_NAMESPACE$
|
|
48217
|
+
const DEFAULT_NAMESPACE$7 = 'Settings.MemberDetail';
|
|
48274
48218
|
/**
|
|
48275
48219
|
* `val-member-detail-modal` — detalle de miembro de organización: perfil, rol y
|
|
48276
48220
|
* permisos. Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
@@ -48307,7 +48251,7 @@ class MemberDetailModalComponent {
|
|
|
48307
48251
|
{ id: 'admin', name: 'admin' },
|
|
48308
48252
|
];
|
|
48309
48253
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
48310
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
48254
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$7;
|
|
48311
48255
|
this.working = signal(false);
|
|
48312
48256
|
this.currentRole = signal('');
|
|
48313
48257
|
this.roleControl = new FormControl('');
|
|
@@ -48347,8 +48291,8 @@ class MemberDetailModalComponent {
|
|
|
48347
48291
|
type: 'button',
|
|
48348
48292
|
state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
48349
48293
|
}));
|
|
48350
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
48351
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
48294
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
|
|
48295
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$7, MEMBER_DETAIL_MODAL_I18N);
|
|
48352
48296
|
}
|
|
48353
48297
|
}
|
|
48354
48298
|
ngOnInit() {
|
|
@@ -48685,7 +48629,7 @@ const TRANSFER_OWNERSHIP_MODAL_I18N = {
|
|
|
48685
48629
|
},
|
|
48686
48630
|
};
|
|
48687
48631
|
|
|
48688
|
-
const DEFAULT_NAMESPACE$
|
|
48632
|
+
const DEFAULT_NAMESPACE$6 = 'Settings.TransferModal';
|
|
48689
48633
|
/**
|
|
48690
48634
|
* `val-transfer-ownership-modal` — selector de miembro para transferir la
|
|
48691
48635
|
* propiedad de la organización. Promovido desde `showcase` bajo el proceso de
|
|
@@ -48709,7 +48653,7 @@ class TransferOwnershipModalComponent {
|
|
|
48709
48653
|
/** Lista de miembros transferibles (todos excepto el owner actual). */
|
|
48710
48654
|
this.members = [];
|
|
48711
48655
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
48712
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
48656
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$6;
|
|
48713
48657
|
this.query = signal('');
|
|
48714
48658
|
this.selectedId = signal('');
|
|
48715
48659
|
this.filtered = computed(() => {
|
|
@@ -48721,8 +48665,8 @@ class TransferOwnershipModalComponent {
|
|
|
48721
48665
|
m.userId.toLowerCase().includes(q));
|
|
48722
48666
|
});
|
|
48723
48667
|
this.noResultsLabel = computed(() => `${this.t('noResults')} "${this.query()}"`);
|
|
48724
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
48725
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
48668
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$6)) {
|
|
48669
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$6, TRANSFER_OWNERSHIP_MODAL_I18N);
|
|
48726
48670
|
}
|
|
48727
48671
|
}
|
|
48728
48672
|
ngOnInit() {
|
|
@@ -48970,7 +48914,7 @@ const MEMBER_IMPORT_MODAL_I18N = {
|
|
|
48970
48914
|
},
|
|
48971
48915
|
};
|
|
48972
48916
|
|
|
48973
|
-
const DEFAULT_NAMESPACE$
|
|
48917
|
+
const DEFAULT_NAMESPACE$5 = 'Settings.ImportModal';
|
|
48974
48918
|
/**
|
|
48975
48919
|
* `val-member-import-modal` — panel de carga masiva de miembros (ADR-023 fase 6).
|
|
48976
48920
|
*
|
|
@@ -48989,7 +48933,7 @@ class MemberImportModalComponent {
|
|
|
48989
48933
|
/** Nombres de roles disponibles (para el hint). Opcional. */
|
|
48990
48934
|
this.availableRoles = [];
|
|
48991
48935
|
/** Namespace i18n. */
|
|
48992
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
48936
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$5;
|
|
48993
48937
|
this.csv = '';
|
|
48994
48938
|
this.onConflict = 'assignRole';
|
|
48995
48939
|
this.sendActivation = true;
|
|
@@ -49003,8 +48947,8 @@ class MemberImportModalComponent {
|
|
|
49003
48947
|
return this.t('summary');
|
|
49004
48948
|
return `${this.t('summary')}: ${s.created} ${this.t('created')} · ${s.assigned} ${this.t('assigned')} · ${s.skipped} ${this.t('skipped')} · ${s.failed} ${this.t('failed')}`;
|
|
49005
48949
|
});
|
|
49006
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
49007
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
48950
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
|
|
48951
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$5, MEMBER_IMPORT_MODAL_I18N);
|
|
49008
48952
|
}
|
|
49009
48953
|
}
|
|
49010
48954
|
ngOnInit() {
|
|
@@ -49376,7 +49320,7 @@ const API_KEYS_MODAL_I18N = {
|
|
|
49376
49320
|
},
|
|
49377
49321
|
};
|
|
49378
49322
|
|
|
49379
|
-
const DEFAULT_NAMESPACE$
|
|
49323
|
+
const DEFAULT_NAMESPACE$4 = 'Settings.ApiKeysModal';
|
|
49380
49324
|
/**
|
|
49381
49325
|
* `val-api-keys-modal` — gestión de Client API Keys org-scopeadas (ADR-023 fase 6).
|
|
49382
49326
|
*
|
|
@@ -49389,7 +49333,7 @@ class ApiKeysModalComponent {
|
|
|
49389
49333
|
this.i18n = inject(I18nService);
|
|
49390
49334
|
this.apiKeys = inject(ApiKeyService);
|
|
49391
49335
|
/** Namespace i18n. */
|
|
49392
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
49336
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$4;
|
|
49393
49337
|
this.keys = signal([]);
|
|
49394
49338
|
this.availablePermissions = signal([]);
|
|
49395
49339
|
this.loading = signal(true);
|
|
@@ -49401,8 +49345,8 @@ class ApiKeysModalComponent {
|
|
|
49401
49345
|
this.name = '';
|
|
49402
49346
|
this.expiresInDays = null;
|
|
49403
49347
|
this.selectedPerms = signal(new Set());
|
|
49404
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
49405
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
49348
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$4)) {
|
|
49349
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$4, API_KEYS_MODAL_I18N);
|
|
49406
49350
|
}
|
|
49407
49351
|
}
|
|
49408
49352
|
ngOnInit() {
|
|
@@ -49902,7 +49846,7 @@ const ORGANIZATION_VIEW_I18N = {
|
|
|
49902
49846
|
},
|
|
49903
49847
|
};
|
|
49904
49848
|
|
|
49905
|
-
const DEFAULT_NAMESPACE$
|
|
49849
|
+
const DEFAULT_NAMESPACE$3 = 'Settings.Organization';
|
|
49906
49850
|
const DEFAULT_VIEW_PERMISSIONS_ROUTE = '/app/settings/permissions';
|
|
49907
49851
|
const DEFAULT_LEAVE_REDIRECT_ROUTE = '/app/settings/account';
|
|
49908
49852
|
/**
|
|
@@ -49959,7 +49903,7 @@ class OrganizationViewComponent {
|
|
|
49959
49903
|
showInvite: merged.showInvite ?? true,
|
|
49960
49904
|
showTransferOwnership: merged.showTransferOwnership ?? true,
|
|
49961
49905
|
showLeave: merged.showLeave ?? true,
|
|
49962
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
49906
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$3,
|
|
49963
49907
|
viewPermissionsRoute: merged.viewPermissionsRoute ?? DEFAULT_VIEW_PERMISSIONS_ROUTE,
|
|
49964
49908
|
leaveRedirectRoute: merged.leaveRedirectRoute ?? DEFAULT_LEAVE_REDIRECT_ROUTE,
|
|
49965
49909
|
onOrgEdited: merged.onOrgEdited,
|
|
@@ -51039,6 +50983,70 @@ function provideValtechOrganizationRoutes(opts) {
|
|
|
51039
50983
|
];
|
|
51040
50984
|
}
|
|
51041
50985
|
|
|
50986
|
+
/**
|
|
50987
|
+
* Helper para montar el bloque completo de Settings en una app del factory: el
|
|
50988
|
+
* HUB de navegación (`val-settings-hub`) en el index + las vistas promovidas
|
|
50989
|
+
* (Perfil, Preferencias) + las rutas propias de la app (`extraRoutes`).
|
|
50990
|
+
*
|
|
50991
|
+
* Pensado para apps nuevas (greenfield) que adoptan el set entero. showcase NO
|
|
50992
|
+
* lo usa hoy (preserva su `settings.routes.ts` por la semántica de sus muchas
|
|
50993
|
+
* secciones no promovidas — ver guía de promoción), pero el provider debe
|
|
50994
|
+
* existir y compilar para el resto de apps.
|
|
50995
|
+
*
|
|
50996
|
+
* @example
|
|
50997
|
+
* ```ts
|
|
50998
|
+
* // app.routes.ts
|
|
50999
|
+
* import { provideValtechSettingsRoutes } from 'valtech-components';
|
|
51000
|
+
*
|
|
51001
|
+
* export const routes: Routes = [
|
|
51002
|
+
* {
|
|
51003
|
+
* path: 'app',
|
|
51004
|
+
* children: [
|
|
51005
|
+
* ...provideValtechSettingsRoutes({
|
|
51006
|
+
* hubConfig: { onLogout: () => auth.logoutWithConfirmation() },
|
|
51007
|
+
* extraRoutes: [
|
|
51008
|
+
* { path: 'security', loadComponent: () => ... },
|
|
51009
|
+
* ],
|
|
51010
|
+
* }),
|
|
51011
|
+
* ],
|
|
51012
|
+
* },
|
|
51013
|
+
* ];
|
|
51014
|
+
* ```
|
|
51015
|
+
*/
|
|
51016
|
+
function provideValtechSettingsRoutes(opts) {
|
|
51017
|
+
const basePath = opts?.basePath ?? 'settings';
|
|
51018
|
+
const includeProfile = opts?.includeProfile ?? true;
|
|
51019
|
+
const includePreferences = opts?.includePreferences ?? true;
|
|
51020
|
+
const includeSecurity = opts?.includeSecurity ?? true;
|
|
51021
|
+
const includeAccount = opts?.includeAccount ?? true;
|
|
51022
|
+
const includeOrganization = opts?.includeOrganization ?? true;
|
|
51023
|
+
return [
|
|
51024
|
+
{
|
|
51025
|
+
path: basePath,
|
|
51026
|
+
children: [
|
|
51027
|
+
{
|
|
51028
|
+
path: '',
|
|
51029
|
+
component: SettingsHubComponent,
|
|
51030
|
+
data: { settingsHubConfig: opts?.hubConfig },
|
|
51031
|
+
},
|
|
51032
|
+
...(includeProfile ? provideValtechProfileRoutes({ config: opts?.profileConfig }) : []),
|
|
51033
|
+
...(includePreferences
|
|
51034
|
+
? provideValtechPreferencesRoutes({
|
|
51035
|
+
path: opts?.preferencesPath ?? 'preferences',
|
|
51036
|
+
config: opts?.preferencesConfig,
|
|
51037
|
+
})
|
|
51038
|
+
: []),
|
|
51039
|
+
...(includeSecurity ? provideValtechSecurityRoutes({ config: opts?.securityConfig }) : []),
|
|
51040
|
+
...(includeAccount ? provideValtechAccountRoutes({ config: opts?.accountConfig }) : []),
|
|
51041
|
+
...(includeOrganization
|
|
51042
|
+
? provideValtechOrganizationRoutes({ config: opts?.organizationConfig })
|
|
51043
|
+
: []),
|
|
51044
|
+
...(opts?.extraRoutes ?? []),
|
|
51045
|
+
],
|
|
51046
|
+
},
|
|
51047
|
+
];
|
|
51048
|
+
}
|
|
51049
|
+
|
|
51042
51050
|
/**
|
|
51043
51051
|
* Refreshable Stream
|
|
51044
51052
|
*
|
|
@@ -51247,7 +51255,7 @@ addIcons({
|
|
|
51247
51255
|
openOutline,
|
|
51248
51256
|
personOutline,
|
|
51249
51257
|
});
|
|
51250
|
-
const DEFAULT_NAMESPACE = 'Notifications';
|
|
51258
|
+
const DEFAULT_NAMESPACE$2 = 'Notifications';
|
|
51251
51259
|
const DEFAULT_SETTINGS_PATH = '/app/settings/notifications';
|
|
51252
51260
|
const DEFAULT_PAGE_SIZE = 10;
|
|
51253
51261
|
const DEFAULT_MAX_LOAD = 50;
|
|
@@ -51307,7 +51315,7 @@ class NotificationsViewComponent {
|
|
|
51307
51315
|
showPushOffCta: merged.showPushOffCta ?? true,
|
|
51308
51316
|
enableMarkAll: merged.enableMarkAll ?? true,
|
|
51309
51317
|
settingsNotificationsPath: merged.settingsNotificationsPath ?? DEFAULT_SETTINGS_PATH,
|
|
51310
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
51318
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$2,
|
|
51311
51319
|
pageSize: merged.pageSize ?? DEFAULT_PAGE_SIZE,
|
|
51312
51320
|
maxLoad: merged.maxLoad ?? DEFAULT_MAX_LOAD,
|
|
51313
51321
|
onNotificationOpen: merged.onNotificationOpen,
|
|
@@ -51975,6 +51983,1419 @@ function provideValtechNotificationsRoutes(opts) {
|
|
|
51975
51983
|
];
|
|
51976
51984
|
}
|
|
51977
51985
|
|
|
51986
|
+
/**
|
|
51987
|
+
* Defaults i18n (es/en) embebidos en `val-notification-preferences-view`.
|
|
51988
|
+
* Auto-registrados en el constructor del componente si el consumer no proveyó
|
|
51989
|
+
* el namespace (`Settings.Notifications` por default). SOLO incluye las keys que
|
|
51990
|
+
* la vista usa.
|
|
51991
|
+
*
|
|
51992
|
+
* Portado del override `Settings.Notifications` (showcase) bajo el proceso de
|
|
51993
|
+
* ADR-021.
|
|
51994
|
+
*/
|
|
51995
|
+
const NOTIFICATION_PREFERENCES_VIEW_I18N = {
|
|
51996
|
+
es: {
|
|
51997
|
+
pageTitle: 'Notificaciones',
|
|
51998
|
+
pageDescription: 'Configura como quieres recibir notificaciones',
|
|
51999
|
+
// Control principal (UX simplificado)
|
|
52000
|
+
simpleTitle: 'Notificaciones push en este dispositivo',
|
|
52001
|
+
simpleDescription: 'Recibe alertas en tiempo real incluso con la app cerrada.',
|
|
52002
|
+
enabling: 'Activando…',
|
|
52003
|
+
disabling: 'Desactivando…',
|
|
52004
|
+
enabled: 'Activadas',
|
|
52005
|
+
errorGeneric: 'No pudimos completar la configuración. Verifica los permisos del navegador y reintenta.',
|
|
52006
|
+
retryCta: 'Reintentar',
|
|
52007
|
+
// Mensajes contextuales
|
|
52008
|
+
permissionUnsupported: 'Tu navegador no soporta notificaciones push.',
|
|
52009
|
+
permissionDeniedInfo: 'Tu navegador esta bloqueando las notificaciones. Para habilitarlas, ve a la configuracion del sitio y permitelas manualmente.',
|
|
52010
|
+
// iOS PWA install
|
|
52011
|
+
iosInstallTitle: 'En iOS, instala la app para recibir notificaciones',
|
|
52012
|
+
iosInstallHint: 'Las notificaciones push en iOS (iPhone/iPad) solo funcionan cuando la app está instalada en la pantalla de inicio. Edge, Chrome y Firefox en iOS comparten esta limitación.',
|
|
52013
|
+
iosInstallStep1: 'Abre este sitio en Safari (no en Chrome/Edge).',
|
|
52014
|
+
iosInstallStep2: 'Toca el botón Compartir (□ con flecha hacia arriba).',
|
|
52015
|
+
iosInstallStep3: "Elige 'Añadir a pantalla de inicio'.",
|
|
52016
|
+
iosInstallStep4: 'Abre la app desde el ícono recién creado y vuelve aquí.',
|
|
52017
|
+
// Detalles técnicos (expandible)
|
|
52018
|
+
detailsShow: 'Ver detalles técnicos',
|
|
52019
|
+
detailsHide: 'Ocultar detalles',
|
|
52020
|
+
detailsToken: 'Token',
|
|
52021
|
+
detailsNone: 'Sin registrar',
|
|
52022
|
+
diagnosticsPermission: 'Permiso del navegador',
|
|
52023
|
+
diagnosticsSupported: 'Soporte del navegador',
|
|
52024
|
+
// Toasts
|
|
52025
|
+
enableSuccess: 'Notificaciones habilitadas',
|
|
52026
|
+
disableSuccess: 'Notificaciones deshabilitadas',
|
|
52027
|
+
},
|
|
52028
|
+
en: {
|
|
52029
|
+
pageTitle: 'Notifications',
|
|
52030
|
+
pageDescription: 'Configure how you want to receive notifications',
|
|
52031
|
+
simpleTitle: 'Push notifications on this device',
|
|
52032
|
+
simpleDescription: 'Get real-time alerts even when the app is closed.',
|
|
52033
|
+
enabling: 'Enabling…',
|
|
52034
|
+
disabling: 'Disabling…',
|
|
52035
|
+
enabled: 'Enabled',
|
|
52036
|
+
errorGeneric: "We couldn't complete the setup. Check your browser permissions and retry.",
|
|
52037
|
+
retryCta: 'Retry',
|
|
52038
|
+
permissionUnsupported: 'Your browser does not support push notifications.',
|
|
52039
|
+
permissionDeniedInfo: 'Your browser is blocking notifications. To enable them, go to site settings and allow them manually.',
|
|
52040
|
+
iosInstallTitle: 'On iOS, install the app to receive notifications',
|
|
52041
|
+
iosInstallHint: 'Push notifications on iOS (iPhone/iPad) only work when the app is installed on the Home Screen. Edge, Chrome and Firefox on iOS share this limitation.',
|
|
52042
|
+
iosInstallStep1: 'Open this site in Safari (not Chrome/Edge).',
|
|
52043
|
+
iosInstallStep2: 'Tap the Share button (□ with an up arrow).',
|
|
52044
|
+
iosInstallStep3: "Choose 'Add to Home Screen'.",
|
|
52045
|
+
iosInstallStep4: 'Open the app from the new icon and come back here.',
|
|
52046
|
+
detailsShow: 'View technical details',
|
|
52047
|
+
detailsHide: 'Hide details',
|
|
52048
|
+
detailsToken: 'Token',
|
|
52049
|
+
detailsNone: 'Not registered',
|
|
52050
|
+
diagnosticsPermission: 'Browser permission',
|
|
52051
|
+
diagnosticsSupported: 'Browser support',
|
|
52052
|
+
enableSuccess: 'Notifications enabled',
|
|
52053
|
+
disableSuccess: 'Notifications disabled',
|
|
52054
|
+
},
|
|
52055
|
+
};
|
|
52056
|
+
|
|
52057
|
+
addIcons({
|
|
52058
|
+
alertCircleOutline,
|
|
52059
|
+
chevronDownOutline,
|
|
52060
|
+
chevronUpOutline,
|
|
52061
|
+
closeCircleOutline,
|
|
52062
|
+
notificationsOffOutline,
|
|
52063
|
+
notificationsOutline,
|
|
52064
|
+
refreshOutline,
|
|
52065
|
+
});
|
|
52066
|
+
const DEFAULT_NAMESPACE$1 = 'Settings.Notifications';
|
|
52067
|
+
/**
|
|
52068
|
+
* `val-notification-preferences-view` — **preferencias de notificaciones push**
|
|
52069
|
+
* (FCM) full-feature autocontenida (organism). Promovida desde `showcase` bajo el
|
|
52070
|
+
* proceso de ADR-021.
|
|
52071
|
+
*
|
|
52072
|
+
* Separada de `val-notifications-view` (el inbox / feed). Aquí el user **activa o
|
|
52073
|
+
* desactiva** las notificaciones push en este dispositivo.
|
|
52074
|
+
*
|
|
52075
|
+
* UX simplificado: UN solo toggle que ejecuta toda la secuencia (pedir permiso →
|
|
52076
|
+
* SW ready → getToken → registrar en backend → set master pref) vía
|
|
52077
|
+
* `MessagingService.enable()`. El registro del device en el backend va como
|
|
52078
|
+
* callback (la lib no depende de `AuthService` para evitar ciclos). Mensajes
|
|
52079
|
+
* contextuales y botón "Reintentar" aparecen solo cuando aplican.
|
|
52080
|
+
*
|
|
52081
|
+
* NO renderiza ion-content — vive dentro de val-page-wrapper. `ActivatedRoute` se
|
|
52082
|
+
* inyecta `{ optional: true }` → funciona sin router (embebida / Storybook).
|
|
52083
|
+
*/
|
|
52084
|
+
class NotificationPreferencesViewComponent {
|
|
52085
|
+
/** Namespace i18n resuelto (capturado para llamadas no-reactivas). */
|
|
52086
|
+
get ns() {
|
|
52087
|
+
return this.resolvedConfig().i18nNamespace;
|
|
52088
|
+
}
|
|
52089
|
+
// ---------------------------------------------------------------------------
|
|
52090
|
+
// Lifecycle
|
|
52091
|
+
// ---------------------------------------------------------------------------
|
|
52092
|
+
constructor() {
|
|
52093
|
+
this.nav = inject(NavigationService);
|
|
52094
|
+
this.i18n = inject(I18nService);
|
|
52095
|
+
this.toast = inject(ToastService);
|
|
52096
|
+
this.auth = inject(AuthService);
|
|
52097
|
+
this.messaging = inject(MessagingService);
|
|
52098
|
+
this.pageRefresh = inject(PageRefreshService);
|
|
52099
|
+
this.prefs = inject(PreferencesService);
|
|
52100
|
+
// `ActivatedRoute` opcional → la vista funciona embebida / en Storybook sin
|
|
52101
|
+
// router (solo lo usa para leer el route data de config como fallback).
|
|
52102
|
+
this.route = inject(ActivatedRoute, { optional: true });
|
|
52103
|
+
this.resolvedConfig = computed(() => {
|
|
52104
|
+
const fromRoute = (this.route?.snapshot.data['notificationPreferencesConfig'] ??
|
|
52105
|
+
{});
|
|
52106
|
+
const merged = { ...fromRoute, ...(this.config ?? {}) };
|
|
52107
|
+
return {
|
|
52108
|
+
showDetails: merged.showDetails ?? true,
|
|
52109
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$1,
|
|
52110
|
+
onPushEnabled: merged.onPushEnabled,
|
|
52111
|
+
onPushDisabled: merged.onPushDisabled,
|
|
52112
|
+
};
|
|
52113
|
+
});
|
|
52114
|
+
/** Operación en curso (gobierna spinner del toggle + label "Activando…"). */
|
|
52115
|
+
this._busy = signal(null);
|
|
52116
|
+
/** True si el último intento falló — gobierna estado `error` + retry button. */
|
|
52117
|
+
this._lastFailed = signal(false);
|
|
52118
|
+
/** True mientras se re-obtiene el token FCM en background (no afecta la UI). */
|
|
52119
|
+
this._repairingToken = false;
|
|
52120
|
+
/** Toggle del expand de detalles técnicos. */
|
|
52121
|
+
this.showDetails = signal(false);
|
|
52122
|
+
this.permission = signal('default');
|
|
52123
|
+
this.isSupported = signal(false);
|
|
52124
|
+
this.currentToken = signal(null);
|
|
52125
|
+
this.needsIOSInstall = signal(this.detectNeedsIOSInstall());
|
|
52126
|
+
/** FormControl del toggle (sync-eado con `isEnabled` via effect). */
|
|
52127
|
+
this.toggleControl = new FormControl(false, {
|
|
52128
|
+
nonNullable: true,
|
|
52129
|
+
});
|
|
52130
|
+
/** Handler del listener `focus` — referencia guardada para poder removerlo. */
|
|
52131
|
+
this.onWindowFocus = () => this.scheduleBrowserRefresh();
|
|
52132
|
+
/** Timer del debounce del refresh disparado por `focus`. */
|
|
52133
|
+
this.focusDebounceTimer = null;
|
|
52134
|
+
/** Ventana de debounce (ms) para el refresh disparado por `focus`. */
|
|
52135
|
+
this.FOCUS_DEBOUNCE_MS = 500;
|
|
52136
|
+
// ---------------------------------------------------------------------------
|
|
52137
|
+
// Estado derivado
|
|
52138
|
+
// ---------------------------------------------------------------------------
|
|
52139
|
+
this.pageTitle = computed(() => this.tt('pageTitle'));
|
|
52140
|
+
this.pageDescription = computed(() => this.tt('pageDescription'));
|
|
52141
|
+
/**
|
|
52142
|
+
* Estado efectivo del toggle: "el usuario tiene push activo".
|
|
52143
|
+
*
|
|
52144
|
+
* Se deriva SOLO de la decisión del usuario (`master`) y del permiso del SO
|
|
52145
|
+
* (`permission`). El token FCM NO forma parte del estado de UI: vive en
|
|
52146
|
+
* memoria, se pierde en cada recarga/suspensión de la PWA y produciría flicker.
|
|
52147
|
+
* Si falta token con `master && granted`, es un detalle reparable en
|
|
52148
|
+
* background (ver `repairTokenIfNeeded`), no un cambio del toggle.
|
|
52149
|
+
*/
|
|
52150
|
+
this.isEnabled = computed(() => this.prefs.notificationsMaster() && this.permission() === 'granted');
|
|
52151
|
+
this.state = computed(() => {
|
|
52152
|
+
if (this.needsIOSInstall())
|
|
52153
|
+
return 'needs-install';
|
|
52154
|
+
if (this.permission() === 'unsupported' || !this.isSupported()) {
|
|
52155
|
+
// iOS standalone aún puede dar isSupported false temprano antes de init —
|
|
52156
|
+
// solo bloqueamos si el browser explícitamente no soporta APIs.
|
|
52157
|
+
if (this.permission() === 'unsupported')
|
|
52158
|
+
return 'unsupported';
|
|
52159
|
+
}
|
|
52160
|
+
const busy = this._busy();
|
|
52161
|
+
if (busy === 'enabling')
|
|
52162
|
+
return 'enabling';
|
|
52163
|
+
if (busy === 'disabling')
|
|
52164
|
+
return 'disabling';
|
|
52165
|
+
if (this.isEnabled())
|
|
52166
|
+
return 'enabled';
|
|
52167
|
+
if (this.permission() === 'denied')
|
|
52168
|
+
return 'blocked';
|
|
52169
|
+
if (this._lastFailed())
|
|
52170
|
+
return 'error';
|
|
52171
|
+
return 'idle';
|
|
52172
|
+
});
|
|
52173
|
+
this.busyLabel = computed(() => {
|
|
52174
|
+
const s = this.state();
|
|
52175
|
+
if (s === 'enabling')
|
|
52176
|
+
return this.tt('enabling');
|
|
52177
|
+
if (s === 'disabling')
|
|
52178
|
+
return this.tt('disabling');
|
|
52179
|
+
return null;
|
|
52180
|
+
});
|
|
52181
|
+
this.contextMessage = computed(() => {
|
|
52182
|
+
switch (this.state()) {
|
|
52183
|
+
case 'unsupported':
|
|
52184
|
+
return this.tt('permissionUnsupported');
|
|
52185
|
+
case 'blocked':
|
|
52186
|
+
return this.tt('permissionDeniedInfo');
|
|
52187
|
+
case 'error':
|
|
52188
|
+
return this.tt('errorGeneric');
|
|
52189
|
+
default:
|
|
52190
|
+
return null;
|
|
52191
|
+
}
|
|
52192
|
+
});
|
|
52193
|
+
this.alertVariant = computed(() => {
|
|
52194
|
+
const s = this.state();
|
|
52195
|
+
if (s === 'error' || s === 'blocked')
|
|
52196
|
+
return 'warning';
|
|
52197
|
+
if (s === 'unsupported')
|
|
52198
|
+
return 'danger';
|
|
52199
|
+
return 'info';
|
|
52200
|
+
});
|
|
52201
|
+
this.alertIcon = computed(() => {
|
|
52202
|
+
const s = this.state();
|
|
52203
|
+
if (s === 'unsupported')
|
|
52204
|
+
return 'close-circle-outline';
|
|
52205
|
+
return 'alert-circle-outline';
|
|
52206
|
+
});
|
|
52207
|
+
this.toggleProps = computed(() => ({
|
|
52208
|
+
control: this.toggleControl,
|
|
52209
|
+
color: 'primary',
|
|
52210
|
+
disabled: this._busy() !== null ||
|
|
52211
|
+
this.state() === 'unsupported' ||
|
|
52212
|
+
this.state() === 'needs-install' ||
|
|
52213
|
+
this.state() === 'blocked',
|
|
52214
|
+
}));
|
|
52215
|
+
this.retryButtonProps = computed(() => ({
|
|
52216
|
+
text: this.tt('retryCta'),
|
|
52217
|
+
color: 'dark',
|
|
52218
|
+
fill: 'outline',
|
|
52219
|
+
size: 'default',
|
|
52220
|
+
shape: 'round',
|
|
52221
|
+
type: 'button',
|
|
52222
|
+
icon: { name: 'refresh-outline', slot: 'start' },
|
|
52223
|
+
state: this._busy() !== null ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
52224
|
+
}));
|
|
52225
|
+
this.tokenPreview = computed(() => {
|
|
52226
|
+
const t = this.currentToken();
|
|
52227
|
+
if (!t)
|
|
52228
|
+
return null;
|
|
52229
|
+
return `${t.slice(0, 22)}…${t.slice(-6)}`;
|
|
52230
|
+
});
|
|
52231
|
+
const ns = this.ns;
|
|
52232
|
+
if (!this.i18n.hasNamespace(ns)) {
|
|
52233
|
+
this.i18n.registerContent(ns, NOTIFICATION_PREFERENCES_VIEW_I18N);
|
|
52234
|
+
}
|
|
52235
|
+
this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
|
|
52236
|
+
void this.refreshBrowserState();
|
|
52237
|
+
// Re-aplicar header en cambio de idioma.
|
|
52238
|
+
effect(() => {
|
|
52239
|
+
this.i18n.lang();
|
|
52240
|
+
this.nav.setBackHeader('pageTitle', this.ns, { withMenu: true });
|
|
52241
|
+
}, { allowSignalWrites: true });
|
|
52242
|
+
// Sync FormControl ←→ estado efectivo. Cuando el estado cambia (cambio de
|
|
52243
|
+
// permission por el browser, o snapshot Firestore de `master`), el toggle se
|
|
52244
|
+
// re-alinea con la realidad.
|
|
52245
|
+
//
|
|
52246
|
+
// IMPORTANTE: se ignoran los cambios mientras hay una operación en curso
|
|
52247
|
+
// (`_busy() !== null`). Durante `onEnable`/`onDisable` las señales fuente se
|
|
52248
|
+
// actualizan en momentos distintos y producirían estados intermedios →
|
|
52249
|
+
// flicker visible. El toggle se re-alinea UNA sola vez en el `finally`.
|
|
52250
|
+
effect(() => {
|
|
52251
|
+
const eff = this.isEnabled();
|
|
52252
|
+
if (this._busy() !== null)
|
|
52253
|
+
return;
|
|
52254
|
+
if (this.toggleControl.value !== eff) {
|
|
52255
|
+
this.toggleControl.setValue(eff, { emitEvent: false });
|
|
52256
|
+
}
|
|
52257
|
+
}, { allowSignalWrites: true });
|
|
52258
|
+
// Interceptar click del user: el toggle ya cambió visualmente, ejecutar
|
|
52259
|
+
// la acción correspondiente.
|
|
52260
|
+
this.toggleControl.valueChanges.subscribe(v => {
|
|
52261
|
+
if (this._busy() !== null)
|
|
52262
|
+
return;
|
|
52263
|
+
if (v && !this.isEnabled()) {
|
|
52264
|
+
void this.onEnable();
|
|
52265
|
+
}
|
|
52266
|
+
else if (!v && this.isEnabled()) {
|
|
52267
|
+
void this.onDisable();
|
|
52268
|
+
}
|
|
52269
|
+
});
|
|
52270
|
+
// Detectar regreso a la pestaña — el user pudo haber cambiado permisos
|
|
52271
|
+
// desde la config del browser. El refresh va con debounce para no
|
|
52272
|
+
// dispararse en ráfagas (en PWA mobile `focus` salta con cada cambio de
|
|
52273
|
+
// app / aparición del teclado). El listener se remueve en ngOnDestroy.
|
|
52274
|
+
if (typeof window !== 'undefined') {
|
|
52275
|
+
window.addEventListener('focus', this.onWindowFocus);
|
|
52276
|
+
}
|
|
52277
|
+
}
|
|
52278
|
+
/**
|
|
52279
|
+
* Registra el pull-to-refresh estándar. Aunque esta vista no muestra una
|
|
52280
|
+
* lista remota, sí refleja estado mutable del browser (permiso, token,
|
|
52281
|
+
* soporte FCM) que puede cambiar fuera de la app — el refresh re-evalúa todo.
|
|
52282
|
+
*
|
|
52283
|
+
* Se usa `ngOnInit`/`ngOnDestroy` (NO `ionViewWillEnter`) porque
|
|
52284
|
+
* `val-page-wrapper` usa `<router-outlet>` plano — los hooks `ionView*` solo
|
|
52285
|
+
* disparan dentro de `<ion-router-outlet>`.
|
|
52286
|
+
*/
|
|
52287
|
+
ngOnInit() {
|
|
52288
|
+
this.pageRefresh.register(() => this.refreshBrowserState());
|
|
52289
|
+
}
|
|
52290
|
+
ngOnDestroy() {
|
|
52291
|
+
this.pageRefresh.unregister();
|
|
52292
|
+
if (typeof window !== 'undefined') {
|
|
52293
|
+
window.removeEventListener('focus', this.onWindowFocus);
|
|
52294
|
+
}
|
|
52295
|
+
if (this.focusDebounceTimer !== null) {
|
|
52296
|
+
clearTimeout(this.focusDebounceTimer);
|
|
52297
|
+
this.focusDebounceTimer = null;
|
|
52298
|
+
}
|
|
52299
|
+
}
|
|
52300
|
+
/** Refresca el estado del browser con debounce — evita ráfagas de `focus`. */
|
|
52301
|
+
scheduleBrowserRefresh() {
|
|
52302
|
+
if (this.focusDebounceTimer !== null) {
|
|
52303
|
+
clearTimeout(this.focusDebounceTimer);
|
|
52304
|
+
}
|
|
52305
|
+
this.focusDebounceTimer = setTimeout(() => {
|
|
52306
|
+
this.focusDebounceTimer = null;
|
|
52307
|
+
void this.refreshBrowserState();
|
|
52308
|
+
}, this.FOCUS_DEBOUNCE_MS);
|
|
52309
|
+
}
|
|
52310
|
+
// ---------------------------------------------------------------------------
|
|
52311
|
+
// Actions
|
|
52312
|
+
// ---------------------------------------------------------------------------
|
|
52313
|
+
/**
|
|
52314
|
+
* Secuencia completa de activación.
|
|
52315
|
+
*
|
|
52316
|
+
* Toda la mecánica (permission → SW ready → getToken con retry → watchdog de
|
|
52317
|
+
* auto-reload) vive en `MessagingService.enable()` de la lib. El paso de
|
|
52318
|
+
* registro del device en el backend se pasa como callback (`registerDevice`)
|
|
52319
|
+
* porque la lib no puede depender de `AuthService` sin crear un ciclo.
|
|
52320
|
+
*
|
|
52321
|
+
* `enable()` no hace throw: resuelve con un `EnablePushResult` descriptivo.
|
|
52322
|
+
*/
|
|
52323
|
+
async onEnable() {
|
|
52324
|
+
this._busy.set('enabling');
|
|
52325
|
+
this._lastFailed.set(false);
|
|
52326
|
+
try {
|
|
52327
|
+
const result = await this.messaging.enable({
|
|
52328
|
+
registerDevice: token => this.auth.registerDevice(token).then(r => r.registered),
|
|
52329
|
+
});
|
|
52330
|
+
if (result.status === 'enabled') {
|
|
52331
|
+
// Persistir flag master si aún no estaba activo.
|
|
52332
|
+
if (!this.prefs.notificationsMaster()) {
|
|
52333
|
+
await this.prefs.setNotificationsMaster(true);
|
|
52334
|
+
}
|
|
52335
|
+
this.toast.show({
|
|
52336
|
+
message: this.tt('enableSuccess'),
|
|
52337
|
+
duration: 2000,
|
|
52338
|
+
color: 'dark',
|
|
52339
|
+
position: 'top',
|
|
52340
|
+
});
|
|
52341
|
+
this.resolvedConfig().onPushEnabled?.();
|
|
52342
|
+
}
|
|
52343
|
+
else if (result.status === 'timeout' && result.reloaded) {
|
|
52344
|
+
// La lib disparó el auto-reload — la página está por recargarse.
|
|
52345
|
+
return;
|
|
52346
|
+
}
|
|
52347
|
+
else {
|
|
52348
|
+
// denied / unsupported / error / timeout sin reload → fallo recuperable.
|
|
52349
|
+
this._lastFailed.set(true);
|
|
52350
|
+
this.toast.show({
|
|
52351
|
+
message: this.tt('errorGeneric'),
|
|
52352
|
+
duration: 3000,
|
|
52353
|
+
color: 'dark',
|
|
52354
|
+
position: 'top',
|
|
52355
|
+
});
|
|
52356
|
+
}
|
|
52357
|
+
}
|
|
52358
|
+
catch {
|
|
52359
|
+
// `enable()` no hace throw, pero el callback de registro sí puede.
|
|
52360
|
+
this._lastFailed.set(true);
|
|
52361
|
+
this.toast.show({
|
|
52362
|
+
message: this.tt('errorGeneric'),
|
|
52363
|
+
duration: 3000,
|
|
52364
|
+
color: 'dark',
|
|
52365
|
+
position: 'top',
|
|
52366
|
+
});
|
|
52367
|
+
}
|
|
52368
|
+
finally {
|
|
52369
|
+
// Único refresh final: re-alinea el toggle UNA sola vez con el estado real.
|
|
52370
|
+
await this.refreshBrowserState();
|
|
52371
|
+
this._busy.set(null);
|
|
52372
|
+
}
|
|
52373
|
+
}
|
|
52374
|
+
async onDisable() {
|
|
52375
|
+
this._busy.set('disabling');
|
|
52376
|
+
this._lastFailed.set(false);
|
|
52377
|
+
try {
|
|
52378
|
+
await this.auth.disableNotifications();
|
|
52379
|
+
if (this.prefs.notificationsMaster()) {
|
|
52380
|
+
await this.prefs.setNotificationsMaster(false);
|
|
52381
|
+
}
|
|
52382
|
+
this.toast.show({
|
|
52383
|
+
message: this.tt('disableSuccess'),
|
|
52384
|
+
duration: 2000,
|
|
52385
|
+
color: 'dark',
|
|
52386
|
+
position: 'top',
|
|
52387
|
+
});
|
|
52388
|
+
this.resolvedConfig().onPushDisabled?.();
|
|
52389
|
+
}
|
|
52390
|
+
catch {
|
|
52391
|
+
this._lastFailed.set(true);
|
|
52392
|
+
}
|
|
52393
|
+
finally {
|
|
52394
|
+
await this.refreshBrowserState();
|
|
52395
|
+
this._busy.set(null);
|
|
52396
|
+
}
|
|
52397
|
+
}
|
|
52398
|
+
/** Reintentar la secuencia completa de activación, sin importar el estado actual. */
|
|
52399
|
+
async onRetry() {
|
|
52400
|
+
this._lastFailed.set(false);
|
|
52401
|
+
await this.onEnable();
|
|
52402
|
+
}
|
|
52403
|
+
toggleDetails() {
|
|
52404
|
+
this.showDetails.update(v => !v);
|
|
52405
|
+
}
|
|
52406
|
+
// ---------------------------------------------------------------------------
|
|
52407
|
+
// Privados
|
|
52408
|
+
// ---------------------------------------------------------------------------
|
|
52409
|
+
async refreshBrowserState() {
|
|
52410
|
+
this.isSupported.set(await this.messaging.isSupported());
|
|
52411
|
+
this.permission.set(this.auth.getNotificationPermissionState());
|
|
52412
|
+
this.currentToken.set(this.messaging.currentToken ?? null);
|
|
52413
|
+
void this.repairTokenIfNeeded();
|
|
52414
|
+
}
|
|
52415
|
+
/**
|
|
52416
|
+
* Repara el token FCM en background.
|
|
52417
|
+
*
|
|
52418
|
+
* Si el usuario tiene push activo (`master && granted`) pero no hay token en
|
|
52419
|
+
* memoria — caso típico tras un cold start de la PWA — re-obtenemos el token
|
|
52420
|
+
* y re-registramos el device sin tocar el toggle ni mostrar un error
|
|
52421
|
+
* alarmista. El token es un detalle de implementación reparable, no parte del
|
|
52422
|
+
* estado de UI (ver `isEnabled`).
|
|
52423
|
+
*/
|
|
52424
|
+
async repairTokenIfNeeded() {
|
|
52425
|
+
if (this._busy() !== null)
|
|
52426
|
+
return;
|
|
52427
|
+
if (this._repairingToken)
|
|
52428
|
+
return;
|
|
52429
|
+
if (!this.isEnabled())
|
|
52430
|
+
return;
|
|
52431
|
+
if (this.currentToken())
|
|
52432
|
+
return;
|
|
52433
|
+
this._repairingToken = true;
|
|
52434
|
+
try {
|
|
52435
|
+
const token = await this.messaging.getToken();
|
|
52436
|
+
if (token) {
|
|
52437
|
+
this.currentToken.set(token);
|
|
52438
|
+
await this.auth.registerDevice(token);
|
|
52439
|
+
}
|
|
52440
|
+
}
|
|
52441
|
+
catch {
|
|
52442
|
+
// Reparación best-effort: no degradamos el toggle ni mostramos error.
|
|
52443
|
+
}
|
|
52444
|
+
finally {
|
|
52445
|
+
this._repairingToken = false;
|
|
52446
|
+
}
|
|
52447
|
+
}
|
|
52448
|
+
/**
|
|
52449
|
+
* iOS browser sin estar instalado como PWA — push web no disponible.
|
|
52450
|
+
* Aplica a Safari y a Edge/Chrome/Firefox en iOS (todos WebKit por mandato Apple).
|
|
52451
|
+
*/
|
|
52452
|
+
detectNeedsIOSInstall() {
|
|
52453
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
52454
|
+
return false;
|
|
52455
|
+
}
|
|
52456
|
+
const ua = navigator.userAgent || '';
|
|
52457
|
+
const isIOS = /iPad|iPhone|iPod/.test(ua);
|
|
52458
|
+
if (!isIOS)
|
|
52459
|
+
return false;
|
|
52460
|
+
const isStandalone = window.matchMedia?.('(display-mode: standalone)').matches ||
|
|
52461
|
+
navigator.standalone === true;
|
|
52462
|
+
return !isStandalone;
|
|
52463
|
+
}
|
|
52464
|
+
tt(key) {
|
|
52465
|
+
return this.i18n.t(key, this.ns);
|
|
52466
|
+
}
|
|
52467
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationPreferencesViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
52468
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: NotificationPreferencesViewComponent, isStandalone: true, selector: "val-notification-preferences-view", inputs: { config: "config" }, ngImport: i0, template: `
|
|
52469
|
+
<div class="page">
|
|
52470
|
+
<header class="page-header">
|
|
52471
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
|
|
52472
|
+
<val-title
|
|
52473
|
+
[props]="{
|
|
52474
|
+
size: 'large',
|
|
52475
|
+
color: 'dark',
|
|
52476
|
+
bold: false,
|
|
52477
|
+
content: pageDescription(),
|
|
52478
|
+
}"
|
|
52479
|
+
/>
|
|
52480
|
+
</header>
|
|
52481
|
+
|
|
52482
|
+
@if (state() === 'needs-install') {
|
|
52483
|
+
<section class="settings-section">
|
|
52484
|
+
<val-title
|
|
52485
|
+
[props]="{
|
|
52486
|
+
size: 'medium',
|
|
52487
|
+
color: 'dark',
|
|
52488
|
+
bold: true,
|
|
52489
|
+
content: tt('iosInstallTitle'),
|
|
52490
|
+
}"
|
|
52491
|
+
/>
|
|
52492
|
+
<div class="section-body">
|
|
52493
|
+
<val-text
|
|
52494
|
+
[props]="{
|
|
52495
|
+
size: 'small',
|
|
52496
|
+
color: 'medium',
|
|
52497
|
+
bold: false,
|
|
52498
|
+
content: tt('iosInstallHint'),
|
|
52499
|
+
}"
|
|
52500
|
+
/>
|
|
52501
|
+
<ol class="install-steps">
|
|
52502
|
+
<li>{{ tt('iosInstallStep1') }}</li>
|
|
52503
|
+
<li>{{ tt('iosInstallStep2') }}</li>
|
|
52504
|
+
<li>{{ tt('iosInstallStep3') }}</li>
|
|
52505
|
+
<li>{{ tt('iosInstallStep4') }}</li>
|
|
52506
|
+
</ol>
|
|
52507
|
+
</div>
|
|
52508
|
+
</section>
|
|
52509
|
+
}
|
|
52510
|
+
|
|
52511
|
+
<section class="settings-section">
|
|
52512
|
+
<div class="notif-row">
|
|
52513
|
+
<div class="notif-row__icon">
|
|
52514
|
+
<ion-icon name="notifications-outline" aria-hidden="true" />
|
|
52515
|
+
</div>
|
|
52516
|
+
<div class="notif-row__body">
|
|
52517
|
+
<val-title
|
|
52518
|
+
[props]="{
|
|
52519
|
+
size: 'medium',
|
|
52520
|
+
color: 'dark',
|
|
52521
|
+
bold: true,
|
|
52522
|
+
content: tt('simpleTitle'),
|
|
52523
|
+
}"
|
|
52524
|
+
/>
|
|
52525
|
+
<val-text
|
|
52526
|
+
[props]="{
|
|
52527
|
+
size: 'small',
|
|
52528
|
+
color: 'medium',
|
|
52529
|
+
bold: false,
|
|
52530
|
+
content: tt('simpleDescription'),
|
|
52531
|
+
}"
|
|
52532
|
+
/>
|
|
52533
|
+
@if (busyLabel(); as label) {
|
|
52534
|
+
<span class="notif-row__busy">{{ label }}</span>
|
|
52535
|
+
}
|
|
52536
|
+
</div>
|
|
52537
|
+
<div class="notif-row__control">
|
|
52538
|
+
<val-toggle-input [props]="toggleProps()" />
|
|
52539
|
+
</div>
|
|
52540
|
+
</div>
|
|
52541
|
+
|
|
52542
|
+
@if (contextMessage(); as msg) {
|
|
52543
|
+
<div class="alert" [class]="'alert--' + alertVariant()">
|
|
52544
|
+
<ion-icon [name]="alertIcon()" aria-hidden="true" />
|
|
52545
|
+
<span>{{ msg }}</span>
|
|
52546
|
+
</div>
|
|
52547
|
+
}
|
|
52548
|
+
|
|
52549
|
+
@if (state() === 'error') {
|
|
52550
|
+
<div class="row-actions">
|
|
52551
|
+
<val-button [props]="retryButtonProps()" (click)="onRetry()" />
|
|
52552
|
+
</div>
|
|
52553
|
+
}
|
|
52554
|
+
</section>
|
|
52555
|
+
|
|
52556
|
+
@if (resolvedConfig().showDetails && state() !== 'needs-install' && state() !== 'unsupported') {
|
|
52557
|
+
<section class="settings-section">
|
|
52558
|
+
<button class="details-toggle" type="button" (click)="toggleDetails()">
|
|
52559
|
+
<span>{{ showDetails() ? tt('detailsHide') : tt('detailsShow') }}</span>
|
|
52560
|
+
<ion-icon [name]="showDetails() ? 'chevron-up-outline' : 'chevron-down-outline'" aria-hidden="true" />
|
|
52561
|
+
</button>
|
|
52562
|
+
@if (showDetails()) {
|
|
52563
|
+
<dl class="details">
|
|
52564
|
+
<div class="details__row">
|
|
52565
|
+
<dt>{{ tt('detailsToken') }}</dt>
|
|
52566
|
+
<dd>{{ tokenPreview() ?? tt('detailsNone') }}</dd>
|
|
52567
|
+
</div>
|
|
52568
|
+
<div class="details__row">
|
|
52569
|
+
<dt>{{ tt('diagnosticsPermission') }}</dt>
|
|
52570
|
+
<dd>{{ permission() }}</dd>
|
|
52571
|
+
</div>
|
|
52572
|
+
<div class="details__row">
|
|
52573
|
+
<dt>{{ tt('diagnosticsSupported') }}</dt>
|
|
52574
|
+
<dd>{{ isSupported() ? '✓' : '✗' }}</dd>
|
|
52575
|
+
</div>
|
|
52576
|
+
</dl>
|
|
52577
|
+
}
|
|
52578
|
+
</section>
|
|
52579
|
+
}
|
|
52580
|
+
</div>
|
|
52581
|
+
`, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:8px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.section-body{margin-top:16px;display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.notif-row{display:grid;grid-template-columns:44px 1fr auto;gap:16px;align-items:center}.notif-row__icon{width:44px;height:44px;border-radius:50%;background:var(--ion-color-light, rgba(0, 0, 0, .06));display:flex;align-items:center;justify-content:center}.notif-row__icon ion-icon{font-size:22px;color:var(--ion-color-dark, #1a1a1a)}.notif-row__body{display:flex;flex-direction:column;gap:4px;min-width:0}.notif-row__busy{font-size:12px;color:var(--ion-color-medium, #92949c);font-style:italic;margin-top:4px}.notif-row__control{flex-shrink:0}.alert{display:flex;gap:10px;align-items:flex-start;margin-top:12px;padding:12px 14px;border-radius:10px;font-size:13px;line-height:1.45;background:var(--ion-color-light, rgba(0, 0, 0, .04));color:var(--ion-color-dark, #1a1a1a)}.alert ion-icon{font-size:18px;flex-shrink:0;margin-top:1px}.alert--warning ion-icon{color:var(--ion-color-warning-shade, #c79e08)}.alert--danger ion-icon{color:var(--ion-color-danger-shade, #cf3c4f)}.alert--info ion-icon{color:var(--ion-color-medium, #6b6b6b)}.install-steps{margin:0;padding-left:20px;font-size:13px;line-height:1.6;color:var(--ion-color-dark, #1a1a1a)}.install-steps li+li{margin-top:4px}.details-toggle{display:flex;align-items:center;gap:6px;background:none;border:0;padding:0;font-size:13px;color:var(--ion-color-medium, #6b6b6b);cursor:pointer}.details-toggle ion-icon{font-size:14px}.details{margin:12px 0 0;display:flex;flex-direction:column;gap:6px}.details__row{display:grid;grid-template-columns:140px 1fr;gap:12px;font-size:12px;align-items:baseline}.details__row dt{margin:0;color:var(--ion-color-medium, #6b6b6b);text-transform:uppercase;letter-spacing:.4px;font-weight:500}.details__row dd{margin:0;color:var(--ion-color-dark, #1a1a1a);font-family:ui-monospace,SFMono-Regular,monospace;font-size:11px;word-break:break-all}\n"], dependencies: [{ kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: ToggleInputComponent, selector: "val-toggle-input", inputs: ["preset", "props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }] }); }
|
|
52582
|
+
}
|
|
52583
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationPreferencesViewComponent, decorators: [{
|
|
52584
|
+
type: Component,
|
|
52585
|
+
args: [{ selector: 'val-notification-preferences-view', standalone: true, imports: [IonIcon, ToggleInputComponent, ButtonComponent, DisplayComponent, TitleComponent, TextComponent], template: `
|
|
52586
|
+
<div class="page">
|
|
52587
|
+
<header class="page-header">
|
|
52588
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
|
|
52589
|
+
<val-title
|
|
52590
|
+
[props]="{
|
|
52591
|
+
size: 'large',
|
|
52592
|
+
color: 'dark',
|
|
52593
|
+
bold: false,
|
|
52594
|
+
content: pageDescription(),
|
|
52595
|
+
}"
|
|
52596
|
+
/>
|
|
52597
|
+
</header>
|
|
52598
|
+
|
|
52599
|
+
@if (state() === 'needs-install') {
|
|
52600
|
+
<section class="settings-section">
|
|
52601
|
+
<val-title
|
|
52602
|
+
[props]="{
|
|
52603
|
+
size: 'medium',
|
|
52604
|
+
color: 'dark',
|
|
52605
|
+
bold: true,
|
|
52606
|
+
content: tt('iosInstallTitle'),
|
|
52607
|
+
}"
|
|
52608
|
+
/>
|
|
52609
|
+
<div class="section-body">
|
|
52610
|
+
<val-text
|
|
52611
|
+
[props]="{
|
|
52612
|
+
size: 'small',
|
|
52613
|
+
color: 'medium',
|
|
52614
|
+
bold: false,
|
|
52615
|
+
content: tt('iosInstallHint'),
|
|
52616
|
+
}"
|
|
52617
|
+
/>
|
|
52618
|
+
<ol class="install-steps">
|
|
52619
|
+
<li>{{ tt('iosInstallStep1') }}</li>
|
|
52620
|
+
<li>{{ tt('iosInstallStep2') }}</li>
|
|
52621
|
+
<li>{{ tt('iosInstallStep3') }}</li>
|
|
52622
|
+
<li>{{ tt('iosInstallStep4') }}</li>
|
|
52623
|
+
</ol>
|
|
52624
|
+
</div>
|
|
52625
|
+
</section>
|
|
52626
|
+
}
|
|
52627
|
+
|
|
52628
|
+
<section class="settings-section">
|
|
52629
|
+
<div class="notif-row">
|
|
52630
|
+
<div class="notif-row__icon">
|
|
52631
|
+
<ion-icon name="notifications-outline" aria-hidden="true" />
|
|
52632
|
+
</div>
|
|
52633
|
+
<div class="notif-row__body">
|
|
52634
|
+
<val-title
|
|
52635
|
+
[props]="{
|
|
52636
|
+
size: 'medium',
|
|
52637
|
+
color: 'dark',
|
|
52638
|
+
bold: true,
|
|
52639
|
+
content: tt('simpleTitle'),
|
|
52640
|
+
}"
|
|
52641
|
+
/>
|
|
52642
|
+
<val-text
|
|
52643
|
+
[props]="{
|
|
52644
|
+
size: 'small',
|
|
52645
|
+
color: 'medium',
|
|
52646
|
+
bold: false,
|
|
52647
|
+
content: tt('simpleDescription'),
|
|
52648
|
+
}"
|
|
52649
|
+
/>
|
|
52650
|
+
@if (busyLabel(); as label) {
|
|
52651
|
+
<span class="notif-row__busy">{{ label }}</span>
|
|
52652
|
+
}
|
|
52653
|
+
</div>
|
|
52654
|
+
<div class="notif-row__control">
|
|
52655
|
+
<val-toggle-input [props]="toggleProps()" />
|
|
52656
|
+
</div>
|
|
52657
|
+
</div>
|
|
52658
|
+
|
|
52659
|
+
@if (contextMessage(); as msg) {
|
|
52660
|
+
<div class="alert" [class]="'alert--' + alertVariant()">
|
|
52661
|
+
<ion-icon [name]="alertIcon()" aria-hidden="true" />
|
|
52662
|
+
<span>{{ msg }}</span>
|
|
52663
|
+
</div>
|
|
52664
|
+
}
|
|
52665
|
+
|
|
52666
|
+
@if (state() === 'error') {
|
|
52667
|
+
<div class="row-actions">
|
|
52668
|
+
<val-button [props]="retryButtonProps()" (click)="onRetry()" />
|
|
52669
|
+
</div>
|
|
52670
|
+
}
|
|
52671
|
+
</section>
|
|
52672
|
+
|
|
52673
|
+
@if (resolvedConfig().showDetails && state() !== 'needs-install' && state() !== 'unsupported') {
|
|
52674
|
+
<section class="settings-section">
|
|
52675
|
+
<button class="details-toggle" type="button" (click)="toggleDetails()">
|
|
52676
|
+
<span>{{ showDetails() ? tt('detailsHide') : tt('detailsShow') }}</span>
|
|
52677
|
+
<ion-icon [name]="showDetails() ? 'chevron-up-outline' : 'chevron-down-outline'" aria-hidden="true" />
|
|
52678
|
+
</button>
|
|
52679
|
+
@if (showDetails()) {
|
|
52680
|
+
<dl class="details">
|
|
52681
|
+
<div class="details__row">
|
|
52682
|
+
<dt>{{ tt('detailsToken') }}</dt>
|
|
52683
|
+
<dd>{{ tokenPreview() ?? tt('detailsNone') }}</dd>
|
|
52684
|
+
</div>
|
|
52685
|
+
<div class="details__row">
|
|
52686
|
+
<dt>{{ tt('diagnosticsPermission') }}</dt>
|
|
52687
|
+
<dd>{{ permission() }}</dd>
|
|
52688
|
+
</div>
|
|
52689
|
+
<div class="details__row">
|
|
52690
|
+
<dt>{{ tt('diagnosticsSupported') }}</dt>
|
|
52691
|
+
<dd>{{ isSupported() ? '✓' : '✗' }}</dd>
|
|
52692
|
+
</div>
|
|
52693
|
+
</dl>
|
|
52694
|
+
}
|
|
52695
|
+
</section>
|
|
52696
|
+
}
|
|
52697
|
+
</div>
|
|
52698
|
+
`, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:8px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.section-body{margin-top:16px;display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.notif-row{display:grid;grid-template-columns:44px 1fr auto;gap:16px;align-items:center}.notif-row__icon{width:44px;height:44px;border-radius:50%;background:var(--ion-color-light, rgba(0, 0, 0, .06));display:flex;align-items:center;justify-content:center}.notif-row__icon ion-icon{font-size:22px;color:var(--ion-color-dark, #1a1a1a)}.notif-row__body{display:flex;flex-direction:column;gap:4px;min-width:0}.notif-row__busy{font-size:12px;color:var(--ion-color-medium, #92949c);font-style:italic;margin-top:4px}.notif-row__control{flex-shrink:0}.alert{display:flex;gap:10px;align-items:flex-start;margin-top:12px;padding:12px 14px;border-radius:10px;font-size:13px;line-height:1.45;background:var(--ion-color-light, rgba(0, 0, 0, .04));color:var(--ion-color-dark, #1a1a1a)}.alert ion-icon{font-size:18px;flex-shrink:0;margin-top:1px}.alert--warning ion-icon{color:var(--ion-color-warning-shade, #c79e08)}.alert--danger ion-icon{color:var(--ion-color-danger-shade, #cf3c4f)}.alert--info ion-icon{color:var(--ion-color-medium, #6b6b6b)}.install-steps{margin:0;padding-left:20px;font-size:13px;line-height:1.6;color:var(--ion-color-dark, #1a1a1a)}.install-steps li+li{margin-top:4px}.details-toggle{display:flex;align-items:center;gap:6px;background:none;border:0;padding:0;font-size:13px;color:var(--ion-color-medium, #6b6b6b);cursor:pointer}.details-toggle ion-icon{font-size:14px}.details{margin:12px 0 0;display:flex;flex-direction:column;gap:6px}.details__row{display:grid;grid-template-columns:140px 1fr;gap:12px;font-size:12px;align-items:baseline}.details__row dt{margin:0;color:var(--ion-color-medium, #6b6b6b);text-transform:uppercase;letter-spacing:.4px;font-weight:500}.details__row dd{margin:0;color:var(--ion-color-dark, #1a1a1a);font-family:ui-monospace,SFMono-Regular,monospace;font-size:11px;word-break:break-all}\n"] }]
|
|
52699
|
+
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
52700
|
+
type: Input
|
|
52701
|
+
}] } });
|
|
52702
|
+
|
|
52703
|
+
/**
|
|
52704
|
+
* Helper para montar las **preferencias de notificaciones push**
|
|
52705
|
+
* (`val-notification-preferences-view`) como ruta en una app del factory. El
|
|
52706
|
+
* `config` se pasa por route `data` (`notificationPreferencesConfig`) y el
|
|
52707
|
+
* componente lo lee como fallback de su `@Input() config`.
|
|
52708
|
+
*
|
|
52709
|
+
* No confundir con `provideValtechNotificationsRoutes` (el inbox / feed).
|
|
52710
|
+
*
|
|
52711
|
+
* @example
|
|
52712
|
+
* ```ts
|
|
52713
|
+
* // app.routes.ts (típicamente bajo settings)
|
|
52714
|
+
* export const settingsRoutes: Routes = [
|
|
52715
|
+
* ...provideValtechNotificationPreferencesRoutes(), // → notifications
|
|
52716
|
+
* ];
|
|
52717
|
+
*
|
|
52718
|
+
* // con config acotada:
|
|
52719
|
+
* ...provideValtechNotificationPreferencesRoutes({ config: { showDetails: false } }),
|
|
52720
|
+
* ...provideValtechNotificationPreferencesRoutes({ path: 'push' }),
|
|
52721
|
+
* ```
|
|
52722
|
+
*/
|
|
52723
|
+
function provideValtechNotificationPreferencesRoutes(opts) {
|
|
52724
|
+
return [
|
|
52725
|
+
{
|
|
52726
|
+
path: opts?.path ?? 'notifications',
|
|
52727
|
+
component: NotificationPreferencesViewComponent,
|
|
52728
|
+
data: { notificationPreferencesConfig: opts?.config },
|
|
52729
|
+
},
|
|
52730
|
+
];
|
|
52731
|
+
}
|
|
52732
|
+
|
|
52733
|
+
/**
|
|
52734
|
+
* Consume el catálogo de permisos del backend (`GET /org/{orgId}/permissions-catalog`).
|
|
52735
|
+
* El backend es la fuente de verdad de qué permisos existen y de su alcance
|
|
52736
|
+
* (globales de plataforma vs. propios de una app). La respuesta incluye el
|
|
52737
|
+
* `appId` del request para que la vista distinga la app actual.
|
|
52738
|
+
*
|
|
52739
|
+
* Promovido desde `showcase` a la lib bajo el proceso de ADR-021. Usa
|
|
52740
|
+
* `VALTECH_AUTH_CONFIG.apiUrl` (como `OrgService`) en vez del `environment` de la
|
|
52741
|
+
* app — la lib no importa el environment del consumer.
|
|
52742
|
+
*/
|
|
52743
|
+
class PermissionCatalogService {
|
|
52744
|
+
constructor(config, http) {
|
|
52745
|
+
this.config = config;
|
|
52746
|
+
this.http = http;
|
|
52747
|
+
}
|
|
52748
|
+
getCatalog(orgId) {
|
|
52749
|
+
return this.http.get(`${this.config.apiUrl}/org/${orgId}/permissions-catalog`);
|
|
52750
|
+
}
|
|
52751
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionCatalogService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$3.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
52752
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionCatalogService, providedIn: 'root' }); }
|
|
52753
|
+
}
|
|
52754
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionCatalogService, decorators: [{
|
|
52755
|
+
type: Injectable,
|
|
52756
|
+
args: [{ providedIn: 'root' }]
|
|
52757
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
52758
|
+
type: Inject,
|
|
52759
|
+
args: [VALTECH_AUTH_CONFIG]
|
|
52760
|
+
}] }, { type: i1$3.HttpClient }] });
|
|
52761
|
+
|
|
52762
|
+
/**
|
|
52763
|
+
* Defaults i18n (es/en) embebidos en `val-permissions-view`. Auto-registrados en
|
|
52764
|
+
* el constructor del componente si el consumer no proveyó el namespace
|
|
52765
|
+
* (`Settings.Permissions` por default). SOLO incluye las keys que la vista usa.
|
|
52766
|
+
*
|
|
52767
|
+
* Portado EXACTO de `PERMISSIONS_I18N` (showcase) bajo el proceso de ADR-021.
|
|
52768
|
+
*/
|
|
52769
|
+
const PERMISSIONS_VIEW_I18N = {
|
|
52770
|
+
es: {
|
|
52771
|
+
pageTitle: 'Permisos',
|
|
52772
|
+
pageDescription: 'Roles y permisos de esta app en la organización activa',
|
|
52773
|
+
systemRolesTitle: 'Roles del sistema',
|
|
52774
|
+
systemRolesHint: 'Predefinidos por la plataforma. No editables.',
|
|
52775
|
+
customRolesTitle: 'Roles personalizados',
|
|
52776
|
+
customRolesHint: 'Definidos por tu organización.',
|
|
52777
|
+
systemBadge: 'Sistema',
|
|
52778
|
+
noCustomRoles: 'Esta organización no tiene roles personalizados.',
|
|
52779
|
+
noPermissions: 'Sin permisos asignados.',
|
|
52780
|
+
// Grupos de alcance
|
|
52781
|
+
groupApp: 'Permisos de la app',
|
|
52782
|
+
groupPlatform: 'Permisos de plataforma',
|
|
52783
|
+
groupOther: 'Otras apps',
|
|
52784
|
+
// Roles del sistema
|
|
52785
|
+
roleViewer: 'Visor',
|
|
52786
|
+
roleEditor: 'Editor',
|
|
52787
|
+
roleAdmin: 'Administrador',
|
|
52788
|
+
roleSuperAdmin: 'Super Administrador',
|
|
52789
|
+
roleBingoAdmin: 'Bingo · Administrador',
|
|
52790
|
+
roleBingoTesorero: 'Bingo · Tesorero',
|
|
52791
|
+
roleBingoTaquilla: 'Bingo · Taquilla',
|
|
52792
|
+
roleBingoOperator: 'Bingo · Operador',
|
|
52793
|
+
// Apps
|
|
52794
|
+
app_showcase: 'Showcase',
|
|
52795
|
+
app_bingo: 'Bingo',
|
|
52796
|
+
// Recursos
|
|
52797
|
+
permUsers: 'Usuarios',
|
|
52798
|
+
permRoles: 'Roles',
|
|
52799
|
+
permRbac: 'Control de acceso',
|
|
52800
|
+
permApps: 'Apps',
|
|
52801
|
+
'permApi-keys': 'API keys',
|
|
52802
|
+
'permEmail-logs': 'Logs de correo',
|
|
52803
|
+
'permSms-logs': 'Logs de SMS',
|
|
52804
|
+
permDocuments: 'Documentos',
|
|
52805
|
+
permMedia: 'Media',
|
|
52806
|
+
permTemplates: 'Plantillas',
|
|
52807
|
+
permRequests: 'Solicitudes',
|
|
52808
|
+
permBingo: 'Bingo',
|
|
52809
|
+
// Acciones
|
|
52810
|
+
permRead: 'Leer',
|
|
52811
|
+
permWrite: 'Escribir',
|
|
52812
|
+
permCreate: 'Crear',
|
|
52813
|
+
permDelete: 'Eliminar',
|
|
52814
|
+
permManage: 'Gestionar',
|
|
52815
|
+
permEdit: 'Editar',
|
|
52816
|
+
permSell: 'Vender',
|
|
52817
|
+
permCheckin: 'Canjear',
|
|
52818
|
+
permSettle: 'Rendir',
|
|
52819
|
+
permExport: 'Exportar',
|
|
52820
|
+
permOperate: 'Operar',
|
|
52821
|
+
permAll: 'Todo',
|
|
52822
|
+
// Loading / error
|
|
52823
|
+
errorTitle: 'Error al cargar permisos',
|
|
52824
|
+
retry: 'Reintentar',
|
|
52825
|
+
},
|
|
52826
|
+
en: {
|
|
52827
|
+
pageTitle: 'Permissions',
|
|
52828
|
+
pageDescription: 'Roles and permissions of this app in the active organization',
|
|
52829
|
+
systemRolesTitle: 'System roles',
|
|
52830
|
+
systemRolesHint: 'Defined by the platform. Not editable.',
|
|
52831
|
+
customRolesTitle: 'Custom roles',
|
|
52832
|
+
customRolesHint: 'Defined by your organization.',
|
|
52833
|
+
systemBadge: 'System',
|
|
52834
|
+
noCustomRoles: 'This organization has no custom roles.',
|
|
52835
|
+
noPermissions: 'No permissions assigned.',
|
|
52836
|
+
groupApp: 'App permissions',
|
|
52837
|
+
groupPlatform: 'Platform permissions',
|
|
52838
|
+
groupOther: 'Other apps',
|
|
52839
|
+
roleViewer: 'Viewer',
|
|
52840
|
+
roleEditor: 'Editor',
|
|
52841
|
+
roleAdmin: 'Administrator',
|
|
52842
|
+
roleSuperAdmin: 'Super Administrator',
|
|
52843
|
+
roleBingoAdmin: 'Bingo · Administrator',
|
|
52844
|
+
roleBingoTesorero: 'Bingo · Treasurer',
|
|
52845
|
+
roleBingoTaquilla: 'Bingo · Box office',
|
|
52846
|
+
roleBingoOperator: 'Bingo · Operator',
|
|
52847
|
+
app_showcase: 'Showcase',
|
|
52848
|
+
app_bingo: 'Bingo',
|
|
52849
|
+
permUsers: 'Users',
|
|
52850
|
+
permRoles: 'Roles',
|
|
52851
|
+
permRbac: 'Access control',
|
|
52852
|
+
permApps: 'Apps',
|
|
52853
|
+
'permApi-keys': 'API keys',
|
|
52854
|
+
'permEmail-logs': 'Email logs',
|
|
52855
|
+
'permSms-logs': 'SMS logs',
|
|
52856
|
+
permDocuments: 'Documents',
|
|
52857
|
+
permMedia: 'Media',
|
|
52858
|
+
permTemplates: 'Templates',
|
|
52859
|
+
permRequests: 'Requests',
|
|
52860
|
+
permBingo: 'Bingo',
|
|
52861
|
+
permRead: 'Read',
|
|
52862
|
+
permWrite: 'Write',
|
|
52863
|
+
permCreate: 'Create',
|
|
52864
|
+
permDelete: 'Delete',
|
|
52865
|
+
permManage: 'Manage',
|
|
52866
|
+
permEdit: 'Edit',
|
|
52867
|
+
permSell: 'Sell',
|
|
52868
|
+
permCheckin: 'Check-in',
|
|
52869
|
+
permSettle: 'Settle',
|
|
52870
|
+
permExport: 'Export',
|
|
52871
|
+
permOperate: 'Operate',
|
|
52872
|
+
permAll: 'All',
|
|
52873
|
+
errorTitle: 'Error loading permissions',
|
|
52874
|
+
retry: 'Retry',
|
|
52875
|
+
},
|
|
52876
|
+
};
|
|
52877
|
+
|
|
52878
|
+
const DEFAULT_NAMESPACE = 'Settings.Permissions';
|
|
52879
|
+
/**
|
|
52880
|
+
* `val-permissions-view` — vista Permisos full-feature autocontenida (organism).
|
|
52881
|
+
* Visor RBAC de solo lectura: muestra los roles del sistema (Visor/Editor/Admin +
|
|
52882
|
+
* roles de plataforma) y los roles personalizados de la org activa, con el
|
|
52883
|
+
* catálogo de permisos agrupado por alcance (app actual / plataforma / otras apps).
|
|
52884
|
+
* Promovida desde `showcase` bajo el proceso de ADR-021.
|
|
52885
|
+
*
|
|
52886
|
+
* **RBAC interno** (como `organization-view`): lee la org activa vía
|
|
52887
|
+
* `AuthService.user().activeOrgId`. La vista NO muta nada — el control de quién
|
|
52888
|
+
* puede verla es responsabilidad del guard de ruta del consumer; no hay gating
|
|
52889
|
+
* fino interno ni se expone como config.
|
|
52890
|
+
*
|
|
52891
|
+
* Datos: `forkJoin` de `OrgService.listOrgRoles(orgId)` (roles de la org) +
|
|
52892
|
+
* `PermissionCatalogService.getCatalog(orgId)` (catálogo + appId actual). El
|
|
52893
|
+
* catálogo define el alcance (`scope`/`appId`) de cada recurso para agrupar.
|
|
52894
|
+
*
|
|
52895
|
+
* NO renderiza ion-content — vive dentro de val-page-wrapper. `ActivatedRoute` se
|
|
52896
|
+
* inyecta `{ optional: true }` solo para leer el route data de config. Auto-registra
|
|
52897
|
+
* sus defaults i18n (es/en) si el consumer no proveyó el namespace (default
|
|
52898
|
+
* `Settings.Permissions`).
|
|
52899
|
+
*/
|
|
52900
|
+
class PermissionsViewComponent {
|
|
52901
|
+
/** Namespace i18n resuelto (capturado para llamadas no-reactivas). */
|
|
52902
|
+
get ns() {
|
|
52903
|
+
return this.resolvedConfig().i18nNamespace;
|
|
52904
|
+
}
|
|
52905
|
+
constructor() {
|
|
52906
|
+
this.i18n = inject(I18nService);
|
|
52907
|
+
this.orgService = inject(OrgService);
|
|
52908
|
+
this.catalog = inject(PermissionCatalogService);
|
|
52909
|
+
this.auth = inject(AuthService);
|
|
52910
|
+
this.nav = inject(NavigationService);
|
|
52911
|
+
this.route = inject(ActivatedRoute, { optional: true });
|
|
52912
|
+
this.resolvedConfig = computed(() => {
|
|
52913
|
+
const fromRoute = (this.route?.snapshot.data['permissionsConfig'] ?? {});
|
|
52914
|
+
const merged = { ...fromRoute, ...(this.config ?? {}) };
|
|
52915
|
+
return {
|
|
52916
|
+
showSystemRoles: merged.showSystemRoles ?? true,
|
|
52917
|
+
showCustomRoles: merged.showCustomRoles ?? true,
|
|
52918
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
52919
|
+
};
|
|
52920
|
+
});
|
|
52921
|
+
this.loading = signal(false);
|
|
52922
|
+
this.loadError = signal(null);
|
|
52923
|
+
this.roles = signal([]);
|
|
52924
|
+
this.systemRoles = computed(() => this.roles().filter(r => r.isSystem));
|
|
52925
|
+
this.customRoles = computed(() => this.roles().filter(r => !r.isSystem));
|
|
52926
|
+
this.activeOrgId = computed(() => {
|
|
52927
|
+
const u = this.auth.user();
|
|
52928
|
+
return u?.activeOrgId ?? u?.activeOrg ?? '';
|
|
52929
|
+
});
|
|
52930
|
+
this.retryButtonProps = computed(() => ({
|
|
52931
|
+
text: this.tt('retry'),
|
|
52932
|
+
color: 'dark',
|
|
52933
|
+
fill: 'outline',
|
|
52934
|
+
size: 'default',
|
|
52935
|
+
type: 'button',
|
|
52936
|
+
state: this.loading() ? ComponentStates.DISABLED : ComponentStates.ENABLED,
|
|
52937
|
+
}));
|
|
52938
|
+
const ns = this.ns;
|
|
52939
|
+
if (!this.i18n.hasNamespace(ns)) {
|
|
52940
|
+
this.i18n.registerContent(ns, PERMISSIONS_VIEW_I18N);
|
|
52941
|
+
}
|
|
52942
|
+
this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
|
|
52943
|
+
this.load();
|
|
52944
|
+
connectPageRefresh(() => this.load());
|
|
52945
|
+
}
|
|
52946
|
+
load() {
|
|
52947
|
+
const orgId = this.activeOrgId();
|
|
52948
|
+
if (!orgId)
|
|
52949
|
+
return;
|
|
52950
|
+
this.loading.set(true);
|
|
52951
|
+
this.loadError.set(null);
|
|
52952
|
+
forkJoin({
|
|
52953
|
+
roles: this.orgService.listOrgRoles(orgId),
|
|
52954
|
+
catalog: this.catalog.getCatalog(orgId),
|
|
52955
|
+
}).subscribe({
|
|
52956
|
+
next: ({ roles: rawRoles, catalog }) => {
|
|
52957
|
+
const byResource = new Map();
|
|
52958
|
+
for (const r of catalog.permissions)
|
|
52959
|
+
byResource.set(r.resource, r);
|
|
52960
|
+
const currentAppId = catalog.appId;
|
|
52961
|
+
this.roles.set((rawRoles ?? []).map((r) => this.buildRoleView({
|
|
52962
|
+
id: r.id,
|
|
52963
|
+
name: r.name,
|
|
52964
|
+
description: r.description,
|
|
52965
|
+
permissions: r.permissions ?? [],
|
|
52966
|
+
isSystem: r.isSystem ?? false,
|
|
52967
|
+
}, byResource, currentAppId)));
|
|
52968
|
+
this.loading.set(false);
|
|
52969
|
+
},
|
|
52970
|
+
error: err => {
|
|
52971
|
+
this.loadError.set(err);
|
|
52972
|
+
this.loading.set(false);
|
|
52973
|
+
},
|
|
52974
|
+
});
|
|
52975
|
+
}
|
|
52976
|
+
buildRoleView(role, byResource, currentAppId) {
|
|
52977
|
+
const appPerms = [];
|
|
52978
|
+
const platformPerms = [];
|
|
52979
|
+
const otherByApp = new Map();
|
|
52980
|
+
for (const perm of role.permissions) {
|
|
52981
|
+
const [resource, action] = perm.split(':');
|
|
52982
|
+
const label = this.permLabel(resource, action);
|
|
52983
|
+
const entry = byResource.get(resource);
|
|
52984
|
+
if (entry?.scope === 'app') {
|
|
52985
|
+
if (entry.appId === currentAppId) {
|
|
52986
|
+
appPerms.push(label);
|
|
52987
|
+
}
|
|
52988
|
+
else {
|
|
52989
|
+
const appId = entry.appId ?? '?';
|
|
52990
|
+
const arr = otherByApp.get(appId) ?? [];
|
|
52991
|
+
arr.push(label);
|
|
52992
|
+
otherByApp.set(appId, arr);
|
|
52993
|
+
}
|
|
52994
|
+
}
|
|
52995
|
+
else {
|
|
52996
|
+
// global o desconocido → plataforma
|
|
52997
|
+
platformPerms.push(label);
|
|
52998
|
+
}
|
|
52999
|
+
}
|
|
53000
|
+
const groups = [];
|
|
53001
|
+
if (appPerms.length) {
|
|
53002
|
+
groups.push({ key: 'app', label: this.tt('groupApp'), perms: appPerms });
|
|
53003
|
+
}
|
|
53004
|
+
if (platformPerms.length) {
|
|
53005
|
+
groups.push({
|
|
53006
|
+
key: 'platform',
|
|
53007
|
+
label: this.tt('groupPlatform'),
|
|
53008
|
+
perms: platformPerms,
|
|
53009
|
+
});
|
|
53010
|
+
}
|
|
53011
|
+
for (const [appId, perms] of otherByApp) {
|
|
53012
|
+
groups.push({
|
|
53013
|
+
key: `other:${appId}`,
|
|
53014
|
+
label: `${this.tt('groupOther')}: ${this.appLabel(appId)}`,
|
|
53015
|
+
perms,
|
|
53016
|
+
});
|
|
53017
|
+
}
|
|
53018
|
+
return {
|
|
53019
|
+
id: role.id,
|
|
53020
|
+
name: role.name,
|
|
53021
|
+
description: role.description,
|
|
53022
|
+
isSystem: role.isSystem,
|
|
53023
|
+
groups,
|
|
53024
|
+
hasPerms: groups.length > 0,
|
|
53025
|
+
};
|
|
53026
|
+
}
|
|
53027
|
+
tt(key) {
|
|
53028
|
+
return this.i18n.t(key, this.ns);
|
|
53029
|
+
}
|
|
53030
|
+
appLabel(appId) {
|
|
53031
|
+
const key = `app_${appId}`;
|
|
53032
|
+
const label = this.tt(key);
|
|
53033
|
+
return label === key ? appId : label;
|
|
53034
|
+
}
|
|
53035
|
+
roleLabel(name) {
|
|
53036
|
+
const map = {
|
|
53037
|
+
viewer: 'roleViewer',
|
|
53038
|
+
editor: 'roleEditor',
|
|
53039
|
+
admin: 'roleAdmin',
|
|
53040
|
+
super_admin: 'roleSuperAdmin',
|
|
53041
|
+
bingo_admin: 'roleBingoAdmin',
|
|
53042
|
+
bingo_tesorero: 'roleBingoTesorero',
|
|
53043
|
+
bingo_taquilla: 'roleBingoTaquilla',
|
|
53044
|
+
bingo_operator: 'roleBingoOperator',
|
|
53045
|
+
};
|
|
53046
|
+
return this.tt(map[name] ?? name);
|
|
53047
|
+
}
|
|
53048
|
+
permLabel(resource, action) {
|
|
53049
|
+
if (resource === '*' && action === '*')
|
|
53050
|
+
return this.tt('permAll');
|
|
53051
|
+
const resKey = resource === '*' ? 'permAll' : `perm${resource.charAt(0).toUpperCase()}${resource.slice(1)}`;
|
|
53052
|
+
const actKey = action === '*' ? 'permAll' : `perm${action.charAt(0).toUpperCase()}${action.slice(1)}`;
|
|
53053
|
+
const res = this.tt(resKey);
|
|
53054
|
+
const act = this.tt(actKey);
|
|
53055
|
+
return `${res}: ${act}`;
|
|
53056
|
+
}
|
|
53057
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionsViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
53058
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: PermissionsViewComponent, isStandalone: true, selector: "val-permissions-view", inputs: { config: "config" }, ngImport: i0, template: `
|
|
53059
|
+
<div class="page">
|
|
53060
|
+
<header class="page-header">
|
|
53061
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: tt('pageTitle') }" />
|
|
53062
|
+
<val-title
|
|
53063
|
+
[props]="{
|
|
53064
|
+
size: 'large',
|
|
53065
|
+
color: 'dark',
|
|
53066
|
+
bold: false,
|
|
53067
|
+
content: tt('pageDescription'),
|
|
53068
|
+
}"
|
|
53069
|
+
/>
|
|
53070
|
+
</header>
|
|
53071
|
+
|
|
53072
|
+
@if (loading()) {
|
|
53073
|
+
<div class="spinner-row">
|
|
53074
|
+
<ion-spinner name="crescent" />
|
|
53075
|
+
</div>
|
|
53076
|
+
} @else if (loadError()) {
|
|
53077
|
+
<div class="error-row">
|
|
53078
|
+
<val-text
|
|
53079
|
+
[props]="{
|
|
53080
|
+
content: tt('errorTitle'),
|
|
53081
|
+
size: 'medium',
|
|
53082
|
+
color: 'medium',
|
|
53083
|
+
bold: false,
|
|
53084
|
+
}"
|
|
53085
|
+
/>
|
|
53086
|
+
<val-button [props]="retryButtonProps()" (click)="load()" />
|
|
53087
|
+
</div>
|
|
53088
|
+
} @else {
|
|
53089
|
+
@if (resolvedConfig().showSystemRoles) {
|
|
53090
|
+
<section class="perm-section">
|
|
53091
|
+
<div class="perm-section__header">
|
|
53092
|
+
<val-title
|
|
53093
|
+
[props]="{
|
|
53094
|
+
size: 'medium',
|
|
53095
|
+
color: 'dark',
|
|
53096
|
+
bold: true,
|
|
53097
|
+
content: tt('systemRolesTitle'),
|
|
53098
|
+
}"
|
|
53099
|
+
/>
|
|
53100
|
+
<val-text
|
|
53101
|
+
[props]="{
|
|
53102
|
+
content: tt('systemRolesHint'),
|
|
53103
|
+
size: 'medium',
|
|
53104
|
+
color: 'medium',
|
|
53105
|
+
bold: false,
|
|
53106
|
+
}"
|
|
53107
|
+
/>
|
|
53108
|
+
</div>
|
|
53109
|
+
<div class="role-list">
|
|
53110
|
+
@for (role of systemRoles(); track role.id) {
|
|
53111
|
+
<div class="role-card">
|
|
53112
|
+
<div class="role-card__header">
|
|
53113
|
+
<span class="role-card__name">{{ roleLabel(role.name) }}</span>
|
|
53114
|
+
<val-pill
|
|
53115
|
+
[props]="{
|
|
53116
|
+
label: tt('systemBadge'),
|
|
53117
|
+
color: 'primary',
|
|
53118
|
+
weight: 'bold',
|
|
53119
|
+
}"
|
|
53120
|
+
/>
|
|
53121
|
+
</div>
|
|
53122
|
+
@if (role.description) {
|
|
53123
|
+
<span class="role-card__desc">{{ role.description }}</span>
|
|
53124
|
+
}
|
|
53125
|
+
@if (!role.hasPerms) {
|
|
53126
|
+
<div class="perm-chips">
|
|
53127
|
+
<span class="perm-chip perm-chip--empty">{{ tt('noPermissions') }}</span>
|
|
53128
|
+
</div>
|
|
53129
|
+
} @else {
|
|
53130
|
+
@for (g of role.groups; track g.key) {
|
|
53131
|
+
<div class="perm-group">
|
|
53132
|
+
<span class="perm-group__label">{{ g.label }}</span>
|
|
53133
|
+
<div class="perm-chips">
|
|
53134
|
+
@for (p of g.perms; track p) {
|
|
53135
|
+
<span class="perm-chip">{{ p }}</span>
|
|
53136
|
+
}
|
|
53137
|
+
</div>
|
|
53138
|
+
</div>
|
|
53139
|
+
}
|
|
53140
|
+
}
|
|
53141
|
+
</div>
|
|
53142
|
+
}
|
|
53143
|
+
</div>
|
|
53144
|
+
</section>
|
|
53145
|
+
}
|
|
53146
|
+
|
|
53147
|
+
@if (resolvedConfig().showCustomRoles) {
|
|
53148
|
+
<section class="perm-section">
|
|
53149
|
+
<div class="perm-section__header">
|
|
53150
|
+
<val-title
|
|
53151
|
+
[props]="{
|
|
53152
|
+
size: 'medium',
|
|
53153
|
+
color: 'dark',
|
|
53154
|
+
bold: true,
|
|
53155
|
+
content: tt('customRolesTitle'),
|
|
53156
|
+
}"
|
|
53157
|
+
/>
|
|
53158
|
+
<val-text
|
|
53159
|
+
[props]="{
|
|
53160
|
+
content: tt('customRolesHint'),
|
|
53161
|
+
size: 'medium',
|
|
53162
|
+
color: 'medium',
|
|
53163
|
+
bold: false,
|
|
53164
|
+
}"
|
|
53165
|
+
/>
|
|
53166
|
+
</div>
|
|
53167
|
+
@if (customRoles().length === 0) {
|
|
53168
|
+
<val-text
|
|
53169
|
+
[props]="{
|
|
53170
|
+
content: tt('noCustomRoles'),
|
|
53171
|
+
size: 'medium',
|
|
53172
|
+
color: 'medium',
|
|
53173
|
+
bold: false,
|
|
53174
|
+
}"
|
|
53175
|
+
/>
|
|
53176
|
+
} @else {
|
|
53177
|
+
<div class="role-list">
|
|
53178
|
+
@for (role of customRoles(); track role.id) {
|
|
53179
|
+
<div class="role-card">
|
|
53180
|
+
<div class="role-card__header">
|
|
53181
|
+
<span class="role-card__name">{{ role.name }}</span>
|
|
53182
|
+
</div>
|
|
53183
|
+
@if (role.description) {
|
|
53184
|
+
<span class="role-card__desc">{{ role.description }}</span>
|
|
53185
|
+
}
|
|
53186
|
+
@if (!role.hasPerms) {
|
|
53187
|
+
<div class="perm-chips">
|
|
53188
|
+
<span class="perm-chip perm-chip--empty">{{ tt('noPermissions') }}</span>
|
|
53189
|
+
</div>
|
|
53190
|
+
} @else {
|
|
53191
|
+
@for (g of role.groups; track g.key) {
|
|
53192
|
+
<div class="perm-group">
|
|
53193
|
+
<span class="perm-group__label">{{ g.label }}</span>
|
|
53194
|
+
<div class="perm-chips">
|
|
53195
|
+
@for (p of g.perms; track p) {
|
|
53196
|
+
<span class="perm-chip">{{ p }}</span>
|
|
53197
|
+
}
|
|
53198
|
+
</div>
|
|
53199
|
+
</div>
|
|
53200
|
+
}
|
|
53201
|
+
}
|
|
53202
|
+
</div>
|
|
53203
|
+
}
|
|
53204
|
+
</div>
|
|
53205
|
+
}
|
|
53206
|
+
</section>
|
|
53207
|
+
}
|
|
53208
|
+
}
|
|
53209
|
+
</div>
|
|
53210
|
+
`, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:24px}.spinner-row,.error-row{display:flex;flex-direction:column;align-items:center;gap:16px;padding:32px 0}.perm-section{margin-bottom:32px}.perm-section__header{margin-bottom:12px}.role-list{display:flex;flex-direction:column;gap:10px}.role-card{border:1.5px solid var(--ion-color-light);border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:10px}.role-card__header{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.role-card__name{font-size:15px;font-weight:600;color:var(--ion-color-dark)}.role-card__desc{font-size:13px;color:var(--ion-color-medium);line-height:1.4}.perm-group{display:flex;flex-direction:column;gap:6px}.perm-group__label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--ion-color-medium)}.perm-chips{display:flex;flex-wrap:wrap;gap:6px}.perm-chip{font-size:12px;padding:3px 10px;border-radius:20px;background:var(--ion-color-light);color:var(--ion-color-dark)}.perm-chip--empty{color:var(--ion-color-medium);background:transparent;padding-left:0}\n"], dependencies: [{ kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: PillComponent, selector: "val-pill", inputs: ["preset", "props"], outputs: ["pillClick", "pillAction"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
|
|
53211
|
+
}
|
|
53212
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionsViewComponent, decorators: [{
|
|
53213
|
+
type: Component,
|
|
53214
|
+
args: [{ selector: 'val-permissions-view', standalone: true, imports: [IonSpinner, DisplayComponent, PillComponent, TitleComponent, TextComponent, ButtonComponent], template: `
|
|
53215
|
+
<div class="page">
|
|
53216
|
+
<header class="page-header">
|
|
53217
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: tt('pageTitle') }" />
|
|
53218
|
+
<val-title
|
|
53219
|
+
[props]="{
|
|
53220
|
+
size: 'large',
|
|
53221
|
+
color: 'dark',
|
|
53222
|
+
bold: false,
|
|
53223
|
+
content: tt('pageDescription'),
|
|
53224
|
+
}"
|
|
53225
|
+
/>
|
|
53226
|
+
</header>
|
|
53227
|
+
|
|
53228
|
+
@if (loading()) {
|
|
53229
|
+
<div class="spinner-row">
|
|
53230
|
+
<ion-spinner name="crescent" />
|
|
53231
|
+
</div>
|
|
53232
|
+
} @else if (loadError()) {
|
|
53233
|
+
<div class="error-row">
|
|
53234
|
+
<val-text
|
|
53235
|
+
[props]="{
|
|
53236
|
+
content: tt('errorTitle'),
|
|
53237
|
+
size: 'medium',
|
|
53238
|
+
color: 'medium',
|
|
53239
|
+
bold: false,
|
|
53240
|
+
}"
|
|
53241
|
+
/>
|
|
53242
|
+
<val-button [props]="retryButtonProps()" (click)="load()" />
|
|
53243
|
+
</div>
|
|
53244
|
+
} @else {
|
|
53245
|
+
@if (resolvedConfig().showSystemRoles) {
|
|
53246
|
+
<section class="perm-section">
|
|
53247
|
+
<div class="perm-section__header">
|
|
53248
|
+
<val-title
|
|
53249
|
+
[props]="{
|
|
53250
|
+
size: 'medium',
|
|
53251
|
+
color: 'dark',
|
|
53252
|
+
bold: true,
|
|
53253
|
+
content: tt('systemRolesTitle'),
|
|
53254
|
+
}"
|
|
53255
|
+
/>
|
|
53256
|
+
<val-text
|
|
53257
|
+
[props]="{
|
|
53258
|
+
content: tt('systemRolesHint'),
|
|
53259
|
+
size: 'medium',
|
|
53260
|
+
color: 'medium',
|
|
53261
|
+
bold: false,
|
|
53262
|
+
}"
|
|
53263
|
+
/>
|
|
53264
|
+
</div>
|
|
53265
|
+
<div class="role-list">
|
|
53266
|
+
@for (role of systemRoles(); track role.id) {
|
|
53267
|
+
<div class="role-card">
|
|
53268
|
+
<div class="role-card__header">
|
|
53269
|
+
<span class="role-card__name">{{ roleLabel(role.name) }}</span>
|
|
53270
|
+
<val-pill
|
|
53271
|
+
[props]="{
|
|
53272
|
+
label: tt('systemBadge'),
|
|
53273
|
+
color: 'primary',
|
|
53274
|
+
weight: 'bold',
|
|
53275
|
+
}"
|
|
53276
|
+
/>
|
|
53277
|
+
</div>
|
|
53278
|
+
@if (role.description) {
|
|
53279
|
+
<span class="role-card__desc">{{ role.description }}</span>
|
|
53280
|
+
}
|
|
53281
|
+
@if (!role.hasPerms) {
|
|
53282
|
+
<div class="perm-chips">
|
|
53283
|
+
<span class="perm-chip perm-chip--empty">{{ tt('noPermissions') }}</span>
|
|
53284
|
+
</div>
|
|
53285
|
+
} @else {
|
|
53286
|
+
@for (g of role.groups; track g.key) {
|
|
53287
|
+
<div class="perm-group">
|
|
53288
|
+
<span class="perm-group__label">{{ g.label }}</span>
|
|
53289
|
+
<div class="perm-chips">
|
|
53290
|
+
@for (p of g.perms; track p) {
|
|
53291
|
+
<span class="perm-chip">{{ p }}</span>
|
|
53292
|
+
}
|
|
53293
|
+
</div>
|
|
53294
|
+
</div>
|
|
53295
|
+
}
|
|
53296
|
+
}
|
|
53297
|
+
</div>
|
|
53298
|
+
}
|
|
53299
|
+
</div>
|
|
53300
|
+
</section>
|
|
53301
|
+
}
|
|
53302
|
+
|
|
53303
|
+
@if (resolvedConfig().showCustomRoles) {
|
|
53304
|
+
<section class="perm-section">
|
|
53305
|
+
<div class="perm-section__header">
|
|
53306
|
+
<val-title
|
|
53307
|
+
[props]="{
|
|
53308
|
+
size: 'medium',
|
|
53309
|
+
color: 'dark',
|
|
53310
|
+
bold: true,
|
|
53311
|
+
content: tt('customRolesTitle'),
|
|
53312
|
+
}"
|
|
53313
|
+
/>
|
|
53314
|
+
<val-text
|
|
53315
|
+
[props]="{
|
|
53316
|
+
content: tt('customRolesHint'),
|
|
53317
|
+
size: 'medium',
|
|
53318
|
+
color: 'medium',
|
|
53319
|
+
bold: false,
|
|
53320
|
+
}"
|
|
53321
|
+
/>
|
|
53322
|
+
</div>
|
|
53323
|
+
@if (customRoles().length === 0) {
|
|
53324
|
+
<val-text
|
|
53325
|
+
[props]="{
|
|
53326
|
+
content: tt('noCustomRoles'),
|
|
53327
|
+
size: 'medium',
|
|
53328
|
+
color: 'medium',
|
|
53329
|
+
bold: false,
|
|
53330
|
+
}"
|
|
53331
|
+
/>
|
|
53332
|
+
} @else {
|
|
53333
|
+
<div class="role-list">
|
|
53334
|
+
@for (role of customRoles(); track role.id) {
|
|
53335
|
+
<div class="role-card">
|
|
53336
|
+
<div class="role-card__header">
|
|
53337
|
+
<span class="role-card__name">{{ role.name }}</span>
|
|
53338
|
+
</div>
|
|
53339
|
+
@if (role.description) {
|
|
53340
|
+
<span class="role-card__desc">{{ role.description }}</span>
|
|
53341
|
+
}
|
|
53342
|
+
@if (!role.hasPerms) {
|
|
53343
|
+
<div class="perm-chips">
|
|
53344
|
+
<span class="perm-chip perm-chip--empty">{{ tt('noPermissions') }}</span>
|
|
53345
|
+
</div>
|
|
53346
|
+
} @else {
|
|
53347
|
+
@for (g of role.groups; track g.key) {
|
|
53348
|
+
<div class="perm-group">
|
|
53349
|
+
<span class="perm-group__label">{{ g.label }}</span>
|
|
53350
|
+
<div class="perm-chips">
|
|
53351
|
+
@for (p of g.perms; track p) {
|
|
53352
|
+
<span class="perm-chip">{{ p }}</span>
|
|
53353
|
+
}
|
|
53354
|
+
</div>
|
|
53355
|
+
</div>
|
|
53356
|
+
}
|
|
53357
|
+
}
|
|
53358
|
+
</div>
|
|
53359
|
+
}
|
|
53360
|
+
</div>
|
|
53361
|
+
}
|
|
53362
|
+
</section>
|
|
53363
|
+
}
|
|
53364
|
+
}
|
|
53365
|
+
</div>
|
|
53366
|
+
`, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:24px}.spinner-row,.error-row{display:flex;flex-direction:column;align-items:center;gap:16px;padding:32px 0}.perm-section{margin-bottom:32px}.perm-section__header{margin-bottom:12px}.role-list{display:flex;flex-direction:column;gap:10px}.role-card{border:1.5px solid var(--ion-color-light);border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:10px}.role-card__header{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.role-card__name{font-size:15px;font-weight:600;color:var(--ion-color-dark)}.role-card__desc{font-size:13px;color:var(--ion-color-medium);line-height:1.4}.perm-group{display:flex;flex-direction:column;gap:6px}.perm-group__label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--ion-color-medium)}.perm-chips{display:flex;flex-wrap:wrap;gap:6px}.perm-chip{font-size:12px;padding:3px 10px;border-radius:20px;background:var(--ion-color-light);color:var(--ion-color-dark)}.perm-chip--empty{color:var(--ion-color-medium);background:transparent;padding-left:0}\n"] }]
|
|
53367
|
+
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
53368
|
+
type: Input
|
|
53369
|
+
}] } });
|
|
53370
|
+
|
|
53371
|
+
/**
|
|
53372
|
+
* Helper para montar la vista Permisos (`val-permissions-view`) como ruta en una
|
|
53373
|
+
* app del factory. El `config` se pasa por route `data` (`permissionsConfig`) y el
|
|
53374
|
+
* componente lo lee como fallback de su `@Input() config`.
|
|
53375
|
+
*
|
|
53376
|
+
* @example
|
|
53377
|
+
* ```ts
|
|
53378
|
+
* // settings.routes.ts
|
|
53379
|
+
* export const settingsRoutes: Routes = [
|
|
53380
|
+
* ...provideValtechPermissionsRoutes(), // → /app/settings/permissions
|
|
53381
|
+
* { path: 'preferences', loadComponent: () => ... },
|
|
53382
|
+
* ];
|
|
53383
|
+
*
|
|
53384
|
+
* // con config acotada:
|
|
53385
|
+
* ...provideValtechPermissionsRoutes({ config: { showCustomRoles: false } }),
|
|
53386
|
+
* ...provideValtechPermissionsRoutes({ path: 'roles' }),
|
|
53387
|
+
* ```
|
|
53388
|
+
*/
|
|
53389
|
+
function provideValtechPermissionsRoutes(opts) {
|
|
53390
|
+
return [
|
|
53391
|
+
{
|
|
53392
|
+
path: opts?.path ?? 'permissions',
|
|
53393
|
+
component: PermissionsViewComponent,
|
|
53394
|
+
data: { permissionsConfig: opts?.config },
|
|
53395
|
+
},
|
|
53396
|
+
];
|
|
53397
|
+
}
|
|
53398
|
+
|
|
51978
53399
|
/** Built-in label sets for the three platform locales. */
|
|
51979
53400
|
const CALLOUT_LABELS = {
|
|
51980
53401
|
es: {
|
|
@@ -62181,5 +63602,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
62181
63602
|
* Generated bundle index. Do not edit.
|
|
62182
63603
|
*/
|
|
62183
63604
|
|
|
62184
|
-
export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, ApiKeyService, ApiKeysModalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MemberImportModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
63605
|
+
export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, ApiKeyService, ApiKeysModalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MemberImportModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PermissionCatalogService, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
62185
63606
|
//# sourceMappingURL=valtech-components.mjs.map
|