valtech-components 2.0.995 → 2.0.997
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/organization-view/organization-view.component.mjs +1 -53
- package/esm2022/lib/components/organisms/organization-view/organization-view.i18n.mjs +1 -3
- 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/auth/auth.service.mjs +1 -14
- package/esm2022/lib/services/auth/types.mjs +1 -1
- package/esm2022/lib/services/org/org.service.mjs +1 -8
- package/esm2022/lib/services/org/permission-catalog.service.mjs +33 -0
- package/esm2022/lib/services/org/types.mjs +1 -1
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +8 -2
- package/fesm2022/valtech-components.mjs +1524 -541
- 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/organization-view/organization-view.component.d.ts +0 -3
- 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/auth/auth.service.d.ts +1 -7
- package/lib/services/auth/types.d.ts +0 -23
- package/lib/services/org/org.service.d.ts +1 -6
- package/lib/services/org/permission-catalog.service.d.ts +34 -0
- package/lib/services/org/types.d.ts +0 -30
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +7 -1
- package/esm2022/lib/components/organisms/member-import-modal/member-import-modal.component.mjs +0 -313
- package/esm2022/lib/components/organisms/member-import-modal/member-import-modal.i18n.mjs +0 -63
- package/lib/components/organisms/member-import-modal/member-import-modal.component.d.ts +0 -47
- package/lib/components/organisms/member-import-modal/member-import-modal.i18n.d.ts +0 -6
|
@@ -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.997';
|
|
58
58
|
|
|
59
59
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
60
60
|
let isRefreshing = false;
|
|
@@ -8658,19 +8658,6 @@ class AuthService {
|
|
|
8658
8658
|
.post(`${this.baseUrl}/reset-password`, request)
|
|
8659
8659
|
.pipe(catchError(error => this.handleAuthError(error)));
|
|
8660
8660
|
}
|
|
8661
|
-
/**
|
|
8662
|
-
* Activa una cuenta pre-aprovisionada por una organización (ADR-023): define
|
|
8663
|
-
* la contraseña con el token del enlace de email. En éxito el backend devuelve
|
|
8664
|
-
* tokens de auto-signin → el usuario queda logueado (mismo flujo que verify-email).
|
|
8665
|
-
*/
|
|
8666
|
-
activateAccount(request) {
|
|
8667
|
-
this.stateService.clearError();
|
|
8668
|
-
return this.http.post(`${this.baseUrl}/activate`, request).pipe(tap(response => {
|
|
8669
|
-
if (response.activated && response.accessToken) {
|
|
8670
|
-
this.handleSuccessfulAuth(response);
|
|
8671
|
-
}
|
|
8672
|
-
}), catchError(error => this.handleAuthError(error)));
|
|
8673
|
-
}
|
|
8674
8661
|
/**
|
|
8675
8662
|
* Cambia la contraseña del usuario autenticado.
|
|
8676
8663
|
* Requiere la contraseña actual para verificación.
|
|
@@ -41750,7 +41737,7 @@ const PROFILE_VIEW_I18N = {
|
|
|
41750
41737
|
const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
|
|
41751
41738
|
const HANDLE_MIN = 3;
|
|
41752
41739
|
const HANDLE_MAX = 20;
|
|
41753
|
-
const DEFAULT_NAMESPACE$
|
|
41740
|
+
const DEFAULT_NAMESPACE$f = 'Settings.Profile';
|
|
41754
41741
|
/**
|
|
41755
41742
|
* `val-profile-view` — vista Perfil full-feature autocontenida (organism).
|
|
41756
41743
|
*
|
|
@@ -41800,7 +41787,7 @@ class ProfileViewComponent {
|
|
|
41800
41787
|
showPhone: merged.showPhone ?? true,
|
|
41801
41788
|
showShareCta: merged.showShareCta ?? true,
|
|
41802
41789
|
profileBaseUrl: merged.profileBaseUrl ?? '',
|
|
41803
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
41790
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$f,
|
|
41804
41791
|
onSaved: merged.onSaved,
|
|
41805
41792
|
onAvatarUploaded: merged.onAvatarUploaded,
|
|
41806
41793
|
};
|
|
@@ -42716,7 +42703,7 @@ const PREFERENCES_VIEW_I18N = {
|
|
|
42716
42703
|
},
|
|
42717
42704
|
};
|
|
42718
42705
|
|
|
42719
|
-
const DEFAULT_NAMESPACE$
|
|
42706
|
+
const DEFAULT_NAMESPACE$e = 'Settings.General';
|
|
42720
42707
|
const DEFAULT_LANGUAGES = ['es', 'en'];
|
|
42721
42708
|
/**
|
|
42722
42709
|
* `val-preferences-view` — vista de preferencias de UI cross-app (tema +
|
|
@@ -42753,7 +42740,7 @@ class PreferencesViewComponent {
|
|
|
42753
42740
|
showLanguage: merged.showLanguage ?? true,
|
|
42754
42741
|
showFontSize: merged.showFontSize ?? true,
|
|
42755
42742
|
supportedLanguages: merged.supportedLanguages ?? DEFAULT_LANGUAGES,
|
|
42756
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
42743
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$e,
|
|
42757
42744
|
onThemeChanged: merged.onThemeChanged,
|
|
42758
42745
|
onLanguageChanged: merged.onLanguageChanged,
|
|
42759
42746
|
onFontSizeChanged: merged.onFontSizeChanged,
|
|
@@ -43401,62 +43388,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
43401
43388
|
type: Input
|
|
43402
43389
|
}] } });
|
|
43403
43390
|
|
|
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
43391
|
addIcons({ shieldCheckmarkOutline, checkmarkCircleOutline });
|
|
43461
43392
|
/**
|
|
43462
43393
|
* `val-login-attempt-modal` — modal de confirmación de intento de inicio de sesión.
|
|
@@ -44565,7 +44496,7 @@ const SECURITY_VIEW_I18N = {
|
|
|
44565
44496
|
};
|
|
44566
44497
|
|
|
44567
44498
|
addIcons({ laptopOutline, phonePortraitOutline });
|
|
44568
|
-
const DEFAULT_NAMESPACE$
|
|
44499
|
+
const DEFAULT_NAMESPACE$d = 'Settings.Security';
|
|
44569
44500
|
/**
|
|
44570
44501
|
* `val-security-view` — vista Seguridad full-feature autocontenida (organism).
|
|
44571
44502
|
*
|
|
@@ -44614,7 +44545,7 @@ class SecurityViewComponent {
|
|
|
44614
44545
|
showChangeEmail: merged.showChangeEmail ?? true,
|
|
44615
44546
|
showMfa: merged.showMfa ?? true,
|
|
44616
44547
|
showPolicies: merged.showPolicies ?? true,
|
|
44617
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
44548
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$d,
|
|
44618
44549
|
handleDeepLinks: merged.handleDeepLinks ?? true,
|
|
44619
44550
|
homeRoute: merged.homeRoute ?? '/app/home',
|
|
44620
44551
|
onMfaCompleted: merged.onMfaCompleted,
|
|
@@ -45416,13 +45347,6 @@ class OrgService {
|
|
|
45416
45347
|
inviteUser(orgId, req) {
|
|
45417
45348
|
return this.http.post(`${this.baseUrl}/${orgId}/invite`, req);
|
|
45418
45349
|
}
|
|
45419
|
-
/**
|
|
45420
|
-
* Crea/asigna miembros en masa (ADR-023). Resultado por-fila (partial success):
|
|
45421
|
-
* `created` (pre-aprovisionado), `assigned` (ya existía), `skipped`, `error`.
|
|
45422
|
-
*/
|
|
45423
|
-
importMembers(orgId, req) {
|
|
45424
|
-
return this.http.post(`${this.baseUrl}/${orgId}/members/import`, req);
|
|
45425
|
-
}
|
|
45426
45350
|
leaveOrg(orgId) {
|
|
45427
45351
|
return this.http.post(`${this.baseUrl}/${orgId}/leave`, {});
|
|
45428
45352
|
}
|
|
@@ -45527,7 +45451,7 @@ const CREATE_ORG_MODAL_I18N = {
|
|
|
45527
45451
|
},
|
|
45528
45452
|
};
|
|
45529
45453
|
|
|
45530
|
-
const DEFAULT_NAMESPACE$
|
|
45454
|
+
const DEFAULT_NAMESPACE$c = 'CreateOrgModal';
|
|
45531
45455
|
/**
|
|
45532
45456
|
* `val-create-org-modal` — modal de creación de organización (organism
|
|
45533
45457
|
* compartido). Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por
|
|
@@ -45553,10 +45477,10 @@ class CreateOrgModalComponent {
|
|
|
45553
45477
|
this.toast = inject(ToastService);
|
|
45554
45478
|
this.errors = inject(ValtechErrorService);
|
|
45555
45479
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
45556
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
45480
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$c;
|
|
45557
45481
|
this._busy = signal(false);
|
|
45558
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
45559
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
45482
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$c)) {
|
|
45483
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$c, CREATE_ORG_MODAL_I18N);
|
|
45560
45484
|
}
|
|
45561
45485
|
this.formMeta = signal(this.buildFormMeta());
|
|
45562
45486
|
effect(() => {
|
|
@@ -45792,7 +45716,7 @@ const DELETE_ACCOUNT_MODAL_I18N = {
|
|
|
45792
45716
|
},
|
|
45793
45717
|
};
|
|
45794
45718
|
|
|
45795
|
-
const DEFAULT_NAMESPACE$
|
|
45719
|
+
const DEFAULT_NAMESPACE$b = 'Settings.DeleteAccount';
|
|
45796
45720
|
/**
|
|
45797
45721
|
* `val-delete-account-modal` — modal de eliminación de cuenta (organism,
|
|
45798
45722
|
* cuenta-específico). Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
@@ -45818,7 +45742,7 @@ class DeleteAccountModalComponent {
|
|
|
45818
45742
|
this.i18n = inject(I18nService);
|
|
45819
45743
|
this.toast = inject(ToastService);
|
|
45820
45744
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
45821
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
45745
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$b;
|
|
45822
45746
|
this.hasPassword = signal(null);
|
|
45823
45747
|
this.codeSent = signal(false);
|
|
45824
45748
|
this.formState = signal(ComponentStates.ENABLED);
|
|
@@ -45885,8 +45809,8 @@ class DeleteAccountModalComponent {
|
|
|
45885
45809
|
],
|
|
45886
45810
|
actions: this.submitBtn('delete-code-submit', 'confirmCta'),
|
|
45887
45811
|
}));
|
|
45888
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
45889
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
45812
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$b)) {
|
|
45813
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$b, DELETE_ACCOUNT_MODAL_I18N);
|
|
45890
45814
|
}
|
|
45891
45815
|
}
|
|
45892
45816
|
ngOnInit() {
|
|
@@ -46087,7 +46011,7 @@ const ORG_INFO_SHEET_I18N = {
|
|
|
46087
46011
|
},
|
|
46088
46012
|
};
|
|
46089
46013
|
|
|
46090
|
-
const DEFAULT_NAMESPACE$
|
|
46014
|
+
const DEFAULT_NAMESPACE$a = 'OrgInfoSheet';
|
|
46091
46015
|
/**
|
|
46092
46016
|
* `val-org-info-sheet` — sheet informativo "¿Qué son las organizaciones?".
|
|
46093
46017
|
* Extraído del `OrgInfoSheetComponent` inline de `AccountPage` bajo el proceso de
|
|
@@ -46132,15 +46056,15 @@ class OrgInfoSheetComponent {
|
|
|
46132
46056
|
text: this.closeProps.text || this.resolvedCloseLabel(),
|
|
46133
46057
|
}));
|
|
46134
46058
|
addIcons({ peopleOutline, swapHorizontalOutline, mailOutline });
|
|
46135
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
46136
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
46059
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$a)) {
|
|
46060
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$a, ORG_INFO_SHEET_I18N);
|
|
46137
46061
|
}
|
|
46138
46062
|
}
|
|
46139
46063
|
dismiss() {
|
|
46140
46064
|
this._modalRef?.dismiss(null, 'cancel');
|
|
46141
46065
|
}
|
|
46142
46066
|
tt(key) {
|
|
46143
|
-
return this.i18n.t(key, DEFAULT_NAMESPACE$
|
|
46067
|
+
return this.i18n.t(key, DEFAULT_NAMESPACE$a);
|
|
46144
46068
|
}
|
|
46145
46069
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgInfoSheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
46146
46070
|
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 +46231,7 @@ const SWITCH_ORG_MODAL_I18N = {
|
|
|
46307
46231
|
},
|
|
46308
46232
|
};
|
|
46309
46233
|
|
|
46310
|
-
const DEFAULT_NAMESPACE$
|
|
46234
|
+
const DEFAULT_NAMESPACE$9 = 'Settings.SwitchOrg';
|
|
46311
46235
|
addIcons({ businessOutline, checkmarkCircleOutline });
|
|
46312
46236
|
/**
|
|
46313
46237
|
* `val-switch-org-modal` — modal de cambio de organización activa (organism
|
|
@@ -46333,7 +46257,7 @@ class SwitchOrgModalComponent {
|
|
|
46333
46257
|
this.orgSwitch = inject(OrgSwitchService);
|
|
46334
46258
|
this.errors = inject(ValtechErrorService);
|
|
46335
46259
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
46336
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
46260
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$9;
|
|
46337
46261
|
this.orgs = signal([]);
|
|
46338
46262
|
this.loading = signal(false);
|
|
46339
46263
|
this.query = signal('');
|
|
@@ -46349,8 +46273,8 @@ class SwitchOrgModalComponent {
|
|
|
46349
46273
|
return list;
|
|
46350
46274
|
return list.filter(o => o.name.toLowerCase().includes(q));
|
|
46351
46275
|
});
|
|
46352
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
46353
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
46276
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$9)) {
|
|
46277
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$9, SWITCH_ORG_MODAL_I18N);
|
|
46354
46278
|
}
|
|
46355
46279
|
this.loadOrgs();
|
|
46356
46280
|
}
|
|
@@ -46668,7 +46592,7 @@ const ACCOUNT_VIEW_I18N = {
|
|
|
46668
46592
|
};
|
|
46669
46593
|
|
|
46670
46594
|
addIcons({ peopleOutline, businessOutline, chevronForwardOutline, swapHorizontalOutline, mailOutline });
|
|
46671
|
-
const DEFAULT_NAMESPACE$
|
|
46595
|
+
const DEFAULT_NAMESPACE$8 = 'Settings.Account';
|
|
46672
46596
|
const DEFAULT_MANAGE_ORG_ROUTE = '/app/settings/organization';
|
|
46673
46597
|
/**
|
|
46674
46598
|
* `val-account-view` — vista Cuenta full-feature autocontenida (organism).
|
|
@@ -46709,7 +46633,7 @@ class AccountViewComponent {
|
|
|
46709
46633
|
showNewOrgCta: merged.showNewOrgCta ?? true,
|
|
46710
46634
|
showLogout: merged.showLogout ?? true,
|
|
46711
46635
|
showDeleteAccount: merged.showDeleteAccount ?? true,
|
|
46712
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
46636
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$8,
|
|
46713
46637
|
manageOrgRoute: merged.manageOrgRoute ?? DEFAULT_MANAGE_ORG_ROUTE,
|
|
46714
46638
|
onOrgSwitched: merged.onOrgSwitched,
|
|
46715
46639
|
onOrgCreated: merged.onOrgCreated,
|
|
@@ -47434,7 +47358,7 @@ const EDIT_ORG_MODAL_I18N = {
|
|
|
47434
47358
|
},
|
|
47435
47359
|
};
|
|
47436
47360
|
|
|
47437
|
-
const DEFAULT_NAMESPACE$
|
|
47361
|
+
const DEFAULT_NAMESPACE$7 = 'EditOrgModal';
|
|
47438
47362
|
/**
|
|
47439
47363
|
* `val-edit-org-modal` — modal de edición de organización (nombre + descripción).
|
|
47440
47364
|
* Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por la vista de
|
|
@@ -47461,10 +47385,10 @@ class EditOrgModalComponent {
|
|
|
47461
47385
|
/** Organización a editar (pasada por `componentProps`). */
|
|
47462
47386
|
this.org = null;
|
|
47463
47387
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
47464
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
47388
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$7;
|
|
47465
47389
|
this.saving = signal(false);
|
|
47466
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
47467
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
47390
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
|
|
47391
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$7, EDIT_ORG_MODAL_I18N);
|
|
47468
47392
|
}
|
|
47469
47393
|
this.formMeta = signal(this.buildFormMeta());
|
|
47470
47394
|
effect(() => {
|
|
@@ -47702,7 +47626,7 @@ const INVITE_MEMBER_MODAL_I18N = {
|
|
|
47702
47626
|
},
|
|
47703
47627
|
};
|
|
47704
47628
|
|
|
47705
|
-
const DEFAULT_NAMESPACE$
|
|
47629
|
+
const DEFAULT_NAMESPACE$6 = 'InviteModal';
|
|
47706
47630
|
addIcons({ personOutline, mailOutline, closeOutline, checkmarkCircleOutline });
|
|
47707
47631
|
/**
|
|
47708
47632
|
* `val-invite-member-modal` — invitación de miembros a una organización.
|
|
@@ -47733,7 +47657,7 @@ class InviteMemberModalComponent {
|
|
|
47733
47657
|
/** Ids de miembros ya existentes (excluidos de los resultados). */
|
|
47734
47658
|
this.existingMemberIds = [];
|
|
47735
47659
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
47736
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
47660
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$6;
|
|
47737
47661
|
this.query = signal('');
|
|
47738
47662
|
this.searching = signal(false);
|
|
47739
47663
|
this.searchResults = signal([]);
|
|
@@ -47780,8 +47704,8 @@ class InviteMemberModalComponent {
|
|
|
47780
47704
|
state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
47781
47705
|
}));
|
|
47782
47706
|
this.searchSubject = new Subject();
|
|
47783
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
47784
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
47707
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$6)) {
|
|
47708
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$6, INVITE_MEMBER_MODAL_I18N);
|
|
47785
47709
|
}
|
|
47786
47710
|
this.loadRoles();
|
|
47787
47711
|
this.roleSub = this.roleControl.valueChanges.subscribe(v => {
|
|
@@ -48270,7 +48194,7 @@ const MEMBER_DETAIL_MODAL_I18N = {
|
|
|
48270
48194
|
},
|
|
48271
48195
|
};
|
|
48272
48196
|
|
|
48273
|
-
const DEFAULT_NAMESPACE$
|
|
48197
|
+
const DEFAULT_NAMESPACE$5 = 'Settings.MemberDetail';
|
|
48274
48198
|
/**
|
|
48275
48199
|
* `val-member-detail-modal` — detalle de miembro de organización: perfil, rol y
|
|
48276
48200
|
* permisos. Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
@@ -48307,7 +48231,7 @@ class MemberDetailModalComponent {
|
|
|
48307
48231
|
{ id: 'admin', name: 'admin' },
|
|
48308
48232
|
];
|
|
48309
48233
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
48310
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
48234
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$5;
|
|
48311
48235
|
this.working = signal(false);
|
|
48312
48236
|
this.currentRole = signal('');
|
|
48313
48237
|
this.roleControl = new FormControl('');
|
|
@@ -48347,8 +48271,8 @@ class MemberDetailModalComponent {
|
|
|
48347
48271
|
type: 'button',
|
|
48348
48272
|
state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
48349
48273
|
}));
|
|
48350
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
48351
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
48274
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
|
|
48275
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$5, MEMBER_DETAIL_MODAL_I18N);
|
|
48352
48276
|
}
|
|
48353
48277
|
}
|
|
48354
48278
|
ngOnInit() {
|
|
@@ -48685,7 +48609,7 @@ const TRANSFER_OWNERSHIP_MODAL_I18N = {
|
|
|
48685
48609
|
},
|
|
48686
48610
|
};
|
|
48687
48611
|
|
|
48688
|
-
const DEFAULT_NAMESPACE$
|
|
48612
|
+
const DEFAULT_NAMESPACE$4 = 'Settings.TransferModal';
|
|
48689
48613
|
/**
|
|
48690
48614
|
* `val-transfer-ownership-modal` — selector de miembro para transferir la
|
|
48691
48615
|
* propiedad de la organización. Promovido desde `showcase` bajo el proceso de
|
|
@@ -48709,7 +48633,7 @@ class TransferOwnershipModalComponent {
|
|
|
48709
48633
|
/** Lista de miembros transferibles (todos excepto el owner actual). */
|
|
48710
48634
|
this.members = [];
|
|
48711
48635
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
48712
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
48636
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$4;
|
|
48713
48637
|
this.query = signal('');
|
|
48714
48638
|
this.selectedId = signal('');
|
|
48715
48639
|
this.filtered = computed(() => {
|
|
@@ -48721,8 +48645,8 @@ class TransferOwnershipModalComponent {
|
|
|
48721
48645
|
m.userId.toLowerCase().includes(q));
|
|
48722
48646
|
});
|
|
48723
48647
|
this.noResultsLabel = computed(() => `${this.t('noResults')} "${this.query()}"`);
|
|
48724
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
48725
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
48648
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$4)) {
|
|
48649
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$4, TRANSFER_OWNERSHIP_MODAL_I18N);
|
|
48726
48650
|
}
|
|
48727
48651
|
}
|
|
48728
48652
|
ngOnInit() {
|
|
@@ -48907,371 +48831,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
48907
48831
|
type: Input
|
|
48908
48832
|
}] } });
|
|
48909
48833
|
|
|
48910
|
-
/**
|
|
48911
|
-
* Defaults i18n (es/en) embebidos en `val-member-import-modal` (ADR-023).
|
|
48912
|
-
* Auto-registrados si el consumer no proveyó el namespace `Settings.ImportModal`.
|
|
48913
|
-
*/
|
|
48914
|
-
const MEMBER_IMPORT_MODAL_I18N = {
|
|
48915
|
-
es: {
|
|
48916
|
-
title: 'Importar miembros',
|
|
48917
|
-
hint: 'Pega un CSV con columnas: email, nombre, rol (una fila por persona).',
|
|
48918
|
-
csvPlaceholder: 'email,nombre,rol\nana@x.com,Ana,editor\nbeto@x.com,Beto,viewer',
|
|
48919
|
-
rolesAvailable: 'Roles disponibles:',
|
|
48920
|
-
onConflictLabel: 'Si el email ya existe',
|
|
48921
|
-
conflictAssign: 'Asignar el rol',
|
|
48922
|
-
conflictSkip: 'Omitir',
|
|
48923
|
-
conflictError: 'Marcar error',
|
|
48924
|
-
sendActivation: 'Enviar email de activación a las cuentas nuevas',
|
|
48925
|
-
submit: 'Importar',
|
|
48926
|
-
submitting: 'Importando...',
|
|
48927
|
-
parseError: 'No se pudo interpretar el CSV. Revisa el formato.',
|
|
48928
|
-
noRows: 'Pega al menos una fila válida (email y rol).',
|
|
48929
|
-
summary: 'Resumen',
|
|
48930
|
-
created: 'creados',
|
|
48931
|
-
assigned: 'asignados',
|
|
48932
|
-
skipped: 'omitidos',
|
|
48933
|
-
failed: 'con error',
|
|
48934
|
-
colEmail: 'Email',
|
|
48935
|
-
colStatus: 'Estado',
|
|
48936
|
-
colDetail: 'Detalle',
|
|
48937
|
-
activationSent: 'activación enviada',
|
|
48938
|
-
activationPending: 'activación pendiente',
|
|
48939
|
-
again: 'Importar otro lote',
|
|
48940
|
-
close: 'Cerrar',
|
|
48941
|
-
genericError: 'No se pudo completar la importación. Intenta de nuevo.',
|
|
48942
|
-
},
|
|
48943
|
-
en: {
|
|
48944
|
-
title: 'Import members',
|
|
48945
|
-
hint: 'Paste a CSV with columns: email, name, role (one row per person).',
|
|
48946
|
-
csvPlaceholder: 'email,name,role\nana@x.com,Ana,editor\nbeto@x.com,Beto,viewer',
|
|
48947
|
-
rolesAvailable: 'Available roles:',
|
|
48948
|
-
onConflictLabel: 'If the email already exists',
|
|
48949
|
-
conflictAssign: 'Assign the role',
|
|
48950
|
-
conflictSkip: 'Skip',
|
|
48951
|
-
conflictError: 'Mark as error',
|
|
48952
|
-
sendActivation: 'Send activation email to new accounts',
|
|
48953
|
-
submit: 'Import',
|
|
48954
|
-
submitting: 'Importing...',
|
|
48955
|
-
parseError: "Couldn't parse the CSV. Check the format.",
|
|
48956
|
-
noRows: 'Paste at least one valid row (email and role).',
|
|
48957
|
-
summary: 'Summary',
|
|
48958
|
-
created: 'created',
|
|
48959
|
-
assigned: 'assigned',
|
|
48960
|
-
skipped: 'skipped',
|
|
48961
|
-
failed: 'failed',
|
|
48962
|
-
colEmail: 'Email',
|
|
48963
|
-
colStatus: 'Status',
|
|
48964
|
-
colDetail: 'Detail',
|
|
48965
|
-
activationSent: 'activation sent',
|
|
48966
|
-
activationPending: 'activation pending',
|
|
48967
|
-
again: 'Import another batch',
|
|
48968
|
-
close: 'Close',
|
|
48969
|
-
genericError: "Couldn't complete the import. Try again.",
|
|
48970
|
-
},
|
|
48971
|
-
};
|
|
48972
|
-
|
|
48973
|
-
const DEFAULT_NAMESPACE$2 = 'Settings.ImportModal';
|
|
48974
|
-
/**
|
|
48975
|
-
* `val-member-import-modal` — panel de carga masiva de miembros (ADR-023 fase 6).
|
|
48976
|
-
*
|
|
48977
|
-
* El admin pega un CSV (`email,nombre,rol`); el modal lo parsea a filas y llama
|
|
48978
|
-
* `OrgService.importMembers`. Muestra el resultado por-fila (partial success):
|
|
48979
|
-
* `created` / `assigned` / `skipped` / `error`. Header canónico (Regla #5).
|
|
48980
|
-
*
|
|
48981
|
-
* Auto-registra defaults i18n (es/en) en `Settings.ImportModal`.
|
|
48982
|
-
*/
|
|
48983
|
-
class MemberImportModalComponent {
|
|
48984
|
-
constructor() {
|
|
48985
|
-
this.i18n = inject(I18nService);
|
|
48986
|
-
this.orgService = inject(OrgService);
|
|
48987
|
-
/** Org destino del import. */
|
|
48988
|
-
this.orgId = '';
|
|
48989
|
-
/** Nombres de roles disponibles (para el hint). Opcional. */
|
|
48990
|
-
this.availableRoles = [];
|
|
48991
|
-
/** Namespace i18n. */
|
|
48992
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$2;
|
|
48993
|
-
this.csv = '';
|
|
48994
|
-
this.onConflict = 'assignRole';
|
|
48995
|
-
this.sendActivation = true;
|
|
48996
|
-
this.submitting = signal(false);
|
|
48997
|
-
this.errorMsg = signal('');
|
|
48998
|
-
this.results = signal(null);
|
|
48999
|
-
this.rolesHint = computed(() => this.availableRoles.length ? `${this.t('rolesAvailable')} ${this.availableRoles.join(', ')}` : '');
|
|
49000
|
-
this.summaryLabel = computed(() => {
|
|
49001
|
-
const s = this.results()?.summary;
|
|
49002
|
-
if (!s)
|
|
49003
|
-
return this.t('summary');
|
|
49004
|
-
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
|
-
});
|
|
49006
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$2)) {
|
|
49007
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$2, MEMBER_IMPORT_MODAL_I18N);
|
|
49008
|
-
}
|
|
49009
|
-
}
|
|
49010
|
-
ngOnInit() {
|
|
49011
|
-
if (!this.i18n.hasNamespace(this.i18nNamespace)) {
|
|
49012
|
-
this.i18n.registerContent(this.i18nNamespace, MEMBER_IMPORT_MODAL_I18N);
|
|
49013
|
-
}
|
|
49014
|
-
}
|
|
49015
|
-
/** Parsea el CSV pegado a filas {email,name,roleName}. Tolera header. */
|
|
49016
|
-
parseCsv() {
|
|
49017
|
-
const rows = [];
|
|
49018
|
-
for (const raw of this.csv.split(/\r?\n/)) {
|
|
49019
|
-
const line = raw.trim();
|
|
49020
|
-
if (!line)
|
|
49021
|
-
continue;
|
|
49022
|
-
const cols = line.split(',').map(c => c.trim());
|
|
49023
|
-
const email = (cols[0] ?? '').toLowerCase();
|
|
49024
|
-
// Saltar header (primera col "email") o líneas sin @.
|
|
49025
|
-
if (!email || !email.includes('@'))
|
|
49026
|
-
continue;
|
|
49027
|
-
rows.push({ email, name: cols[1] ?? '', roleName: cols[2] ?? '' });
|
|
49028
|
-
}
|
|
49029
|
-
return rows;
|
|
49030
|
-
}
|
|
49031
|
-
submit() {
|
|
49032
|
-
this.errorMsg.set('');
|
|
49033
|
-
const users = this.parseCsv();
|
|
49034
|
-
if (users.length === 0) {
|
|
49035
|
-
this.errorMsg.set(this.t('noRows'));
|
|
49036
|
-
return;
|
|
49037
|
-
}
|
|
49038
|
-
if (users.some(u => !u.roleName)) {
|
|
49039
|
-
this.errorMsg.set(this.t('parseError'));
|
|
49040
|
-
return;
|
|
49041
|
-
}
|
|
49042
|
-
this.submitting.set(true);
|
|
49043
|
-
this.orgService
|
|
49044
|
-
.importMembers(this.orgId, {
|
|
49045
|
-
users,
|
|
49046
|
-
onConflict: this.onConflict,
|
|
49047
|
-
sendActivation: this.sendActivation,
|
|
49048
|
-
})
|
|
49049
|
-
.subscribe({
|
|
49050
|
-
next: res => {
|
|
49051
|
-
this.submitting.set(false);
|
|
49052
|
-
this.results.set(res);
|
|
49053
|
-
// Refrescar lista del consumer si hubo altas/asignaciones efectivas.
|
|
49054
|
-
if (res.summary.created + res.summary.assigned > 0) {
|
|
49055
|
-
this.onSuccess?.();
|
|
49056
|
-
}
|
|
49057
|
-
},
|
|
49058
|
-
error: err => {
|
|
49059
|
-
this.submitting.set(false);
|
|
49060
|
-
this.errorMsg.set(err?.error?.message ?? this.t('genericError'));
|
|
49061
|
-
},
|
|
49062
|
-
});
|
|
49063
|
-
}
|
|
49064
|
-
statusLabel(r) {
|
|
49065
|
-
const base = this.t(r.status === 'error' ? 'failed' : r.status);
|
|
49066
|
-
if (r.status === 'created' && r.activation) {
|
|
49067
|
-
return `${base} · ${this.t(r.activation === 'sent' ? 'activationSent' : 'activationPending')}`;
|
|
49068
|
-
}
|
|
49069
|
-
return base;
|
|
49070
|
-
}
|
|
49071
|
-
reset() {
|
|
49072
|
-
this.csv = '';
|
|
49073
|
-
this.errorMsg.set('');
|
|
49074
|
-
this.results.set(null);
|
|
49075
|
-
}
|
|
49076
|
-
dismiss() {
|
|
49077
|
-
this._modalRef?.dismiss(null, 'cancel');
|
|
49078
|
-
}
|
|
49079
|
-
t(key) {
|
|
49080
|
-
return this.i18n.t(key, this.i18nNamespace);
|
|
49081
|
-
}
|
|
49082
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberImportModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
49083
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MemberImportModalComponent, isStandalone: true, selector: "val-member-import-modal", inputs: { orgId: "orgId", availableRoles: "availableRoles", onSuccess: "onSuccess", _modalRef: "_modalRef", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
|
|
49084
|
-
<ion-header>
|
|
49085
|
-
<ion-toolbar>
|
|
49086
|
-
<ion-buttons slot="end">
|
|
49087
|
-
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
49088
|
-
<strong>{{ t('close') }}</strong>
|
|
49089
|
-
</ion-button>
|
|
49090
|
-
</ion-buttons>
|
|
49091
|
-
</ion-toolbar>
|
|
49092
|
-
</ion-header>
|
|
49093
|
-
|
|
49094
|
-
<ion-content class="ion-padding">
|
|
49095
|
-
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
49096
|
-
|
|
49097
|
-
@if (!results()) {
|
|
49098
|
-
<val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
|
|
49099
|
-
|
|
49100
|
-
@if (rolesHint()) {
|
|
49101
|
-
<val-text [props]="{ content: rolesHint(), size: 'small', color: 'medium', bold: false }" />
|
|
49102
|
-
}
|
|
49103
|
-
|
|
49104
|
-
<ion-textarea
|
|
49105
|
-
class="import-csv"
|
|
49106
|
-
[autoGrow]="true"
|
|
49107
|
-
[rows]="6"
|
|
49108
|
-
[placeholder]="t('csvPlaceholder')"
|
|
49109
|
-
[(ngModel)]="csv"
|
|
49110
|
-
/>
|
|
49111
|
-
|
|
49112
|
-
<ion-select
|
|
49113
|
-
class="import-conflict"
|
|
49114
|
-
label="{{ t('onConflictLabel') }}"
|
|
49115
|
-
labelPlacement="stacked"
|
|
49116
|
-
interface="popover"
|
|
49117
|
-
[(ngModel)]="onConflict"
|
|
49118
|
-
>
|
|
49119
|
-
<ion-select-option value="assignRole">{{ t('conflictAssign') }}</ion-select-option>
|
|
49120
|
-
<ion-select-option value="skip">{{ t('conflictSkip') }}</ion-select-option>
|
|
49121
|
-
<ion-select-option value="error">{{ t('conflictError') }}</ion-select-option>
|
|
49122
|
-
</ion-select>
|
|
49123
|
-
|
|
49124
|
-
<ion-checkbox class="import-activation" labelPlacement="end" [(ngModel)]="sendActivation">
|
|
49125
|
-
{{ t('sendActivation') }}
|
|
49126
|
-
</ion-checkbox>
|
|
49127
|
-
|
|
49128
|
-
@if (errorMsg()) {
|
|
49129
|
-
<p class="import-error">{{ errorMsg() }}</p>
|
|
49130
|
-
}
|
|
49131
|
-
|
|
49132
|
-
<div class="import-actions">
|
|
49133
|
-
<ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
|
|
49134
|
-
@if (submitting()) {
|
|
49135
|
-
<ion-spinner name="dots" />
|
|
49136
|
-
{{ t('submitting') }}
|
|
49137
|
-
} @else {
|
|
49138
|
-
{{ t('submit') }}
|
|
49139
|
-
}
|
|
49140
|
-
</ion-button>
|
|
49141
|
-
</div>
|
|
49142
|
-
} @else {
|
|
49143
|
-
<!-- Resultados -->
|
|
49144
|
-
<val-title [props]="{ content: summaryLabel(), size: 'large', color: '', bold: true }" />
|
|
49145
|
-
<div class="import-results">
|
|
49146
|
-
@for (r of results()!.results; track r.email) {
|
|
49147
|
-
<div class="import-row import-row--{{ r.status }}">
|
|
49148
|
-
<span class="import-row__email">{{ r.email }}</span>
|
|
49149
|
-
<span class="import-row__status">{{ statusLabel(r) }}</span>
|
|
49150
|
-
@if (r.reason) {
|
|
49151
|
-
<span class="import-row__reason">{{ r.reason }}</span>
|
|
49152
|
-
}
|
|
49153
|
-
</div>
|
|
49154
|
-
}
|
|
49155
|
-
</div>
|
|
49156
|
-
<div class="import-actions">
|
|
49157
|
-
<ion-button expand="block" fill="outline" color="dark" (click)="reset()">
|
|
49158
|
-
{{ t('again') }}
|
|
49159
|
-
</ion-button>
|
|
49160
|
-
</div>
|
|
49161
|
-
}
|
|
49162
|
-
</ion-content>
|
|
49163
|
-
`, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.import-csv{--background: var(--ion-color-light);border-radius:10px;margin-bottom:16px;font-family:monospace;font-size:13px}.import-conflict{margin-bottom:12px}.import-activation{display:block;margin-bottom:16px;font-size:14px}.import-error{margin:0 0 12px;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.import-actions{margin-top:8px}.import-results{display:flex;flex-direction:column;gap:6px;margin:12px 0}.import-row{display:grid;grid-template-columns:1fr auto;grid-row-gap:2px;align-items:center;padding:10px 12px;border-radius:8px;border-left:3px solid var(--ion-color-medium);background:var(--ion-color-light);font-size:14px}.import-row--created{border-left-color:var(--ion-color-success, #2dd36f)}.import-row--assigned{border-left-color:var(--ion-color-primary, #7026df)}.import-row--skipped{border-left-color:var(--ion-color-medium, #92949c)}.import-row--error{border-left-color:var(--ion-color-danger, #c0392b)}.import-row__email{font-weight:600;color:var(--ion-color-dark)}.import-row__status{text-align:right;color:var(--ion-color-medium-shade, #6b6e76)}.import-row__reason{grid-column:1 / -1;font-size:12px;color:var(--ion-color-danger, #c0392b)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
|
|
49164
|
-
}
|
|
49165
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberImportModalComponent, decorators: [{
|
|
49166
|
-
type: Component,
|
|
49167
|
-
args: [{ selector: 'val-member-import-modal', standalone: true, imports: [
|
|
49168
|
-
FormsModule,
|
|
49169
|
-
IonHeader,
|
|
49170
|
-
IonToolbar,
|
|
49171
|
-
IonButtons,
|
|
49172
|
-
IonButton,
|
|
49173
|
-
IonContent,
|
|
49174
|
-
IonTextarea,
|
|
49175
|
-
IonSelect,
|
|
49176
|
-
IonSelectOption,
|
|
49177
|
-
IonCheckbox,
|
|
49178
|
-
IonSpinner,
|
|
49179
|
-
DisplayComponent,
|
|
49180
|
-
TextComponent,
|
|
49181
|
-
TitleComponent,
|
|
49182
|
-
], template: `
|
|
49183
|
-
<ion-header>
|
|
49184
|
-
<ion-toolbar>
|
|
49185
|
-
<ion-buttons slot="end">
|
|
49186
|
-
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
49187
|
-
<strong>{{ t('close') }}</strong>
|
|
49188
|
-
</ion-button>
|
|
49189
|
-
</ion-buttons>
|
|
49190
|
-
</ion-toolbar>
|
|
49191
|
-
</ion-header>
|
|
49192
|
-
|
|
49193
|
-
<ion-content class="ion-padding">
|
|
49194
|
-
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
49195
|
-
|
|
49196
|
-
@if (!results()) {
|
|
49197
|
-
<val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
|
|
49198
|
-
|
|
49199
|
-
@if (rolesHint()) {
|
|
49200
|
-
<val-text [props]="{ content: rolesHint(), size: 'small', color: 'medium', bold: false }" />
|
|
49201
|
-
}
|
|
49202
|
-
|
|
49203
|
-
<ion-textarea
|
|
49204
|
-
class="import-csv"
|
|
49205
|
-
[autoGrow]="true"
|
|
49206
|
-
[rows]="6"
|
|
49207
|
-
[placeholder]="t('csvPlaceholder')"
|
|
49208
|
-
[(ngModel)]="csv"
|
|
49209
|
-
/>
|
|
49210
|
-
|
|
49211
|
-
<ion-select
|
|
49212
|
-
class="import-conflict"
|
|
49213
|
-
label="{{ t('onConflictLabel') }}"
|
|
49214
|
-
labelPlacement="stacked"
|
|
49215
|
-
interface="popover"
|
|
49216
|
-
[(ngModel)]="onConflict"
|
|
49217
|
-
>
|
|
49218
|
-
<ion-select-option value="assignRole">{{ t('conflictAssign') }}</ion-select-option>
|
|
49219
|
-
<ion-select-option value="skip">{{ t('conflictSkip') }}</ion-select-option>
|
|
49220
|
-
<ion-select-option value="error">{{ t('conflictError') }}</ion-select-option>
|
|
49221
|
-
</ion-select>
|
|
49222
|
-
|
|
49223
|
-
<ion-checkbox class="import-activation" labelPlacement="end" [(ngModel)]="sendActivation">
|
|
49224
|
-
{{ t('sendActivation') }}
|
|
49225
|
-
</ion-checkbox>
|
|
49226
|
-
|
|
49227
|
-
@if (errorMsg()) {
|
|
49228
|
-
<p class="import-error">{{ errorMsg() }}</p>
|
|
49229
|
-
}
|
|
49230
|
-
|
|
49231
|
-
<div class="import-actions">
|
|
49232
|
-
<ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
|
|
49233
|
-
@if (submitting()) {
|
|
49234
|
-
<ion-spinner name="dots" />
|
|
49235
|
-
{{ t('submitting') }}
|
|
49236
|
-
} @else {
|
|
49237
|
-
{{ t('submit') }}
|
|
49238
|
-
}
|
|
49239
|
-
</ion-button>
|
|
49240
|
-
</div>
|
|
49241
|
-
} @else {
|
|
49242
|
-
<!-- Resultados -->
|
|
49243
|
-
<val-title [props]="{ content: summaryLabel(), size: 'large', color: '', bold: true }" />
|
|
49244
|
-
<div class="import-results">
|
|
49245
|
-
@for (r of results()!.results; track r.email) {
|
|
49246
|
-
<div class="import-row import-row--{{ r.status }}">
|
|
49247
|
-
<span class="import-row__email">{{ r.email }}</span>
|
|
49248
|
-
<span class="import-row__status">{{ statusLabel(r) }}</span>
|
|
49249
|
-
@if (r.reason) {
|
|
49250
|
-
<span class="import-row__reason">{{ r.reason }}</span>
|
|
49251
|
-
}
|
|
49252
|
-
</div>
|
|
49253
|
-
}
|
|
49254
|
-
</div>
|
|
49255
|
-
<div class="import-actions">
|
|
49256
|
-
<ion-button expand="block" fill="outline" color="dark" (click)="reset()">
|
|
49257
|
-
{{ t('again') }}
|
|
49258
|
-
</ion-button>
|
|
49259
|
-
</div>
|
|
49260
|
-
}
|
|
49261
|
-
</ion-content>
|
|
49262
|
-
`, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.import-csv{--background: var(--ion-color-light);border-radius:10px;margin-bottom:16px;font-family:monospace;font-size:13px}.import-conflict{margin-bottom:12px}.import-activation{display:block;margin-bottom:16px;font-size:14px}.import-error{margin:0 0 12px;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.import-actions{margin-top:8px}.import-results{display:flex;flex-direction:column;gap:6px;margin:12px 0}.import-row{display:grid;grid-template-columns:1fr auto;grid-row-gap:2px;align-items:center;padding:10px 12px;border-radius:8px;border-left:3px solid var(--ion-color-medium);background:var(--ion-color-light);font-size:14px}.import-row--created{border-left-color:var(--ion-color-success, #2dd36f)}.import-row--assigned{border-left-color:var(--ion-color-primary, #7026df)}.import-row--skipped{border-left-color:var(--ion-color-medium, #92949c)}.import-row--error{border-left-color:var(--ion-color-danger, #c0392b)}.import-row__email{font-weight:600;color:var(--ion-color-dark)}.import-row__status{text-align:right;color:var(--ion-color-medium-shade, #6b6e76)}.import-row__reason{grid-column:1 / -1;font-size:12px;color:var(--ion-color-danger, #c0392b)}\n"] }]
|
|
49263
|
-
}], ctorParameters: () => [], propDecorators: { orgId: [{
|
|
49264
|
-
type: Input
|
|
49265
|
-
}], availableRoles: [{
|
|
49266
|
-
type: Input
|
|
49267
|
-
}], onSuccess: [{
|
|
49268
|
-
type: Input
|
|
49269
|
-
}], _modalRef: [{
|
|
49270
|
-
type: Input
|
|
49271
|
-
}], i18nNamespace: [{
|
|
49272
|
-
type: Input
|
|
49273
|
-
}] } });
|
|
49274
|
-
|
|
49275
48834
|
/**
|
|
49276
48835
|
* Defaults i18n (es/en) embebidos en `val-organization-view`. Auto-registrados en
|
|
49277
48836
|
* el constructor del componente si el consumer no proveyó el namespace
|
|
@@ -49327,7 +48886,6 @@ const ORGANIZATION_VIEW_I18N = {
|
|
|
49327
48886
|
inviteCtaTitle: '¿Deseas agregar a alguien?',
|
|
49328
48887
|
inviteCtaHint: 'Invita a usuarios a unirse a tu organización.',
|
|
49329
48888
|
inviteOpen: 'Invitar',
|
|
49330
|
-
importOpen: 'Importar',
|
|
49331
48889
|
viewMemberCta: 'Ver miembro',
|
|
49332
48890
|
roleViewer: 'Visor',
|
|
49333
48891
|
roleEditor: 'Editor',
|
|
@@ -49397,7 +48955,6 @@ const ORGANIZATION_VIEW_I18N = {
|
|
|
49397
48955
|
inviteCtaTitle: 'Want to add someone?',
|
|
49398
48956
|
inviteCtaHint: 'Invite users to join your organization.',
|
|
49399
48957
|
inviteOpen: 'Invite',
|
|
49400
|
-
importOpen: 'Import',
|
|
49401
48958
|
viewMemberCta: 'View member',
|
|
49402
48959
|
roleViewer: 'Viewer',
|
|
49403
48960
|
roleEditor: 'Editor',
|
|
@@ -49422,7 +48979,7 @@ const ORGANIZATION_VIEW_I18N = {
|
|
|
49422
48979
|
},
|
|
49423
48980
|
};
|
|
49424
48981
|
|
|
49425
|
-
const DEFAULT_NAMESPACE$
|
|
48982
|
+
const DEFAULT_NAMESPACE$3 = 'Settings.Organization';
|
|
49426
48983
|
const DEFAULT_VIEW_PERMISSIONS_ROUTE = '/app/settings/permissions';
|
|
49427
48984
|
const DEFAULT_LEAVE_REDIRECT_ROUTE = '/app/settings/account';
|
|
49428
48985
|
/**
|
|
@@ -49479,7 +49036,7 @@ class OrganizationViewComponent {
|
|
|
49479
49036
|
showInvite: merged.showInvite ?? true,
|
|
49480
49037
|
showTransferOwnership: merged.showTransferOwnership ?? true,
|
|
49481
49038
|
showLeave: merged.showLeave ?? true,
|
|
49482
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
49039
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$3,
|
|
49483
49040
|
viewPermissionsRoute: merged.viewPermissionsRoute ?? DEFAULT_VIEW_PERMISSIONS_ROUTE,
|
|
49484
49041
|
leaveRedirectRoute: merged.leaveRedirectRoute ?? DEFAULT_LEAVE_REDIRECT_ROUTE,
|
|
49485
49042
|
onOrgEdited: merged.onOrgEdited,
|
|
@@ -49511,22 +49068,6 @@ class OrganizationViewComponent {
|
|
|
49511
49068
|
this.members = signal([]);
|
|
49512
49069
|
this.membersLoading = signal(false);
|
|
49513
49070
|
this.membersLoadError = signal(null);
|
|
49514
|
-
this.membersErrorState = computed(() => {
|
|
49515
|
-
this.i18n.lang();
|
|
49516
|
-
const err = this.membersLoadError();
|
|
49517
|
-
if (!err)
|
|
49518
|
-
return null;
|
|
49519
|
-
return createErrorStateProps(err, {
|
|
49520
|
-
title: { offline: this.tt('offlineTitle'), error: this.tt('errorTitle') },
|
|
49521
|
-
description: {
|
|
49522
|
-
offline: this.tt('offlineHint'),
|
|
49523
|
-
error: interpretError(err).message,
|
|
49524
|
-
},
|
|
49525
|
-
retryLabel: this.tt('retry'),
|
|
49526
|
-
onRetry: () => this.loadMembers(),
|
|
49527
|
-
retrying: this.membersLoading(),
|
|
49528
|
-
});
|
|
49529
|
-
});
|
|
49530
49071
|
this.membersNextToken = signal(null);
|
|
49531
49072
|
this.loadingMoreMembers = signal(false);
|
|
49532
49073
|
this.showAllMembers = signal(false);
|
|
@@ -49613,14 +49154,6 @@ class OrganizationViewComponent {
|
|
|
49613
49154
|
shape: 'round',
|
|
49614
49155
|
type: 'button',
|
|
49615
49156
|
}));
|
|
49616
|
-
this.openImportButtonProps = computed(() => ({
|
|
49617
|
-
text: this.tt('importOpen'),
|
|
49618
|
-
color: 'dark',
|
|
49619
|
-
fill: 'outline',
|
|
49620
|
-
size: 'default',
|
|
49621
|
-
shape: 'round',
|
|
49622
|
-
type: 'button',
|
|
49623
|
-
}));
|
|
49624
49157
|
/** Guarda para detectar el primer disparo del effect (evita doble carga). */
|
|
49625
49158
|
this.lastLoadedOrgId = null;
|
|
49626
49159
|
const ns = this.ns;
|
|
@@ -49721,25 +49254,6 @@ class OrganizationViewComponent {
|
|
|
49721
49254
|
backdropDismiss: true,
|
|
49722
49255
|
});
|
|
49723
49256
|
}
|
|
49724
|
-
onOpenImport() {
|
|
49725
|
-
void this.modalService.open({
|
|
49726
|
-
component: MemberImportModalComponent,
|
|
49727
|
-
componentProps: {
|
|
49728
|
-
orgId: this.activeOrgId(),
|
|
49729
|
-
availableRoles: this.availableRoles().map(r => r.name),
|
|
49730
|
-
onSuccess: () => {
|
|
49731
|
-
this.loadMembers();
|
|
49732
|
-
this.resolvedConfig().onMemberInvited?.();
|
|
49733
|
-
},
|
|
49734
|
-
},
|
|
49735
|
-
breakpoints: {
|
|
49736
|
-
breakpoints: [0, 0.92],
|
|
49737
|
-
initialBreakpoint: 0.92,
|
|
49738
|
-
showHandle: true,
|
|
49739
|
-
},
|
|
49740
|
-
backdropDismiss: true,
|
|
49741
|
-
});
|
|
49742
|
-
}
|
|
49743
49257
|
onViewPermissions() {
|
|
49744
49258
|
this.nav.navigateByUrl(this.resolvedConfig().viewPermissionsRoute);
|
|
49745
49259
|
}
|
|
@@ -49821,7 +49335,6 @@ class OrganizationViewComponent {
|
|
|
49821
49335
|
if (!orgId)
|
|
49822
49336
|
return Promise.resolve();
|
|
49823
49337
|
this.membersLoading.set(true);
|
|
49824
|
-
this.membersLoadError.set(null);
|
|
49825
49338
|
this.membersNextToken.set(null);
|
|
49826
49339
|
this.showAllMembers.set(false);
|
|
49827
49340
|
return new Promise(resolve => {
|
|
@@ -50032,8 +49545,6 @@ class OrganizationViewComponent {
|
|
|
50032
49545
|
<div class="section-body">
|
|
50033
49546
|
@if (membersLoading()) {
|
|
50034
49547
|
<val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
|
|
50035
|
-
} @else if (membersErrorState()) {
|
|
50036
|
-
<val-empty-state [props]="membersErrorState()!" />
|
|
50037
49548
|
} @else if (members().length === 0) {
|
|
50038
49549
|
<val-text
|
|
50039
49550
|
[props]="{
|
|
@@ -50126,7 +49637,6 @@ class OrganizationViewComponent {
|
|
|
50126
49637
|
/>
|
|
50127
49638
|
</div>
|
|
50128
49639
|
<val-button [props]="openInviteButtonProps()" (click)="onOpenInvite()" />
|
|
50129
|
-
<val-button [props]="openImportButtonProps()" (click)="onOpenImport()" />
|
|
50130
49640
|
</div>
|
|
50131
49641
|
</section>
|
|
50132
49642
|
}
|
|
@@ -50235,7 +49745,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
50235
49745
|
ButtonComponent,
|
|
50236
49746
|
MemberCardComponent,
|
|
50237
49747
|
TransferOwnershipModalComponent,
|
|
50238
|
-
MemberImportModalComponent,
|
|
50239
49748
|
], template: `
|
|
50240
49749
|
<div class="page">
|
|
50241
49750
|
<header class="page-header">
|
|
@@ -50314,8 +49823,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
50314
49823
|
<div class="section-body">
|
|
50315
49824
|
@if (membersLoading()) {
|
|
50316
49825
|
<val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
|
|
50317
|
-
} @else if (membersErrorState()) {
|
|
50318
|
-
<val-empty-state [props]="membersErrorState()!" />
|
|
50319
49826
|
} @else if (members().length === 0) {
|
|
50320
49827
|
<val-text
|
|
50321
49828
|
[props]="{
|
|
@@ -50408,7 +49915,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
50408
49915
|
/>
|
|
50409
49916
|
</div>
|
|
50410
49917
|
<val-button [props]="openInviteButtonProps()" (click)="onOpenInvite()" />
|
|
50411
|
-
<val-button [props]="openImportButtonProps()" (click)="onOpenImport()" />
|
|
50412
49918
|
</div>
|
|
50413
49919
|
</section>
|
|
50414
49920
|
}
|
|
@@ -50537,6 +50043,70 @@ function provideValtechOrganizationRoutes(opts) {
|
|
|
50537
50043
|
];
|
|
50538
50044
|
}
|
|
50539
50045
|
|
|
50046
|
+
/**
|
|
50047
|
+
* Helper para montar el bloque completo de Settings en una app del factory: el
|
|
50048
|
+
* HUB de navegación (`val-settings-hub`) en el index + las vistas promovidas
|
|
50049
|
+
* (Perfil, Preferencias) + las rutas propias de la app (`extraRoutes`).
|
|
50050
|
+
*
|
|
50051
|
+
* Pensado para apps nuevas (greenfield) que adoptan el set entero. showcase NO
|
|
50052
|
+
* lo usa hoy (preserva su `settings.routes.ts` por la semántica de sus muchas
|
|
50053
|
+
* secciones no promovidas — ver guía de promoción), pero el provider debe
|
|
50054
|
+
* existir y compilar para el resto de apps.
|
|
50055
|
+
*
|
|
50056
|
+
* @example
|
|
50057
|
+
* ```ts
|
|
50058
|
+
* // app.routes.ts
|
|
50059
|
+
* import { provideValtechSettingsRoutes } from 'valtech-components';
|
|
50060
|
+
*
|
|
50061
|
+
* export const routes: Routes = [
|
|
50062
|
+
* {
|
|
50063
|
+
* path: 'app',
|
|
50064
|
+
* children: [
|
|
50065
|
+
* ...provideValtechSettingsRoutes({
|
|
50066
|
+
* hubConfig: { onLogout: () => auth.logoutWithConfirmation() },
|
|
50067
|
+
* extraRoutes: [
|
|
50068
|
+
* { path: 'security', loadComponent: () => ... },
|
|
50069
|
+
* ],
|
|
50070
|
+
* }),
|
|
50071
|
+
* ],
|
|
50072
|
+
* },
|
|
50073
|
+
* ];
|
|
50074
|
+
* ```
|
|
50075
|
+
*/
|
|
50076
|
+
function provideValtechSettingsRoutes(opts) {
|
|
50077
|
+
const basePath = opts?.basePath ?? 'settings';
|
|
50078
|
+
const includeProfile = opts?.includeProfile ?? true;
|
|
50079
|
+
const includePreferences = opts?.includePreferences ?? true;
|
|
50080
|
+
const includeSecurity = opts?.includeSecurity ?? true;
|
|
50081
|
+
const includeAccount = opts?.includeAccount ?? true;
|
|
50082
|
+
const includeOrganization = opts?.includeOrganization ?? true;
|
|
50083
|
+
return [
|
|
50084
|
+
{
|
|
50085
|
+
path: basePath,
|
|
50086
|
+
children: [
|
|
50087
|
+
{
|
|
50088
|
+
path: '',
|
|
50089
|
+
component: SettingsHubComponent,
|
|
50090
|
+
data: { settingsHubConfig: opts?.hubConfig },
|
|
50091
|
+
},
|
|
50092
|
+
...(includeProfile ? provideValtechProfileRoutes({ config: opts?.profileConfig }) : []),
|
|
50093
|
+
...(includePreferences
|
|
50094
|
+
? provideValtechPreferencesRoutes({
|
|
50095
|
+
path: opts?.preferencesPath ?? 'preferences',
|
|
50096
|
+
config: opts?.preferencesConfig,
|
|
50097
|
+
})
|
|
50098
|
+
: []),
|
|
50099
|
+
...(includeSecurity ? provideValtechSecurityRoutes({ config: opts?.securityConfig }) : []),
|
|
50100
|
+
...(includeAccount ? provideValtechAccountRoutes({ config: opts?.accountConfig }) : []),
|
|
50101
|
+
...(includeOrganization
|
|
50102
|
+
? provideValtechOrganizationRoutes({ config: opts?.organizationConfig })
|
|
50103
|
+
: []),
|
|
50104
|
+
...(opts?.extraRoutes ?? []),
|
|
50105
|
+
],
|
|
50106
|
+
},
|
|
50107
|
+
];
|
|
50108
|
+
}
|
|
50109
|
+
|
|
50540
50110
|
/**
|
|
50541
50111
|
* Refreshable Stream
|
|
50542
50112
|
*
|
|
@@ -50745,7 +50315,7 @@ addIcons({
|
|
|
50745
50315
|
openOutline,
|
|
50746
50316
|
personOutline,
|
|
50747
50317
|
});
|
|
50748
|
-
const DEFAULT_NAMESPACE = 'Notifications';
|
|
50318
|
+
const DEFAULT_NAMESPACE$2 = 'Notifications';
|
|
50749
50319
|
const DEFAULT_SETTINGS_PATH = '/app/settings/notifications';
|
|
50750
50320
|
const DEFAULT_PAGE_SIZE = 10;
|
|
50751
50321
|
const DEFAULT_MAX_LOAD = 50;
|
|
@@ -50805,7 +50375,7 @@ class NotificationsViewComponent {
|
|
|
50805
50375
|
showPushOffCta: merged.showPushOffCta ?? true,
|
|
50806
50376
|
enableMarkAll: merged.enableMarkAll ?? true,
|
|
50807
50377
|
settingsNotificationsPath: merged.settingsNotificationsPath ?? DEFAULT_SETTINGS_PATH,
|
|
50808
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
50378
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$2,
|
|
50809
50379
|
pageSize: merged.pageSize ?? DEFAULT_PAGE_SIZE,
|
|
50810
50380
|
maxLoad: merged.maxLoad ?? DEFAULT_MAX_LOAD,
|
|
50811
50381
|
onNotificationOpen: merged.onNotificationOpen,
|
|
@@ -51473,6 +51043,1419 @@ function provideValtechNotificationsRoutes(opts) {
|
|
|
51473
51043
|
];
|
|
51474
51044
|
}
|
|
51475
51045
|
|
|
51046
|
+
/**
|
|
51047
|
+
* Defaults i18n (es/en) embebidos en `val-notification-preferences-view`.
|
|
51048
|
+
* Auto-registrados en el constructor del componente si el consumer no proveyó
|
|
51049
|
+
* el namespace (`Settings.Notifications` por default). SOLO incluye las keys que
|
|
51050
|
+
* la vista usa.
|
|
51051
|
+
*
|
|
51052
|
+
* Portado del override `Settings.Notifications` (showcase) bajo el proceso de
|
|
51053
|
+
* ADR-021.
|
|
51054
|
+
*/
|
|
51055
|
+
const NOTIFICATION_PREFERENCES_VIEW_I18N = {
|
|
51056
|
+
es: {
|
|
51057
|
+
pageTitle: 'Notificaciones',
|
|
51058
|
+
pageDescription: 'Configura como quieres recibir notificaciones',
|
|
51059
|
+
// Control principal (UX simplificado)
|
|
51060
|
+
simpleTitle: 'Notificaciones push en este dispositivo',
|
|
51061
|
+
simpleDescription: 'Recibe alertas en tiempo real incluso con la app cerrada.',
|
|
51062
|
+
enabling: 'Activando…',
|
|
51063
|
+
disabling: 'Desactivando…',
|
|
51064
|
+
enabled: 'Activadas',
|
|
51065
|
+
errorGeneric: 'No pudimos completar la configuración. Verifica los permisos del navegador y reintenta.',
|
|
51066
|
+
retryCta: 'Reintentar',
|
|
51067
|
+
// Mensajes contextuales
|
|
51068
|
+
permissionUnsupported: 'Tu navegador no soporta notificaciones push.',
|
|
51069
|
+
permissionDeniedInfo: 'Tu navegador esta bloqueando las notificaciones. Para habilitarlas, ve a la configuracion del sitio y permitelas manualmente.',
|
|
51070
|
+
// iOS PWA install
|
|
51071
|
+
iosInstallTitle: 'En iOS, instala la app para recibir notificaciones',
|
|
51072
|
+
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.',
|
|
51073
|
+
iosInstallStep1: 'Abre este sitio en Safari (no en Chrome/Edge).',
|
|
51074
|
+
iosInstallStep2: 'Toca el botón Compartir (□ con flecha hacia arriba).',
|
|
51075
|
+
iosInstallStep3: "Elige 'Añadir a pantalla de inicio'.",
|
|
51076
|
+
iosInstallStep4: 'Abre la app desde el ícono recién creado y vuelve aquí.',
|
|
51077
|
+
// Detalles técnicos (expandible)
|
|
51078
|
+
detailsShow: 'Ver detalles técnicos',
|
|
51079
|
+
detailsHide: 'Ocultar detalles',
|
|
51080
|
+
detailsToken: 'Token',
|
|
51081
|
+
detailsNone: 'Sin registrar',
|
|
51082
|
+
diagnosticsPermission: 'Permiso del navegador',
|
|
51083
|
+
diagnosticsSupported: 'Soporte del navegador',
|
|
51084
|
+
// Toasts
|
|
51085
|
+
enableSuccess: 'Notificaciones habilitadas',
|
|
51086
|
+
disableSuccess: 'Notificaciones deshabilitadas',
|
|
51087
|
+
},
|
|
51088
|
+
en: {
|
|
51089
|
+
pageTitle: 'Notifications',
|
|
51090
|
+
pageDescription: 'Configure how you want to receive notifications',
|
|
51091
|
+
simpleTitle: 'Push notifications on this device',
|
|
51092
|
+
simpleDescription: 'Get real-time alerts even when the app is closed.',
|
|
51093
|
+
enabling: 'Enabling…',
|
|
51094
|
+
disabling: 'Disabling…',
|
|
51095
|
+
enabled: 'Enabled',
|
|
51096
|
+
errorGeneric: "We couldn't complete the setup. Check your browser permissions and retry.",
|
|
51097
|
+
retryCta: 'Retry',
|
|
51098
|
+
permissionUnsupported: 'Your browser does not support push notifications.',
|
|
51099
|
+
permissionDeniedInfo: 'Your browser is blocking notifications. To enable them, go to site settings and allow them manually.',
|
|
51100
|
+
iosInstallTitle: 'On iOS, install the app to receive notifications',
|
|
51101
|
+
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.',
|
|
51102
|
+
iosInstallStep1: 'Open this site in Safari (not Chrome/Edge).',
|
|
51103
|
+
iosInstallStep2: 'Tap the Share button (□ with an up arrow).',
|
|
51104
|
+
iosInstallStep3: "Choose 'Add to Home Screen'.",
|
|
51105
|
+
iosInstallStep4: 'Open the app from the new icon and come back here.',
|
|
51106
|
+
detailsShow: 'View technical details',
|
|
51107
|
+
detailsHide: 'Hide details',
|
|
51108
|
+
detailsToken: 'Token',
|
|
51109
|
+
detailsNone: 'Not registered',
|
|
51110
|
+
diagnosticsPermission: 'Browser permission',
|
|
51111
|
+
diagnosticsSupported: 'Browser support',
|
|
51112
|
+
enableSuccess: 'Notifications enabled',
|
|
51113
|
+
disableSuccess: 'Notifications disabled',
|
|
51114
|
+
},
|
|
51115
|
+
};
|
|
51116
|
+
|
|
51117
|
+
addIcons({
|
|
51118
|
+
alertCircleOutline,
|
|
51119
|
+
chevronDownOutline,
|
|
51120
|
+
chevronUpOutline,
|
|
51121
|
+
closeCircleOutline,
|
|
51122
|
+
notificationsOffOutline,
|
|
51123
|
+
notificationsOutline,
|
|
51124
|
+
refreshOutline,
|
|
51125
|
+
});
|
|
51126
|
+
const DEFAULT_NAMESPACE$1 = 'Settings.Notifications';
|
|
51127
|
+
/**
|
|
51128
|
+
* `val-notification-preferences-view` — **preferencias de notificaciones push**
|
|
51129
|
+
* (FCM) full-feature autocontenida (organism). Promovida desde `showcase` bajo el
|
|
51130
|
+
* proceso de ADR-021.
|
|
51131
|
+
*
|
|
51132
|
+
* Separada de `val-notifications-view` (el inbox / feed). Aquí el user **activa o
|
|
51133
|
+
* desactiva** las notificaciones push en este dispositivo.
|
|
51134
|
+
*
|
|
51135
|
+
* UX simplificado: UN solo toggle que ejecuta toda la secuencia (pedir permiso →
|
|
51136
|
+
* SW ready → getToken → registrar en backend → set master pref) vía
|
|
51137
|
+
* `MessagingService.enable()`. El registro del device en el backend va como
|
|
51138
|
+
* callback (la lib no depende de `AuthService` para evitar ciclos). Mensajes
|
|
51139
|
+
* contextuales y botón "Reintentar" aparecen solo cuando aplican.
|
|
51140
|
+
*
|
|
51141
|
+
* NO renderiza ion-content — vive dentro de val-page-wrapper. `ActivatedRoute` se
|
|
51142
|
+
* inyecta `{ optional: true }` → funciona sin router (embebida / Storybook).
|
|
51143
|
+
*/
|
|
51144
|
+
class NotificationPreferencesViewComponent {
|
|
51145
|
+
/** Namespace i18n resuelto (capturado para llamadas no-reactivas). */
|
|
51146
|
+
get ns() {
|
|
51147
|
+
return this.resolvedConfig().i18nNamespace;
|
|
51148
|
+
}
|
|
51149
|
+
// ---------------------------------------------------------------------------
|
|
51150
|
+
// Lifecycle
|
|
51151
|
+
// ---------------------------------------------------------------------------
|
|
51152
|
+
constructor() {
|
|
51153
|
+
this.nav = inject(NavigationService);
|
|
51154
|
+
this.i18n = inject(I18nService);
|
|
51155
|
+
this.toast = inject(ToastService);
|
|
51156
|
+
this.auth = inject(AuthService);
|
|
51157
|
+
this.messaging = inject(MessagingService);
|
|
51158
|
+
this.pageRefresh = inject(PageRefreshService);
|
|
51159
|
+
this.prefs = inject(PreferencesService);
|
|
51160
|
+
// `ActivatedRoute` opcional → la vista funciona embebida / en Storybook sin
|
|
51161
|
+
// router (solo lo usa para leer el route data de config como fallback).
|
|
51162
|
+
this.route = inject(ActivatedRoute, { optional: true });
|
|
51163
|
+
this.resolvedConfig = computed(() => {
|
|
51164
|
+
const fromRoute = (this.route?.snapshot.data['notificationPreferencesConfig'] ??
|
|
51165
|
+
{});
|
|
51166
|
+
const merged = { ...fromRoute, ...(this.config ?? {}) };
|
|
51167
|
+
return {
|
|
51168
|
+
showDetails: merged.showDetails ?? true,
|
|
51169
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$1,
|
|
51170
|
+
onPushEnabled: merged.onPushEnabled,
|
|
51171
|
+
onPushDisabled: merged.onPushDisabled,
|
|
51172
|
+
};
|
|
51173
|
+
});
|
|
51174
|
+
/** Operación en curso (gobierna spinner del toggle + label "Activando…"). */
|
|
51175
|
+
this._busy = signal(null);
|
|
51176
|
+
/** True si el último intento falló — gobierna estado `error` + retry button. */
|
|
51177
|
+
this._lastFailed = signal(false);
|
|
51178
|
+
/** True mientras se re-obtiene el token FCM en background (no afecta la UI). */
|
|
51179
|
+
this._repairingToken = false;
|
|
51180
|
+
/** Toggle del expand de detalles técnicos. */
|
|
51181
|
+
this.showDetails = signal(false);
|
|
51182
|
+
this.permission = signal('default');
|
|
51183
|
+
this.isSupported = signal(false);
|
|
51184
|
+
this.currentToken = signal(null);
|
|
51185
|
+
this.needsIOSInstall = signal(this.detectNeedsIOSInstall());
|
|
51186
|
+
/** FormControl del toggle (sync-eado con `isEnabled` via effect). */
|
|
51187
|
+
this.toggleControl = new FormControl(false, {
|
|
51188
|
+
nonNullable: true,
|
|
51189
|
+
});
|
|
51190
|
+
/** Handler del listener `focus` — referencia guardada para poder removerlo. */
|
|
51191
|
+
this.onWindowFocus = () => this.scheduleBrowserRefresh();
|
|
51192
|
+
/** Timer del debounce del refresh disparado por `focus`. */
|
|
51193
|
+
this.focusDebounceTimer = null;
|
|
51194
|
+
/** Ventana de debounce (ms) para el refresh disparado por `focus`. */
|
|
51195
|
+
this.FOCUS_DEBOUNCE_MS = 500;
|
|
51196
|
+
// ---------------------------------------------------------------------------
|
|
51197
|
+
// Estado derivado
|
|
51198
|
+
// ---------------------------------------------------------------------------
|
|
51199
|
+
this.pageTitle = computed(() => this.tt('pageTitle'));
|
|
51200
|
+
this.pageDescription = computed(() => this.tt('pageDescription'));
|
|
51201
|
+
/**
|
|
51202
|
+
* Estado efectivo del toggle: "el usuario tiene push activo".
|
|
51203
|
+
*
|
|
51204
|
+
* Se deriva SOLO de la decisión del usuario (`master`) y del permiso del SO
|
|
51205
|
+
* (`permission`). El token FCM NO forma parte del estado de UI: vive en
|
|
51206
|
+
* memoria, se pierde en cada recarga/suspensión de la PWA y produciría flicker.
|
|
51207
|
+
* Si falta token con `master && granted`, es un detalle reparable en
|
|
51208
|
+
* background (ver `repairTokenIfNeeded`), no un cambio del toggle.
|
|
51209
|
+
*/
|
|
51210
|
+
this.isEnabled = computed(() => this.prefs.notificationsMaster() && this.permission() === 'granted');
|
|
51211
|
+
this.state = computed(() => {
|
|
51212
|
+
if (this.needsIOSInstall())
|
|
51213
|
+
return 'needs-install';
|
|
51214
|
+
if (this.permission() === 'unsupported' || !this.isSupported()) {
|
|
51215
|
+
// iOS standalone aún puede dar isSupported false temprano antes de init —
|
|
51216
|
+
// solo bloqueamos si el browser explícitamente no soporta APIs.
|
|
51217
|
+
if (this.permission() === 'unsupported')
|
|
51218
|
+
return 'unsupported';
|
|
51219
|
+
}
|
|
51220
|
+
const busy = this._busy();
|
|
51221
|
+
if (busy === 'enabling')
|
|
51222
|
+
return 'enabling';
|
|
51223
|
+
if (busy === 'disabling')
|
|
51224
|
+
return 'disabling';
|
|
51225
|
+
if (this.isEnabled())
|
|
51226
|
+
return 'enabled';
|
|
51227
|
+
if (this.permission() === 'denied')
|
|
51228
|
+
return 'blocked';
|
|
51229
|
+
if (this._lastFailed())
|
|
51230
|
+
return 'error';
|
|
51231
|
+
return 'idle';
|
|
51232
|
+
});
|
|
51233
|
+
this.busyLabel = computed(() => {
|
|
51234
|
+
const s = this.state();
|
|
51235
|
+
if (s === 'enabling')
|
|
51236
|
+
return this.tt('enabling');
|
|
51237
|
+
if (s === 'disabling')
|
|
51238
|
+
return this.tt('disabling');
|
|
51239
|
+
return null;
|
|
51240
|
+
});
|
|
51241
|
+
this.contextMessage = computed(() => {
|
|
51242
|
+
switch (this.state()) {
|
|
51243
|
+
case 'unsupported':
|
|
51244
|
+
return this.tt('permissionUnsupported');
|
|
51245
|
+
case 'blocked':
|
|
51246
|
+
return this.tt('permissionDeniedInfo');
|
|
51247
|
+
case 'error':
|
|
51248
|
+
return this.tt('errorGeneric');
|
|
51249
|
+
default:
|
|
51250
|
+
return null;
|
|
51251
|
+
}
|
|
51252
|
+
});
|
|
51253
|
+
this.alertVariant = computed(() => {
|
|
51254
|
+
const s = this.state();
|
|
51255
|
+
if (s === 'error' || s === 'blocked')
|
|
51256
|
+
return 'warning';
|
|
51257
|
+
if (s === 'unsupported')
|
|
51258
|
+
return 'danger';
|
|
51259
|
+
return 'info';
|
|
51260
|
+
});
|
|
51261
|
+
this.alertIcon = computed(() => {
|
|
51262
|
+
const s = this.state();
|
|
51263
|
+
if (s === 'unsupported')
|
|
51264
|
+
return 'close-circle-outline';
|
|
51265
|
+
return 'alert-circle-outline';
|
|
51266
|
+
});
|
|
51267
|
+
this.toggleProps = computed(() => ({
|
|
51268
|
+
control: this.toggleControl,
|
|
51269
|
+
color: 'primary',
|
|
51270
|
+
disabled: this._busy() !== null ||
|
|
51271
|
+
this.state() === 'unsupported' ||
|
|
51272
|
+
this.state() === 'needs-install' ||
|
|
51273
|
+
this.state() === 'blocked',
|
|
51274
|
+
}));
|
|
51275
|
+
this.retryButtonProps = computed(() => ({
|
|
51276
|
+
text: this.tt('retryCta'),
|
|
51277
|
+
color: 'dark',
|
|
51278
|
+
fill: 'outline',
|
|
51279
|
+
size: 'default',
|
|
51280
|
+
shape: 'round',
|
|
51281
|
+
type: 'button',
|
|
51282
|
+
icon: { name: 'refresh-outline', slot: 'start' },
|
|
51283
|
+
state: this._busy() !== null ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
51284
|
+
}));
|
|
51285
|
+
this.tokenPreview = computed(() => {
|
|
51286
|
+
const t = this.currentToken();
|
|
51287
|
+
if (!t)
|
|
51288
|
+
return null;
|
|
51289
|
+
return `${t.slice(0, 22)}…${t.slice(-6)}`;
|
|
51290
|
+
});
|
|
51291
|
+
const ns = this.ns;
|
|
51292
|
+
if (!this.i18n.hasNamespace(ns)) {
|
|
51293
|
+
this.i18n.registerContent(ns, NOTIFICATION_PREFERENCES_VIEW_I18N);
|
|
51294
|
+
}
|
|
51295
|
+
this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
|
|
51296
|
+
void this.refreshBrowserState();
|
|
51297
|
+
// Re-aplicar header en cambio de idioma.
|
|
51298
|
+
effect(() => {
|
|
51299
|
+
this.i18n.lang();
|
|
51300
|
+
this.nav.setBackHeader('pageTitle', this.ns, { withMenu: true });
|
|
51301
|
+
}, { allowSignalWrites: true });
|
|
51302
|
+
// Sync FormControl ←→ estado efectivo. Cuando el estado cambia (cambio de
|
|
51303
|
+
// permission por el browser, o snapshot Firestore de `master`), el toggle se
|
|
51304
|
+
// re-alinea con la realidad.
|
|
51305
|
+
//
|
|
51306
|
+
// IMPORTANTE: se ignoran los cambios mientras hay una operación en curso
|
|
51307
|
+
// (`_busy() !== null`). Durante `onEnable`/`onDisable` las señales fuente se
|
|
51308
|
+
// actualizan en momentos distintos y producirían estados intermedios →
|
|
51309
|
+
// flicker visible. El toggle se re-alinea UNA sola vez en el `finally`.
|
|
51310
|
+
effect(() => {
|
|
51311
|
+
const eff = this.isEnabled();
|
|
51312
|
+
if (this._busy() !== null)
|
|
51313
|
+
return;
|
|
51314
|
+
if (this.toggleControl.value !== eff) {
|
|
51315
|
+
this.toggleControl.setValue(eff, { emitEvent: false });
|
|
51316
|
+
}
|
|
51317
|
+
}, { allowSignalWrites: true });
|
|
51318
|
+
// Interceptar click del user: el toggle ya cambió visualmente, ejecutar
|
|
51319
|
+
// la acción correspondiente.
|
|
51320
|
+
this.toggleControl.valueChanges.subscribe(v => {
|
|
51321
|
+
if (this._busy() !== null)
|
|
51322
|
+
return;
|
|
51323
|
+
if (v && !this.isEnabled()) {
|
|
51324
|
+
void this.onEnable();
|
|
51325
|
+
}
|
|
51326
|
+
else if (!v && this.isEnabled()) {
|
|
51327
|
+
void this.onDisable();
|
|
51328
|
+
}
|
|
51329
|
+
});
|
|
51330
|
+
// Detectar regreso a la pestaña — el user pudo haber cambiado permisos
|
|
51331
|
+
// desde la config del browser. El refresh va con debounce para no
|
|
51332
|
+
// dispararse en ráfagas (en PWA mobile `focus` salta con cada cambio de
|
|
51333
|
+
// app / aparición del teclado). El listener se remueve en ngOnDestroy.
|
|
51334
|
+
if (typeof window !== 'undefined') {
|
|
51335
|
+
window.addEventListener('focus', this.onWindowFocus);
|
|
51336
|
+
}
|
|
51337
|
+
}
|
|
51338
|
+
/**
|
|
51339
|
+
* Registra el pull-to-refresh estándar. Aunque esta vista no muestra una
|
|
51340
|
+
* lista remota, sí refleja estado mutable del browser (permiso, token,
|
|
51341
|
+
* soporte FCM) que puede cambiar fuera de la app — el refresh re-evalúa todo.
|
|
51342
|
+
*
|
|
51343
|
+
* Se usa `ngOnInit`/`ngOnDestroy` (NO `ionViewWillEnter`) porque
|
|
51344
|
+
* `val-page-wrapper` usa `<router-outlet>` plano — los hooks `ionView*` solo
|
|
51345
|
+
* disparan dentro de `<ion-router-outlet>`.
|
|
51346
|
+
*/
|
|
51347
|
+
ngOnInit() {
|
|
51348
|
+
this.pageRefresh.register(() => this.refreshBrowserState());
|
|
51349
|
+
}
|
|
51350
|
+
ngOnDestroy() {
|
|
51351
|
+
this.pageRefresh.unregister();
|
|
51352
|
+
if (typeof window !== 'undefined') {
|
|
51353
|
+
window.removeEventListener('focus', this.onWindowFocus);
|
|
51354
|
+
}
|
|
51355
|
+
if (this.focusDebounceTimer !== null) {
|
|
51356
|
+
clearTimeout(this.focusDebounceTimer);
|
|
51357
|
+
this.focusDebounceTimer = null;
|
|
51358
|
+
}
|
|
51359
|
+
}
|
|
51360
|
+
/** Refresca el estado del browser con debounce — evita ráfagas de `focus`. */
|
|
51361
|
+
scheduleBrowserRefresh() {
|
|
51362
|
+
if (this.focusDebounceTimer !== null) {
|
|
51363
|
+
clearTimeout(this.focusDebounceTimer);
|
|
51364
|
+
}
|
|
51365
|
+
this.focusDebounceTimer = setTimeout(() => {
|
|
51366
|
+
this.focusDebounceTimer = null;
|
|
51367
|
+
void this.refreshBrowserState();
|
|
51368
|
+
}, this.FOCUS_DEBOUNCE_MS);
|
|
51369
|
+
}
|
|
51370
|
+
// ---------------------------------------------------------------------------
|
|
51371
|
+
// Actions
|
|
51372
|
+
// ---------------------------------------------------------------------------
|
|
51373
|
+
/**
|
|
51374
|
+
* Secuencia completa de activación.
|
|
51375
|
+
*
|
|
51376
|
+
* Toda la mecánica (permission → SW ready → getToken con retry → watchdog de
|
|
51377
|
+
* auto-reload) vive en `MessagingService.enable()` de la lib. El paso de
|
|
51378
|
+
* registro del device en el backend se pasa como callback (`registerDevice`)
|
|
51379
|
+
* porque la lib no puede depender de `AuthService` sin crear un ciclo.
|
|
51380
|
+
*
|
|
51381
|
+
* `enable()` no hace throw: resuelve con un `EnablePushResult` descriptivo.
|
|
51382
|
+
*/
|
|
51383
|
+
async onEnable() {
|
|
51384
|
+
this._busy.set('enabling');
|
|
51385
|
+
this._lastFailed.set(false);
|
|
51386
|
+
try {
|
|
51387
|
+
const result = await this.messaging.enable({
|
|
51388
|
+
registerDevice: token => this.auth.registerDevice(token).then(r => r.registered),
|
|
51389
|
+
});
|
|
51390
|
+
if (result.status === 'enabled') {
|
|
51391
|
+
// Persistir flag master si aún no estaba activo.
|
|
51392
|
+
if (!this.prefs.notificationsMaster()) {
|
|
51393
|
+
await this.prefs.setNotificationsMaster(true);
|
|
51394
|
+
}
|
|
51395
|
+
this.toast.show({
|
|
51396
|
+
message: this.tt('enableSuccess'),
|
|
51397
|
+
duration: 2000,
|
|
51398
|
+
color: 'dark',
|
|
51399
|
+
position: 'top',
|
|
51400
|
+
});
|
|
51401
|
+
this.resolvedConfig().onPushEnabled?.();
|
|
51402
|
+
}
|
|
51403
|
+
else if (result.status === 'timeout' && result.reloaded) {
|
|
51404
|
+
// La lib disparó el auto-reload — la página está por recargarse.
|
|
51405
|
+
return;
|
|
51406
|
+
}
|
|
51407
|
+
else {
|
|
51408
|
+
// denied / unsupported / error / timeout sin reload → fallo recuperable.
|
|
51409
|
+
this._lastFailed.set(true);
|
|
51410
|
+
this.toast.show({
|
|
51411
|
+
message: this.tt('errorGeneric'),
|
|
51412
|
+
duration: 3000,
|
|
51413
|
+
color: 'dark',
|
|
51414
|
+
position: 'top',
|
|
51415
|
+
});
|
|
51416
|
+
}
|
|
51417
|
+
}
|
|
51418
|
+
catch {
|
|
51419
|
+
// `enable()` no hace throw, pero el callback de registro sí puede.
|
|
51420
|
+
this._lastFailed.set(true);
|
|
51421
|
+
this.toast.show({
|
|
51422
|
+
message: this.tt('errorGeneric'),
|
|
51423
|
+
duration: 3000,
|
|
51424
|
+
color: 'dark',
|
|
51425
|
+
position: 'top',
|
|
51426
|
+
});
|
|
51427
|
+
}
|
|
51428
|
+
finally {
|
|
51429
|
+
// Único refresh final: re-alinea el toggle UNA sola vez con el estado real.
|
|
51430
|
+
await this.refreshBrowserState();
|
|
51431
|
+
this._busy.set(null);
|
|
51432
|
+
}
|
|
51433
|
+
}
|
|
51434
|
+
async onDisable() {
|
|
51435
|
+
this._busy.set('disabling');
|
|
51436
|
+
this._lastFailed.set(false);
|
|
51437
|
+
try {
|
|
51438
|
+
await this.auth.disableNotifications();
|
|
51439
|
+
if (this.prefs.notificationsMaster()) {
|
|
51440
|
+
await this.prefs.setNotificationsMaster(false);
|
|
51441
|
+
}
|
|
51442
|
+
this.toast.show({
|
|
51443
|
+
message: this.tt('disableSuccess'),
|
|
51444
|
+
duration: 2000,
|
|
51445
|
+
color: 'dark',
|
|
51446
|
+
position: 'top',
|
|
51447
|
+
});
|
|
51448
|
+
this.resolvedConfig().onPushDisabled?.();
|
|
51449
|
+
}
|
|
51450
|
+
catch {
|
|
51451
|
+
this._lastFailed.set(true);
|
|
51452
|
+
}
|
|
51453
|
+
finally {
|
|
51454
|
+
await this.refreshBrowserState();
|
|
51455
|
+
this._busy.set(null);
|
|
51456
|
+
}
|
|
51457
|
+
}
|
|
51458
|
+
/** Reintentar la secuencia completa de activación, sin importar el estado actual. */
|
|
51459
|
+
async onRetry() {
|
|
51460
|
+
this._lastFailed.set(false);
|
|
51461
|
+
await this.onEnable();
|
|
51462
|
+
}
|
|
51463
|
+
toggleDetails() {
|
|
51464
|
+
this.showDetails.update(v => !v);
|
|
51465
|
+
}
|
|
51466
|
+
// ---------------------------------------------------------------------------
|
|
51467
|
+
// Privados
|
|
51468
|
+
// ---------------------------------------------------------------------------
|
|
51469
|
+
async refreshBrowserState() {
|
|
51470
|
+
this.isSupported.set(await this.messaging.isSupported());
|
|
51471
|
+
this.permission.set(this.auth.getNotificationPermissionState());
|
|
51472
|
+
this.currentToken.set(this.messaging.currentToken ?? null);
|
|
51473
|
+
void this.repairTokenIfNeeded();
|
|
51474
|
+
}
|
|
51475
|
+
/**
|
|
51476
|
+
* Repara el token FCM en background.
|
|
51477
|
+
*
|
|
51478
|
+
* Si el usuario tiene push activo (`master && granted`) pero no hay token en
|
|
51479
|
+
* memoria — caso típico tras un cold start de la PWA — re-obtenemos el token
|
|
51480
|
+
* y re-registramos el device sin tocar el toggle ni mostrar un error
|
|
51481
|
+
* alarmista. El token es un detalle de implementación reparable, no parte del
|
|
51482
|
+
* estado de UI (ver `isEnabled`).
|
|
51483
|
+
*/
|
|
51484
|
+
async repairTokenIfNeeded() {
|
|
51485
|
+
if (this._busy() !== null)
|
|
51486
|
+
return;
|
|
51487
|
+
if (this._repairingToken)
|
|
51488
|
+
return;
|
|
51489
|
+
if (!this.isEnabled())
|
|
51490
|
+
return;
|
|
51491
|
+
if (this.currentToken())
|
|
51492
|
+
return;
|
|
51493
|
+
this._repairingToken = true;
|
|
51494
|
+
try {
|
|
51495
|
+
const token = await this.messaging.getToken();
|
|
51496
|
+
if (token) {
|
|
51497
|
+
this.currentToken.set(token);
|
|
51498
|
+
await this.auth.registerDevice(token);
|
|
51499
|
+
}
|
|
51500
|
+
}
|
|
51501
|
+
catch {
|
|
51502
|
+
// Reparación best-effort: no degradamos el toggle ni mostramos error.
|
|
51503
|
+
}
|
|
51504
|
+
finally {
|
|
51505
|
+
this._repairingToken = false;
|
|
51506
|
+
}
|
|
51507
|
+
}
|
|
51508
|
+
/**
|
|
51509
|
+
* iOS browser sin estar instalado como PWA — push web no disponible.
|
|
51510
|
+
* Aplica a Safari y a Edge/Chrome/Firefox en iOS (todos WebKit por mandato Apple).
|
|
51511
|
+
*/
|
|
51512
|
+
detectNeedsIOSInstall() {
|
|
51513
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
51514
|
+
return false;
|
|
51515
|
+
}
|
|
51516
|
+
const ua = navigator.userAgent || '';
|
|
51517
|
+
const isIOS = /iPad|iPhone|iPod/.test(ua);
|
|
51518
|
+
if (!isIOS)
|
|
51519
|
+
return false;
|
|
51520
|
+
const isStandalone = window.matchMedia?.('(display-mode: standalone)').matches ||
|
|
51521
|
+
navigator.standalone === true;
|
|
51522
|
+
return !isStandalone;
|
|
51523
|
+
}
|
|
51524
|
+
tt(key) {
|
|
51525
|
+
return this.i18n.t(key, this.ns);
|
|
51526
|
+
}
|
|
51527
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationPreferencesViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
51528
|
+
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: `
|
|
51529
|
+
<div class="page">
|
|
51530
|
+
<header class="page-header">
|
|
51531
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
|
|
51532
|
+
<val-title
|
|
51533
|
+
[props]="{
|
|
51534
|
+
size: 'large',
|
|
51535
|
+
color: 'dark',
|
|
51536
|
+
bold: false,
|
|
51537
|
+
content: pageDescription(),
|
|
51538
|
+
}"
|
|
51539
|
+
/>
|
|
51540
|
+
</header>
|
|
51541
|
+
|
|
51542
|
+
@if (state() === 'needs-install') {
|
|
51543
|
+
<section class="settings-section">
|
|
51544
|
+
<val-title
|
|
51545
|
+
[props]="{
|
|
51546
|
+
size: 'medium',
|
|
51547
|
+
color: 'dark',
|
|
51548
|
+
bold: true,
|
|
51549
|
+
content: tt('iosInstallTitle'),
|
|
51550
|
+
}"
|
|
51551
|
+
/>
|
|
51552
|
+
<div class="section-body">
|
|
51553
|
+
<val-text
|
|
51554
|
+
[props]="{
|
|
51555
|
+
size: 'small',
|
|
51556
|
+
color: 'medium',
|
|
51557
|
+
bold: false,
|
|
51558
|
+
content: tt('iosInstallHint'),
|
|
51559
|
+
}"
|
|
51560
|
+
/>
|
|
51561
|
+
<ol class="install-steps">
|
|
51562
|
+
<li>{{ tt('iosInstallStep1') }}</li>
|
|
51563
|
+
<li>{{ tt('iosInstallStep2') }}</li>
|
|
51564
|
+
<li>{{ tt('iosInstallStep3') }}</li>
|
|
51565
|
+
<li>{{ tt('iosInstallStep4') }}</li>
|
|
51566
|
+
</ol>
|
|
51567
|
+
</div>
|
|
51568
|
+
</section>
|
|
51569
|
+
}
|
|
51570
|
+
|
|
51571
|
+
<section class="settings-section">
|
|
51572
|
+
<div class="notif-row">
|
|
51573
|
+
<div class="notif-row__icon">
|
|
51574
|
+
<ion-icon name="notifications-outline" aria-hidden="true" />
|
|
51575
|
+
</div>
|
|
51576
|
+
<div class="notif-row__body">
|
|
51577
|
+
<val-title
|
|
51578
|
+
[props]="{
|
|
51579
|
+
size: 'medium',
|
|
51580
|
+
color: 'dark',
|
|
51581
|
+
bold: true,
|
|
51582
|
+
content: tt('simpleTitle'),
|
|
51583
|
+
}"
|
|
51584
|
+
/>
|
|
51585
|
+
<val-text
|
|
51586
|
+
[props]="{
|
|
51587
|
+
size: 'small',
|
|
51588
|
+
color: 'medium',
|
|
51589
|
+
bold: false,
|
|
51590
|
+
content: tt('simpleDescription'),
|
|
51591
|
+
}"
|
|
51592
|
+
/>
|
|
51593
|
+
@if (busyLabel(); as label) {
|
|
51594
|
+
<span class="notif-row__busy">{{ label }}</span>
|
|
51595
|
+
}
|
|
51596
|
+
</div>
|
|
51597
|
+
<div class="notif-row__control">
|
|
51598
|
+
<val-toggle-input [props]="toggleProps()" />
|
|
51599
|
+
</div>
|
|
51600
|
+
</div>
|
|
51601
|
+
|
|
51602
|
+
@if (contextMessage(); as msg) {
|
|
51603
|
+
<div class="alert" [class]="'alert--' + alertVariant()">
|
|
51604
|
+
<ion-icon [name]="alertIcon()" aria-hidden="true" />
|
|
51605
|
+
<span>{{ msg }}</span>
|
|
51606
|
+
</div>
|
|
51607
|
+
}
|
|
51608
|
+
|
|
51609
|
+
@if (state() === 'error') {
|
|
51610
|
+
<div class="row-actions">
|
|
51611
|
+
<val-button [props]="retryButtonProps()" (click)="onRetry()" />
|
|
51612
|
+
</div>
|
|
51613
|
+
}
|
|
51614
|
+
</section>
|
|
51615
|
+
|
|
51616
|
+
@if (resolvedConfig().showDetails && state() !== 'needs-install' && state() !== 'unsupported') {
|
|
51617
|
+
<section class="settings-section">
|
|
51618
|
+
<button class="details-toggle" type="button" (click)="toggleDetails()">
|
|
51619
|
+
<span>{{ showDetails() ? tt('detailsHide') : tt('detailsShow') }}</span>
|
|
51620
|
+
<ion-icon [name]="showDetails() ? 'chevron-up-outline' : 'chevron-down-outline'" aria-hidden="true" />
|
|
51621
|
+
</button>
|
|
51622
|
+
@if (showDetails()) {
|
|
51623
|
+
<dl class="details">
|
|
51624
|
+
<div class="details__row">
|
|
51625
|
+
<dt>{{ tt('detailsToken') }}</dt>
|
|
51626
|
+
<dd>{{ tokenPreview() ?? tt('detailsNone') }}</dd>
|
|
51627
|
+
</div>
|
|
51628
|
+
<div class="details__row">
|
|
51629
|
+
<dt>{{ tt('diagnosticsPermission') }}</dt>
|
|
51630
|
+
<dd>{{ permission() }}</dd>
|
|
51631
|
+
</div>
|
|
51632
|
+
<div class="details__row">
|
|
51633
|
+
<dt>{{ tt('diagnosticsSupported') }}</dt>
|
|
51634
|
+
<dd>{{ isSupported() ? '✓' : '✗' }}</dd>
|
|
51635
|
+
</div>
|
|
51636
|
+
</dl>
|
|
51637
|
+
}
|
|
51638
|
+
</section>
|
|
51639
|
+
}
|
|
51640
|
+
</div>
|
|
51641
|
+
`, 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"] }] }); }
|
|
51642
|
+
}
|
|
51643
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationPreferencesViewComponent, decorators: [{
|
|
51644
|
+
type: Component,
|
|
51645
|
+
args: [{ selector: 'val-notification-preferences-view', standalone: true, imports: [IonIcon, ToggleInputComponent, ButtonComponent, DisplayComponent, TitleComponent, TextComponent], template: `
|
|
51646
|
+
<div class="page">
|
|
51647
|
+
<header class="page-header">
|
|
51648
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
|
|
51649
|
+
<val-title
|
|
51650
|
+
[props]="{
|
|
51651
|
+
size: 'large',
|
|
51652
|
+
color: 'dark',
|
|
51653
|
+
bold: false,
|
|
51654
|
+
content: pageDescription(),
|
|
51655
|
+
}"
|
|
51656
|
+
/>
|
|
51657
|
+
</header>
|
|
51658
|
+
|
|
51659
|
+
@if (state() === 'needs-install') {
|
|
51660
|
+
<section class="settings-section">
|
|
51661
|
+
<val-title
|
|
51662
|
+
[props]="{
|
|
51663
|
+
size: 'medium',
|
|
51664
|
+
color: 'dark',
|
|
51665
|
+
bold: true,
|
|
51666
|
+
content: tt('iosInstallTitle'),
|
|
51667
|
+
}"
|
|
51668
|
+
/>
|
|
51669
|
+
<div class="section-body">
|
|
51670
|
+
<val-text
|
|
51671
|
+
[props]="{
|
|
51672
|
+
size: 'small',
|
|
51673
|
+
color: 'medium',
|
|
51674
|
+
bold: false,
|
|
51675
|
+
content: tt('iosInstallHint'),
|
|
51676
|
+
}"
|
|
51677
|
+
/>
|
|
51678
|
+
<ol class="install-steps">
|
|
51679
|
+
<li>{{ tt('iosInstallStep1') }}</li>
|
|
51680
|
+
<li>{{ tt('iosInstallStep2') }}</li>
|
|
51681
|
+
<li>{{ tt('iosInstallStep3') }}</li>
|
|
51682
|
+
<li>{{ tt('iosInstallStep4') }}</li>
|
|
51683
|
+
</ol>
|
|
51684
|
+
</div>
|
|
51685
|
+
</section>
|
|
51686
|
+
}
|
|
51687
|
+
|
|
51688
|
+
<section class="settings-section">
|
|
51689
|
+
<div class="notif-row">
|
|
51690
|
+
<div class="notif-row__icon">
|
|
51691
|
+
<ion-icon name="notifications-outline" aria-hidden="true" />
|
|
51692
|
+
</div>
|
|
51693
|
+
<div class="notif-row__body">
|
|
51694
|
+
<val-title
|
|
51695
|
+
[props]="{
|
|
51696
|
+
size: 'medium',
|
|
51697
|
+
color: 'dark',
|
|
51698
|
+
bold: true,
|
|
51699
|
+
content: tt('simpleTitle'),
|
|
51700
|
+
}"
|
|
51701
|
+
/>
|
|
51702
|
+
<val-text
|
|
51703
|
+
[props]="{
|
|
51704
|
+
size: 'small',
|
|
51705
|
+
color: 'medium',
|
|
51706
|
+
bold: false,
|
|
51707
|
+
content: tt('simpleDescription'),
|
|
51708
|
+
}"
|
|
51709
|
+
/>
|
|
51710
|
+
@if (busyLabel(); as label) {
|
|
51711
|
+
<span class="notif-row__busy">{{ label }}</span>
|
|
51712
|
+
}
|
|
51713
|
+
</div>
|
|
51714
|
+
<div class="notif-row__control">
|
|
51715
|
+
<val-toggle-input [props]="toggleProps()" />
|
|
51716
|
+
</div>
|
|
51717
|
+
</div>
|
|
51718
|
+
|
|
51719
|
+
@if (contextMessage(); as msg) {
|
|
51720
|
+
<div class="alert" [class]="'alert--' + alertVariant()">
|
|
51721
|
+
<ion-icon [name]="alertIcon()" aria-hidden="true" />
|
|
51722
|
+
<span>{{ msg }}</span>
|
|
51723
|
+
</div>
|
|
51724
|
+
}
|
|
51725
|
+
|
|
51726
|
+
@if (state() === 'error') {
|
|
51727
|
+
<div class="row-actions">
|
|
51728
|
+
<val-button [props]="retryButtonProps()" (click)="onRetry()" />
|
|
51729
|
+
</div>
|
|
51730
|
+
}
|
|
51731
|
+
</section>
|
|
51732
|
+
|
|
51733
|
+
@if (resolvedConfig().showDetails && state() !== 'needs-install' && state() !== 'unsupported') {
|
|
51734
|
+
<section class="settings-section">
|
|
51735
|
+
<button class="details-toggle" type="button" (click)="toggleDetails()">
|
|
51736
|
+
<span>{{ showDetails() ? tt('detailsHide') : tt('detailsShow') }}</span>
|
|
51737
|
+
<ion-icon [name]="showDetails() ? 'chevron-up-outline' : 'chevron-down-outline'" aria-hidden="true" />
|
|
51738
|
+
</button>
|
|
51739
|
+
@if (showDetails()) {
|
|
51740
|
+
<dl class="details">
|
|
51741
|
+
<div class="details__row">
|
|
51742
|
+
<dt>{{ tt('detailsToken') }}</dt>
|
|
51743
|
+
<dd>{{ tokenPreview() ?? tt('detailsNone') }}</dd>
|
|
51744
|
+
</div>
|
|
51745
|
+
<div class="details__row">
|
|
51746
|
+
<dt>{{ tt('diagnosticsPermission') }}</dt>
|
|
51747
|
+
<dd>{{ permission() }}</dd>
|
|
51748
|
+
</div>
|
|
51749
|
+
<div class="details__row">
|
|
51750
|
+
<dt>{{ tt('diagnosticsSupported') }}</dt>
|
|
51751
|
+
<dd>{{ isSupported() ? '✓' : '✗' }}</dd>
|
|
51752
|
+
</div>
|
|
51753
|
+
</dl>
|
|
51754
|
+
}
|
|
51755
|
+
</section>
|
|
51756
|
+
}
|
|
51757
|
+
</div>
|
|
51758
|
+
`, 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"] }]
|
|
51759
|
+
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
51760
|
+
type: Input
|
|
51761
|
+
}] } });
|
|
51762
|
+
|
|
51763
|
+
/**
|
|
51764
|
+
* Helper para montar las **preferencias de notificaciones push**
|
|
51765
|
+
* (`val-notification-preferences-view`) como ruta en una app del factory. El
|
|
51766
|
+
* `config` se pasa por route `data` (`notificationPreferencesConfig`) y el
|
|
51767
|
+
* componente lo lee como fallback de su `@Input() config`.
|
|
51768
|
+
*
|
|
51769
|
+
* No confundir con `provideValtechNotificationsRoutes` (el inbox / feed).
|
|
51770
|
+
*
|
|
51771
|
+
* @example
|
|
51772
|
+
* ```ts
|
|
51773
|
+
* // app.routes.ts (típicamente bajo settings)
|
|
51774
|
+
* export const settingsRoutes: Routes = [
|
|
51775
|
+
* ...provideValtechNotificationPreferencesRoutes(), // → notifications
|
|
51776
|
+
* ];
|
|
51777
|
+
*
|
|
51778
|
+
* // con config acotada:
|
|
51779
|
+
* ...provideValtechNotificationPreferencesRoutes({ config: { showDetails: false } }),
|
|
51780
|
+
* ...provideValtechNotificationPreferencesRoutes({ path: 'push' }),
|
|
51781
|
+
* ```
|
|
51782
|
+
*/
|
|
51783
|
+
function provideValtechNotificationPreferencesRoutes(opts) {
|
|
51784
|
+
return [
|
|
51785
|
+
{
|
|
51786
|
+
path: opts?.path ?? 'notifications',
|
|
51787
|
+
component: NotificationPreferencesViewComponent,
|
|
51788
|
+
data: { notificationPreferencesConfig: opts?.config },
|
|
51789
|
+
},
|
|
51790
|
+
];
|
|
51791
|
+
}
|
|
51792
|
+
|
|
51793
|
+
/**
|
|
51794
|
+
* Consume el catálogo de permisos del backend (`GET /org/{orgId}/permissions-catalog`).
|
|
51795
|
+
* El backend es la fuente de verdad de qué permisos existen y de su alcance
|
|
51796
|
+
* (globales de plataforma vs. propios de una app). La respuesta incluye el
|
|
51797
|
+
* `appId` del request para que la vista distinga la app actual.
|
|
51798
|
+
*
|
|
51799
|
+
* Promovido desde `showcase` a la lib bajo el proceso de ADR-021. Usa
|
|
51800
|
+
* `VALTECH_AUTH_CONFIG.apiUrl` (como `OrgService`) en vez del `environment` de la
|
|
51801
|
+
* app — la lib no importa el environment del consumer.
|
|
51802
|
+
*/
|
|
51803
|
+
class PermissionCatalogService {
|
|
51804
|
+
constructor(config, http) {
|
|
51805
|
+
this.config = config;
|
|
51806
|
+
this.http = http;
|
|
51807
|
+
}
|
|
51808
|
+
getCatalog(orgId) {
|
|
51809
|
+
return this.http.get(`${this.config.apiUrl}/org/${orgId}/permissions-catalog`);
|
|
51810
|
+
}
|
|
51811
|
+
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 }); }
|
|
51812
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionCatalogService, providedIn: 'root' }); }
|
|
51813
|
+
}
|
|
51814
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionCatalogService, decorators: [{
|
|
51815
|
+
type: Injectable,
|
|
51816
|
+
args: [{ providedIn: 'root' }]
|
|
51817
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
51818
|
+
type: Inject,
|
|
51819
|
+
args: [VALTECH_AUTH_CONFIG]
|
|
51820
|
+
}] }, { type: i1$3.HttpClient }] });
|
|
51821
|
+
|
|
51822
|
+
/**
|
|
51823
|
+
* Defaults i18n (es/en) embebidos en `val-permissions-view`. Auto-registrados en
|
|
51824
|
+
* el constructor del componente si el consumer no proveyó el namespace
|
|
51825
|
+
* (`Settings.Permissions` por default). SOLO incluye las keys que la vista usa.
|
|
51826
|
+
*
|
|
51827
|
+
* Portado EXACTO de `PERMISSIONS_I18N` (showcase) bajo el proceso de ADR-021.
|
|
51828
|
+
*/
|
|
51829
|
+
const PERMISSIONS_VIEW_I18N = {
|
|
51830
|
+
es: {
|
|
51831
|
+
pageTitle: 'Permisos',
|
|
51832
|
+
pageDescription: 'Roles y permisos de esta app en la organización activa',
|
|
51833
|
+
systemRolesTitle: 'Roles del sistema',
|
|
51834
|
+
systemRolesHint: 'Predefinidos por la plataforma. No editables.',
|
|
51835
|
+
customRolesTitle: 'Roles personalizados',
|
|
51836
|
+
customRolesHint: 'Definidos por tu organización.',
|
|
51837
|
+
systemBadge: 'Sistema',
|
|
51838
|
+
noCustomRoles: 'Esta organización no tiene roles personalizados.',
|
|
51839
|
+
noPermissions: 'Sin permisos asignados.',
|
|
51840
|
+
// Grupos de alcance
|
|
51841
|
+
groupApp: 'Permisos de la app',
|
|
51842
|
+
groupPlatform: 'Permisos de plataforma',
|
|
51843
|
+
groupOther: 'Otras apps',
|
|
51844
|
+
// Roles del sistema
|
|
51845
|
+
roleViewer: 'Visor',
|
|
51846
|
+
roleEditor: 'Editor',
|
|
51847
|
+
roleAdmin: 'Administrador',
|
|
51848
|
+
roleSuperAdmin: 'Super Administrador',
|
|
51849
|
+
roleBingoAdmin: 'Bingo · Administrador',
|
|
51850
|
+
roleBingoTesorero: 'Bingo · Tesorero',
|
|
51851
|
+
roleBingoTaquilla: 'Bingo · Taquilla',
|
|
51852
|
+
roleBingoOperator: 'Bingo · Operador',
|
|
51853
|
+
// Apps
|
|
51854
|
+
app_showcase: 'Showcase',
|
|
51855
|
+
app_bingo: 'Bingo',
|
|
51856
|
+
// Recursos
|
|
51857
|
+
permUsers: 'Usuarios',
|
|
51858
|
+
permRoles: 'Roles',
|
|
51859
|
+
permRbac: 'Control de acceso',
|
|
51860
|
+
permApps: 'Apps',
|
|
51861
|
+
'permApi-keys': 'API keys',
|
|
51862
|
+
'permEmail-logs': 'Logs de correo',
|
|
51863
|
+
'permSms-logs': 'Logs de SMS',
|
|
51864
|
+
permDocuments: 'Documentos',
|
|
51865
|
+
permMedia: 'Media',
|
|
51866
|
+
permTemplates: 'Plantillas',
|
|
51867
|
+
permRequests: 'Solicitudes',
|
|
51868
|
+
permBingo: 'Bingo',
|
|
51869
|
+
// Acciones
|
|
51870
|
+
permRead: 'Leer',
|
|
51871
|
+
permWrite: 'Escribir',
|
|
51872
|
+
permCreate: 'Crear',
|
|
51873
|
+
permDelete: 'Eliminar',
|
|
51874
|
+
permManage: 'Gestionar',
|
|
51875
|
+
permEdit: 'Editar',
|
|
51876
|
+
permSell: 'Vender',
|
|
51877
|
+
permCheckin: 'Canjear',
|
|
51878
|
+
permSettle: 'Rendir',
|
|
51879
|
+
permExport: 'Exportar',
|
|
51880
|
+
permOperate: 'Operar',
|
|
51881
|
+
permAll: 'Todo',
|
|
51882
|
+
// Loading / error
|
|
51883
|
+
errorTitle: 'Error al cargar permisos',
|
|
51884
|
+
retry: 'Reintentar',
|
|
51885
|
+
},
|
|
51886
|
+
en: {
|
|
51887
|
+
pageTitle: 'Permissions',
|
|
51888
|
+
pageDescription: 'Roles and permissions of this app in the active organization',
|
|
51889
|
+
systemRolesTitle: 'System roles',
|
|
51890
|
+
systemRolesHint: 'Defined by the platform. Not editable.',
|
|
51891
|
+
customRolesTitle: 'Custom roles',
|
|
51892
|
+
customRolesHint: 'Defined by your organization.',
|
|
51893
|
+
systemBadge: 'System',
|
|
51894
|
+
noCustomRoles: 'This organization has no custom roles.',
|
|
51895
|
+
noPermissions: 'No permissions assigned.',
|
|
51896
|
+
groupApp: 'App permissions',
|
|
51897
|
+
groupPlatform: 'Platform permissions',
|
|
51898
|
+
groupOther: 'Other apps',
|
|
51899
|
+
roleViewer: 'Viewer',
|
|
51900
|
+
roleEditor: 'Editor',
|
|
51901
|
+
roleAdmin: 'Administrator',
|
|
51902
|
+
roleSuperAdmin: 'Super Administrator',
|
|
51903
|
+
roleBingoAdmin: 'Bingo · Administrator',
|
|
51904
|
+
roleBingoTesorero: 'Bingo · Treasurer',
|
|
51905
|
+
roleBingoTaquilla: 'Bingo · Box office',
|
|
51906
|
+
roleBingoOperator: 'Bingo · Operator',
|
|
51907
|
+
app_showcase: 'Showcase',
|
|
51908
|
+
app_bingo: 'Bingo',
|
|
51909
|
+
permUsers: 'Users',
|
|
51910
|
+
permRoles: 'Roles',
|
|
51911
|
+
permRbac: 'Access control',
|
|
51912
|
+
permApps: 'Apps',
|
|
51913
|
+
'permApi-keys': 'API keys',
|
|
51914
|
+
'permEmail-logs': 'Email logs',
|
|
51915
|
+
'permSms-logs': 'SMS logs',
|
|
51916
|
+
permDocuments: 'Documents',
|
|
51917
|
+
permMedia: 'Media',
|
|
51918
|
+
permTemplates: 'Templates',
|
|
51919
|
+
permRequests: 'Requests',
|
|
51920
|
+
permBingo: 'Bingo',
|
|
51921
|
+
permRead: 'Read',
|
|
51922
|
+
permWrite: 'Write',
|
|
51923
|
+
permCreate: 'Create',
|
|
51924
|
+
permDelete: 'Delete',
|
|
51925
|
+
permManage: 'Manage',
|
|
51926
|
+
permEdit: 'Edit',
|
|
51927
|
+
permSell: 'Sell',
|
|
51928
|
+
permCheckin: 'Check-in',
|
|
51929
|
+
permSettle: 'Settle',
|
|
51930
|
+
permExport: 'Export',
|
|
51931
|
+
permOperate: 'Operate',
|
|
51932
|
+
permAll: 'All',
|
|
51933
|
+
errorTitle: 'Error loading permissions',
|
|
51934
|
+
retry: 'Retry',
|
|
51935
|
+
},
|
|
51936
|
+
};
|
|
51937
|
+
|
|
51938
|
+
const DEFAULT_NAMESPACE = 'Settings.Permissions';
|
|
51939
|
+
/**
|
|
51940
|
+
* `val-permissions-view` — vista Permisos full-feature autocontenida (organism).
|
|
51941
|
+
* Visor RBAC de solo lectura: muestra los roles del sistema (Visor/Editor/Admin +
|
|
51942
|
+
* roles de plataforma) y los roles personalizados de la org activa, con el
|
|
51943
|
+
* catálogo de permisos agrupado por alcance (app actual / plataforma / otras apps).
|
|
51944
|
+
* Promovida desde `showcase` bajo el proceso de ADR-021.
|
|
51945
|
+
*
|
|
51946
|
+
* **RBAC interno** (como `organization-view`): lee la org activa vía
|
|
51947
|
+
* `AuthService.user().activeOrgId`. La vista NO muta nada — el control de quién
|
|
51948
|
+
* puede verla es responsabilidad del guard de ruta del consumer; no hay gating
|
|
51949
|
+
* fino interno ni se expone como config.
|
|
51950
|
+
*
|
|
51951
|
+
* Datos: `forkJoin` de `OrgService.listOrgRoles(orgId)` (roles de la org) +
|
|
51952
|
+
* `PermissionCatalogService.getCatalog(orgId)` (catálogo + appId actual). El
|
|
51953
|
+
* catálogo define el alcance (`scope`/`appId`) de cada recurso para agrupar.
|
|
51954
|
+
*
|
|
51955
|
+
* NO renderiza ion-content — vive dentro de val-page-wrapper. `ActivatedRoute` se
|
|
51956
|
+
* inyecta `{ optional: true }` solo para leer el route data de config. Auto-registra
|
|
51957
|
+
* sus defaults i18n (es/en) si el consumer no proveyó el namespace (default
|
|
51958
|
+
* `Settings.Permissions`).
|
|
51959
|
+
*/
|
|
51960
|
+
class PermissionsViewComponent {
|
|
51961
|
+
/** Namespace i18n resuelto (capturado para llamadas no-reactivas). */
|
|
51962
|
+
get ns() {
|
|
51963
|
+
return this.resolvedConfig().i18nNamespace;
|
|
51964
|
+
}
|
|
51965
|
+
constructor() {
|
|
51966
|
+
this.i18n = inject(I18nService);
|
|
51967
|
+
this.orgService = inject(OrgService);
|
|
51968
|
+
this.catalog = inject(PermissionCatalogService);
|
|
51969
|
+
this.auth = inject(AuthService);
|
|
51970
|
+
this.nav = inject(NavigationService);
|
|
51971
|
+
this.route = inject(ActivatedRoute, { optional: true });
|
|
51972
|
+
this.resolvedConfig = computed(() => {
|
|
51973
|
+
const fromRoute = (this.route?.snapshot.data['permissionsConfig'] ?? {});
|
|
51974
|
+
const merged = { ...fromRoute, ...(this.config ?? {}) };
|
|
51975
|
+
return {
|
|
51976
|
+
showSystemRoles: merged.showSystemRoles ?? true,
|
|
51977
|
+
showCustomRoles: merged.showCustomRoles ?? true,
|
|
51978
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
51979
|
+
};
|
|
51980
|
+
});
|
|
51981
|
+
this.loading = signal(false);
|
|
51982
|
+
this.loadError = signal(null);
|
|
51983
|
+
this.roles = signal([]);
|
|
51984
|
+
this.systemRoles = computed(() => this.roles().filter(r => r.isSystem));
|
|
51985
|
+
this.customRoles = computed(() => this.roles().filter(r => !r.isSystem));
|
|
51986
|
+
this.activeOrgId = computed(() => {
|
|
51987
|
+
const u = this.auth.user();
|
|
51988
|
+
return u?.activeOrgId ?? u?.activeOrg ?? '';
|
|
51989
|
+
});
|
|
51990
|
+
this.retryButtonProps = computed(() => ({
|
|
51991
|
+
text: this.tt('retry'),
|
|
51992
|
+
color: 'dark',
|
|
51993
|
+
fill: 'outline',
|
|
51994
|
+
size: 'default',
|
|
51995
|
+
type: 'button',
|
|
51996
|
+
state: this.loading() ? ComponentStates.DISABLED : ComponentStates.ENABLED,
|
|
51997
|
+
}));
|
|
51998
|
+
const ns = this.ns;
|
|
51999
|
+
if (!this.i18n.hasNamespace(ns)) {
|
|
52000
|
+
this.i18n.registerContent(ns, PERMISSIONS_VIEW_I18N);
|
|
52001
|
+
}
|
|
52002
|
+
this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
|
|
52003
|
+
this.load();
|
|
52004
|
+
connectPageRefresh(() => this.load());
|
|
52005
|
+
}
|
|
52006
|
+
load() {
|
|
52007
|
+
const orgId = this.activeOrgId();
|
|
52008
|
+
if (!orgId)
|
|
52009
|
+
return;
|
|
52010
|
+
this.loading.set(true);
|
|
52011
|
+
this.loadError.set(null);
|
|
52012
|
+
forkJoin({
|
|
52013
|
+
roles: this.orgService.listOrgRoles(orgId),
|
|
52014
|
+
catalog: this.catalog.getCatalog(orgId),
|
|
52015
|
+
}).subscribe({
|
|
52016
|
+
next: ({ roles: rawRoles, catalog }) => {
|
|
52017
|
+
const byResource = new Map();
|
|
52018
|
+
for (const r of catalog.permissions)
|
|
52019
|
+
byResource.set(r.resource, r);
|
|
52020
|
+
const currentAppId = catalog.appId;
|
|
52021
|
+
this.roles.set((rawRoles ?? []).map((r) => this.buildRoleView({
|
|
52022
|
+
id: r.id,
|
|
52023
|
+
name: r.name,
|
|
52024
|
+
description: r.description,
|
|
52025
|
+
permissions: r.permissions ?? [],
|
|
52026
|
+
isSystem: r.isSystem ?? false,
|
|
52027
|
+
}, byResource, currentAppId)));
|
|
52028
|
+
this.loading.set(false);
|
|
52029
|
+
},
|
|
52030
|
+
error: err => {
|
|
52031
|
+
this.loadError.set(err);
|
|
52032
|
+
this.loading.set(false);
|
|
52033
|
+
},
|
|
52034
|
+
});
|
|
52035
|
+
}
|
|
52036
|
+
buildRoleView(role, byResource, currentAppId) {
|
|
52037
|
+
const appPerms = [];
|
|
52038
|
+
const platformPerms = [];
|
|
52039
|
+
const otherByApp = new Map();
|
|
52040
|
+
for (const perm of role.permissions) {
|
|
52041
|
+
const [resource, action] = perm.split(':');
|
|
52042
|
+
const label = this.permLabel(resource, action);
|
|
52043
|
+
const entry = byResource.get(resource);
|
|
52044
|
+
if (entry?.scope === 'app') {
|
|
52045
|
+
if (entry.appId === currentAppId) {
|
|
52046
|
+
appPerms.push(label);
|
|
52047
|
+
}
|
|
52048
|
+
else {
|
|
52049
|
+
const appId = entry.appId ?? '?';
|
|
52050
|
+
const arr = otherByApp.get(appId) ?? [];
|
|
52051
|
+
arr.push(label);
|
|
52052
|
+
otherByApp.set(appId, arr);
|
|
52053
|
+
}
|
|
52054
|
+
}
|
|
52055
|
+
else {
|
|
52056
|
+
// global o desconocido → plataforma
|
|
52057
|
+
platformPerms.push(label);
|
|
52058
|
+
}
|
|
52059
|
+
}
|
|
52060
|
+
const groups = [];
|
|
52061
|
+
if (appPerms.length) {
|
|
52062
|
+
groups.push({ key: 'app', label: this.tt('groupApp'), perms: appPerms });
|
|
52063
|
+
}
|
|
52064
|
+
if (platformPerms.length) {
|
|
52065
|
+
groups.push({
|
|
52066
|
+
key: 'platform',
|
|
52067
|
+
label: this.tt('groupPlatform'),
|
|
52068
|
+
perms: platformPerms,
|
|
52069
|
+
});
|
|
52070
|
+
}
|
|
52071
|
+
for (const [appId, perms] of otherByApp) {
|
|
52072
|
+
groups.push({
|
|
52073
|
+
key: `other:${appId}`,
|
|
52074
|
+
label: `${this.tt('groupOther')}: ${this.appLabel(appId)}`,
|
|
52075
|
+
perms,
|
|
52076
|
+
});
|
|
52077
|
+
}
|
|
52078
|
+
return {
|
|
52079
|
+
id: role.id,
|
|
52080
|
+
name: role.name,
|
|
52081
|
+
description: role.description,
|
|
52082
|
+
isSystem: role.isSystem,
|
|
52083
|
+
groups,
|
|
52084
|
+
hasPerms: groups.length > 0,
|
|
52085
|
+
};
|
|
52086
|
+
}
|
|
52087
|
+
tt(key) {
|
|
52088
|
+
return this.i18n.t(key, this.ns);
|
|
52089
|
+
}
|
|
52090
|
+
appLabel(appId) {
|
|
52091
|
+
const key = `app_${appId}`;
|
|
52092
|
+
const label = this.tt(key);
|
|
52093
|
+
return label === key ? appId : label;
|
|
52094
|
+
}
|
|
52095
|
+
roleLabel(name) {
|
|
52096
|
+
const map = {
|
|
52097
|
+
viewer: 'roleViewer',
|
|
52098
|
+
editor: 'roleEditor',
|
|
52099
|
+
admin: 'roleAdmin',
|
|
52100
|
+
super_admin: 'roleSuperAdmin',
|
|
52101
|
+
bingo_admin: 'roleBingoAdmin',
|
|
52102
|
+
bingo_tesorero: 'roleBingoTesorero',
|
|
52103
|
+
bingo_taquilla: 'roleBingoTaquilla',
|
|
52104
|
+
bingo_operator: 'roleBingoOperator',
|
|
52105
|
+
};
|
|
52106
|
+
return this.tt(map[name] ?? name);
|
|
52107
|
+
}
|
|
52108
|
+
permLabel(resource, action) {
|
|
52109
|
+
if (resource === '*' && action === '*')
|
|
52110
|
+
return this.tt('permAll');
|
|
52111
|
+
const resKey = resource === '*' ? 'permAll' : `perm${resource.charAt(0).toUpperCase()}${resource.slice(1)}`;
|
|
52112
|
+
const actKey = action === '*' ? 'permAll' : `perm${action.charAt(0).toUpperCase()}${action.slice(1)}`;
|
|
52113
|
+
const res = this.tt(resKey);
|
|
52114
|
+
const act = this.tt(actKey);
|
|
52115
|
+
return `${res}: ${act}`;
|
|
52116
|
+
}
|
|
52117
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionsViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
52118
|
+
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: `
|
|
52119
|
+
<div class="page">
|
|
52120
|
+
<header class="page-header">
|
|
52121
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: tt('pageTitle') }" />
|
|
52122
|
+
<val-title
|
|
52123
|
+
[props]="{
|
|
52124
|
+
size: 'large',
|
|
52125
|
+
color: 'dark',
|
|
52126
|
+
bold: false,
|
|
52127
|
+
content: tt('pageDescription'),
|
|
52128
|
+
}"
|
|
52129
|
+
/>
|
|
52130
|
+
</header>
|
|
52131
|
+
|
|
52132
|
+
@if (loading()) {
|
|
52133
|
+
<div class="spinner-row">
|
|
52134
|
+
<ion-spinner name="crescent" />
|
|
52135
|
+
</div>
|
|
52136
|
+
} @else if (loadError()) {
|
|
52137
|
+
<div class="error-row">
|
|
52138
|
+
<val-text
|
|
52139
|
+
[props]="{
|
|
52140
|
+
content: tt('errorTitle'),
|
|
52141
|
+
size: 'medium',
|
|
52142
|
+
color: 'medium',
|
|
52143
|
+
bold: false,
|
|
52144
|
+
}"
|
|
52145
|
+
/>
|
|
52146
|
+
<val-button [props]="retryButtonProps()" (click)="load()" />
|
|
52147
|
+
</div>
|
|
52148
|
+
} @else {
|
|
52149
|
+
@if (resolvedConfig().showSystemRoles) {
|
|
52150
|
+
<section class="perm-section">
|
|
52151
|
+
<div class="perm-section__header">
|
|
52152
|
+
<val-title
|
|
52153
|
+
[props]="{
|
|
52154
|
+
size: 'medium',
|
|
52155
|
+
color: 'dark',
|
|
52156
|
+
bold: true,
|
|
52157
|
+
content: tt('systemRolesTitle'),
|
|
52158
|
+
}"
|
|
52159
|
+
/>
|
|
52160
|
+
<val-text
|
|
52161
|
+
[props]="{
|
|
52162
|
+
content: tt('systemRolesHint'),
|
|
52163
|
+
size: 'medium',
|
|
52164
|
+
color: 'medium',
|
|
52165
|
+
bold: false,
|
|
52166
|
+
}"
|
|
52167
|
+
/>
|
|
52168
|
+
</div>
|
|
52169
|
+
<div class="role-list">
|
|
52170
|
+
@for (role of systemRoles(); track role.id) {
|
|
52171
|
+
<div class="role-card">
|
|
52172
|
+
<div class="role-card__header">
|
|
52173
|
+
<span class="role-card__name">{{ roleLabel(role.name) }}</span>
|
|
52174
|
+
<val-pill
|
|
52175
|
+
[props]="{
|
|
52176
|
+
label: tt('systemBadge'),
|
|
52177
|
+
color: 'primary',
|
|
52178
|
+
weight: 'bold',
|
|
52179
|
+
}"
|
|
52180
|
+
/>
|
|
52181
|
+
</div>
|
|
52182
|
+
@if (role.description) {
|
|
52183
|
+
<span class="role-card__desc">{{ role.description }}</span>
|
|
52184
|
+
}
|
|
52185
|
+
@if (!role.hasPerms) {
|
|
52186
|
+
<div class="perm-chips">
|
|
52187
|
+
<span class="perm-chip perm-chip--empty">{{ tt('noPermissions') }}</span>
|
|
52188
|
+
</div>
|
|
52189
|
+
} @else {
|
|
52190
|
+
@for (g of role.groups; track g.key) {
|
|
52191
|
+
<div class="perm-group">
|
|
52192
|
+
<span class="perm-group__label">{{ g.label }}</span>
|
|
52193
|
+
<div class="perm-chips">
|
|
52194
|
+
@for (p of g.perms; track p) {
|
|
52195
|
+
<span class="perm-chip">{{ p }}</span>
|
|
52196
|
+
}
|
|
52197
|
+
</div>
|
|
52198
|
+
</div>
|
|
52199
|
+
}
|
|
52200
|
+
}
|
|
52201
|
+
</div>
|
|
52202
|
+
}
|
|
52203
|
+
</div>
|
|
52204
|
+
</section>
|
|
52205
|
+
}
|
|
52206
|
+
|
|
52207
|
+
@if (resolvedConfig().showCustomRoles) {
|
|
52208
|
+
<section class="perm-section">
|
|
52209
|
+
<div class="perm-section__header">
|
|
52210
|
+
<val-title
|
|
52211
|
+
[props]="{
|
|
52212
|
+
size: 'medium',
|
|
52213
|
+
color: 'dark',
|
|
52214
|
+
bold: true,
|
|
52215
|
+
content: tt('customRolesTitle'),
|
|
52216
|
+
}"
|
|
52217
|
+
/>
|
|
52218
|
+
<val-text
|
|
52219
|
+
[props]="{
|
|
52220
|
+
content: tt('customRolesHint'),
|
|
52221
|
+
size: 'medium',
|
|
52222
|
+
color: 'medium',
|
|
52223
|
+
bold: false,
|
|
52224
|
+
}"
|
|
52225
|
+
/>
|
|
52226
|
+
</div>
|
|
52227
|
+
@if (customRoles().length === 0) {
|
|
52228
|
+
<val-text
|
|
52229
|
+
[props]="{
|
|
52230
|
+
content: tt('noCustomRoles'),
|
|
52231
|
+
size: 'medium',
|
|
52232
|
+
color: 'medium',
|
|
52233
|
+
bold: false,
|
|
52234
|
+
}"
|
|
52235
|
+
/>
|
|
52236
|
+
} @else {
|
|
52237
|
+
<div class="role-list">
|
|
52238
|
+
@for (role of customRoles(); track role.id) {
|
|
52239
|
+
<div class="role-card">
|
|
52240
|
+
<div class="role-card__header">
|
|
52241
|
+
<span class="role-card__name">{{ role.name }}</span>
|
|
52242
|
+
</div>
|
|
52243
|
+
@if (role.description) {
|
|
52244
|
+
<span class="role-card__desc">{{ role.description }}</span>
|
|
52245
|
+
}
|
|
52246
|
+
@if (!role.hasPerms) {
|
|
52247
|
+
<div class="perm-chips">
|
|
52248
|
+
<span class="perm-chip perm-chip--empty">{{ tt('noPermissions') }}</span>
|
|
52249
|
+
</div>
|
|
52250
|
+
} @else {
|
|
52251
|
+
@for (g of role.groups; track g.key) {
|
|
52252
|
+
<div class="perm-group">
|
|
52253
|
+
<span class="perm-group__label">{{ g.label }}</span>
|
|
52254
|
+
<div class="perm-chips">
|
|
52255
|
+
@for (p of g.perms; track p) {
|
|
52256
|
+
<span class="perm-chip">{{ p }}</span>
|
|
52257
|
+
}
|
|
52258
|
+
</div>
|
|
52259
|
+
</div>
|
|
52260
|
+
}
|
|
52261
|
+
}
|
|
52262
|
+
</div>
|
|
52263
|
+
}
|
|
52264
|
+
</div>
|
|
52265
|
+
}
|
|
52266
|
+
</section>
|
|
52267
|
+
}
|
|
52268
|
+
}
|
|
52269
|
+
</div>
|
|
52270
|
+
`, 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"] }] }); }
|
|
52271
|
+
}
|
|
52272
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionsViewComponent, decorators: [{
|
|
52273
|
+
type: Component,
|
|
52274
|
+
args: [{ selector: 'val-permissions-view', standalone: true, imports: [IonSpinner, DisplayComponent, PillComponent, TitleComponent, TextComponent, ButtonComponent], template: `
|
|
52275
|
+
<div class="page">
|
|
52276
|
+
<header class="page-header">
|
|
52277
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: tt('pageTitle') }" />
|
|
52278
|
+
<val-title
|
|
52279
|
+
[props]="{
|
|
52280
|
+
size: 'large',
|
|
52281
|
+
color: 'dark',
|
|
52282
|
+
bold: false,
|
|
52283
|
+
content: tt('pageDescription'),
|
|
52284
|
+
}"
|
|
52285
|
+
/>
|
|
52286
|
+
</header>
|
|
52287
|
+
|
|
52288
|
+
@if (loading()) {
|
|
52289
|
+
<div class="spinner-row">
|
|
52290
|
+
<ion-spinner name="crescent" />
|
|
52291
|
+
</div>
|
|
52292
|
+
} @else if (loadError()) {
|
|
52293
|
+
<div class="error-row">
|
|
52294
|
+
<val-text
|
|
52295
|
+
[props]="{
|
|
52296
|
+
content: tt('errorTitle'),
|
|
52297
|
+
size: 'medium',
|
|
52298
|
+
color: 'medium',
|
|
52299
|
+
bold: false,
|
|
52300
|
+
}"
|
|
52301
|
+
/>
|
|
52302
|
+
<val-button [props]="retryButtonProps()" (click)="load()" />
|
|
52303
|
+
</div>
|
|
52304
|
+
} @else {
|
|
52305
|
+
@if (resolvedConfig().showSystemRoles) {
|
|
52306
|
+
<section class="perm-section">
|
|
52307
|
+
<div class="perm-section__header">
|
|
52308
|
+
<val-title
|
|
52309
|
+
[props]="{
|
|
52310
|
+
size: 'medium',
|
|
52311
|
+
color: 'dark',
|
|
52312
|
+
bold: true,
|
|
52313
|
+
content: tt('systemRolesTitle'),
|
|
52314
|
+
}"
|
|
52315
|
+
/>
|
|
52316
|
+
<val-text
|
|
52317
|
+
[props]="{
|
|
52318
|
+
content: tt('systemRolesHint'),
|
|
52319
|
+
size: 'medium',
|
|
52320
|
+
color: 'medium',
|
|
52321
|
+
bold: false,
|
|
52322
|
+
}"
|
|
52323
|
+
/>
|
|
52324
|
+
</div>
|
|
52325
|
+
<div class="role-list">
|
|
52326
|
+
@for (role of systemRoles(); track role.id) {
|
|
52327
|
+
<div class="role-card">
|
|
52328
|
+
<div class="role-card__header">
|
|
52329
|
+
<span class="role-card__name">{{ roleLabel(role.name) }}</span>
|
|
52330
|
+
<val-pill
|
|
52331
|
+
[props]="{
|
|
52332
|
+
label: tt('systemBadge'),
|
|
52333
|
+
color: 'primary',
|
|
52334
|
+
weight: 'bold',
|
|
52335
|
+
}"
|
|
52336
|
+
/>
|
|
52337
|
+
</div>
|
|
52338
|
+
@if (role.description) {
|
|
52339
|
+
<span class="role-card__desc">{{ role.description }}</span>
|
|
52340
|
+
}
|
|
52341
|
+
@if (!role.hasPerms) {
|
|
52342
|
+
<div class="perm-chips">
|
|
52343
|
+
<span class="perm-chip perm-chip--empty">{{ tt('noPermissions') }}</span>
|
|
52344
|
+
</div>
|
|
52345
|
+
} @else {
|
|
52346
|
+
@for (g of role.groups; track g.key) {
|
|
52347
|
+
<div class="perm-group">
|
|
52348
|
+
<span class="perm-group__label">{{ g.label }}</span>
|
|
52349
|
+
<div class="perm-chips">
|
|
52350
|
+
@for (p of g.perms; track p) {
|
|
52351
|
+
<span class="perm-chip">{{ p }}</span>
|
|
52352
|
+
}
|
|
52353
|
+
</div>
|
|
52354
|
+
</div>
|
|
52355
|
+
}
|
|
52356
|
+
}
|
|
52357
|
+
</div>
|
|
52358
|
+
}
|
|
52359
|
+
</div>
|
|
52360
|
+
</section>
|
|
52361
|
+
}
|
|
52362
|
+
|
|
52363
|
+
@if (resolvedConfig().showCustomRoles) {
|
|
52364
|
+
<section class="perm-section">
|
|
52365
|
+
<div class="perm-section__header">
|
|
52366
|
+
<val-title
|
|
52367
|
+
[props]="{
|
|
52368
|
+
size: 'medium',
|
|
52369
|
+
color: 'dark',
|
|
52370
|
+
bold: true,
|
|
52371
|
+
content: tt('customRolesTitle'),
|
|
52372
|
+
}"
|
|
52373
|
+
/>
|
|
52374
|
+
<val-text
|
|
52375
|
+
[props]="{
|
|
52376
|
+
content: tt('customRolesHint'),
|
|
52377
|
+
size: 'medium',
|
|
52378
|
+
color: 'medium',
|
|
52379
|
+
bold: false,
|
|
52380
|
+
}"
|
|
52381
|
+
/>
|
|
52382
|
+
</div>
|
|
52383
|
+
@if (customRoles().length === 0) {
|
|
52384
|
+
<val-text
|
|
52385
|
+
[props]="{
|
|
52386
|
+
content: tt('noCustomRoles'),
|
|
52387
|
+
size: 'medium',
|
|
52388
|
+
color: 'medium',
|
|
52389
|
+
bold: false,
|
|
52390
|
+
}"
|
|
52391
|
+
/>
|
|
52392
|
+
} @else {
|
|
52393
|
+
<div class="role-list">
|
|
52394
|
+
@for (role of customRoles(); track role.id) {
|
|
52395
|
+
<div class="role-card">
|
|
52396
|
+
<div class="role-card__header">
|
|
52397
|
+
<span class="role-card__name">{{ role.name }}</span>
|
|
52398
|
+
</div>
|
|
52399
|
+
@if (role.description) {
|
|
52400
|
+
<span class="role-card__desc">{{ role.description }}</span>
|
|
52401
|
+
}
|
|
52402
|
+
@if (!role.hasPerms) {
|
|
52403
|
+
<div class="perm-chips">
|
|
52404
|
+
<span class="perm-chip perm-chip--empty">{{ tt('noPermissions') }}</span>
|
|
52405
|
+
</div>
|
|
52406
|
+
} @else {
|
|
52407
|
+
@for (g of role.groups; track g.key) {
|
|
52408
|
+
<div class="perm-group">
|
|
52409
|
+
<span class="perm-group__label">{{ g.label }}</span>
|
|
52410
|
+
<div class="perm-chips">
|
|
52411
|
+
@for (p of g.perms; track p) {
|
|
52412
|
+
<span class="perm-chip">{{ p }}</span>
|
|
52413
|
+
}
|
|
52414
|
+
</div>
|
|
52415
|
+
</div>
|
|
52416
|
+
}
|
|
52417
|
+
}
|
|
52418
|
+
</div>
|
|
52419
|
+
}
|
|
52420
|
+
</div>
|
|
52421
|
+
}
|
|
52422
|
+
</section>
|
|
52423
|
+
}
|
|
52424
|
+
}
|
|
52425
|
+
</div>
|
|
52426
|
+
`, 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"] }]
|
|
52427
|
+
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
52428
|
+
type: Input
|
|
52429
|
+
}] } });
|
|
52430
|
+
|
|
52431
|
+
/**
|
|
52432
|
+
* Helper para montar la vista Permisos (`val-permissions-view`) como ruta en una
|
|
52433
|
+
* app del factory. El `config` se pasa por route `data` (`permissionsConfig`) y el
|
|
52434
|
+
* componente lo lee como fallback de su `@Input() config`.
|
|
52435
|
+
*
|
|
52436
|
+
* @example
|
|
52437
|
+
* ```ts
|
|
52438
|
+
* // settings.routes.ts
|
|
52439
|
+
* export const settingsRoutes: Routes = [
|
|
52440
|
+
* ...provideValtechPermissionsRoutes(), // → /app/settings/permissions
|
|
52441
|
+
* { path: 'preferences', loadComponent: () => ... },
|
|
52442
|
+
* ];
|
|
52443
|
+
*
|
|
52444
|
+
* // con config acotada:
|
|
52445
|
+
* ...provideValtechPermissionsRoutes({ config: { showCustomRoles: false } }),
|
|
52446
|
+
* ...provideValtechPermissionsRoutes({ path: 'roles' }),
|
|
52447
|
+
* ```
|
|
52448
|
+
*/
|
|
52449
|
+
function provideValtechPermissionsRoutes(opts) {
|
|
52450
|
+
return [
|
|
52451
|
+
{
|
|
52452
|
+
path: opts?.path ?? 'permissions',
|
|
52453
|
+
component: PermissionsViewComponent,
|
|
52454
|
+
data: { permissionsConfig: opts?.config },
|
|
52455
|
+
},
|
|
52456
|
+
];
|
|
52457
|
+
}
|
|
52458
|
+
|
|
51476
52459
|
/** Built-in label sets for the three platform locales. */
|
|
51477
52460
|
const CALLOUT_LABELS = {
|
|
51478
52461
|
es: {
|
|
@@ -61679,5 +62662,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
61679
62662
|
* Generated bundle index. Do not edit.
|
|
61680
62663
|
*/
|
|
61681
62664
|
|
|
61682
|
-
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, 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 };
|
|
62665
|
+
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, 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, 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 };
|
|
61683
62666
|
//# sourceMappingURL=valtech-components.mjs.map
|