valtech-components 2.0.839 → 2.0.840

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.
@@ -53,7 +53,7 @@ import 'prismjs/components/prism-json';
53
53
  * Current version of valtech-components.
54
54
  * This is automatically updated during the publish process.
55
55
  */
56
- const VERSION = '2.0.839';
56
+ const VERSION = '2.0.840';
57
57
 
58
58
  /**
59
59
  * Servicio para gestionar presets de componentes.
@@ -4412,6 +4412,8 @@ const VALTECH_DEFAULT_CONTENT = {
4412
4412
  termsAndConditions: 'Términos y Condiciones',
4413
4413
  and: 'y',
4414
4414
  privacyPolicy: 'Política de Privacidad',
4415
+ // Acciones genéricas
4416
+ close: 'Cerrar',
4415
4417
  // Toasts
4416
4418
  welcome: '¡Bienvenido!',
4417
4419
  completeAllFields: 'Completa todos los campos.',
@@ -4566,6 +4568,8 @@ const VALTECH_DEFAULT_CONTENT = {
4566
4568
  termsAndConditions: 'Terms and Conditions',
4567
4569
  and: 'and',
4568
4570
  privacyPolicy: 'Privacy Policy',
4571
+ // Acciones genéricas
4572
+ close: 'Close',
4569
4573
  // Toasts
4570
4574
  welcome: 'Welcome!',
4571
4575
  completeAllFields: 'Complete all fields.',
@@ -20781,6 +20785,144 @@ function provideValtechAuthInterceptor() {
20781
20785
  return makeEnvironmentProviders([provideHttpClient(withInterceptors([authInterceptor]))]);
20782
20786
  }
20783
20787
 
20788
+ /**
20789
+ * Error interpretation helper for the Valtech factory.
20790
+ *
20791
+ * Todos los frontends del factory consumen la misma API (backend Go) y la misma
20792
+ * librería. La lógica de interpretar errores debe vivir una sola vez, acá.
20793
+ *
20794
+ * El backend Go (`apperrors`) SIEMPRE devuelve errores como JSON:
20795
+ * { "code": string, "message": string, "operationId": string }
20796
+ * donde `message` ya viene en español y es user-friendly.
20797
+ *
20798
+ * En Angular un error HTTP llega como `HttpErrorResponse` (body en `.error`).
20799
+ * Un fallo de red es un `HttpErrorResponse` con `status === 0`.
20800
+ *
20801
+ * Además `AuthService.handleAuthError` aplana el `HttpErrorResponse` a un
20802
+ * `AuthError { code, message }` (code/message al nivel superior). Por eso este
20803
+ * helper acepta AMBAS formas — el crudo y el aplanado.
20804
+ */
20805
+ /** Mensaje genérico para fallos de red (sin conexión / backend inalcanzable). */
20806
+ const NETWORK_MESSAGE = 'Sin conexión. Verifica tu conexión a internet e inténtalo de nuevo.';
20807
+ /** Mensaje genérico para errores no identificables. */
20808
+ const UNKNOWN_MESSAGE = 'Ocurrió un error inesperado. Inténtalo de nuevo.';
20809
+ /** Sentinel para fallos de red. */
20810
+ const NETWORK_CODE = 'NETWORK';
20811
+ /** Sentinel para errores no identificables. */
20812
+ const UNKNOWN_CODE = 'UNKNOWN';
20813
+ /** Type guard laxo: ¿el valor parece un `HttpErrorResponse`? */
20814
+ function isHttpErrorResponse(err) {
20815
+ return (typeof err === 'object' &&
20816
+ err !== null &&
20817
+ // No importamos HttpErrorResponse para mantener la fn libre de Angular;
20818
+ // detectamos por shape: tiene `status` numérico y `name` reconocible o `error`.
20819
+ ('status' in err || err.name === 'HttpErrorResponse'));
20820
+ }
20821
+ /** Devuelve un string si el valor lo es y no está vacío; si no, `undefined`. */
20822
+ function asNonEmptyString(value) {
20823
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
20824
+ }
20825
+ /**
20826
+ * Normaliza CUALQUIER error a un `InterpretedError`.
20827
+ *
20828
+ * Función pura — sin dependencias de Angular DI, testeable y usable desde
20829
+ * cualquier lado (componentes, servicios, interceptores, scripts).
20830
+ *
20831
+ * Nunca lanza: siempre devuelve un `InterpretedError` válido.
20832
+ *
20833
+ * Casos cubiertos:
20834
+ * - `HttpErrorResponse` con `status === 0` (o sin respuesta) → fallo de red.
20835
+ * - `HttpErrorResponse` con body `{ code, message, operationId }` del backend.
20836
+ * - `AuthError` aplanado `{ code, message }` (code/message top-level).
20837
+ * - `Error` plano de JS → `code: 'UNKNOWN'`, `message: err.message`.
20838
+ * - Cualquier otra cosa (string, null, undefined, objeto raro) → genérico.
20839
+ *
20840
+ * @example
20841
+ * ```ts
20842
+ * try {
20843
+ * await firstValueFrom(this.http.get(url));
20844
+ * } catch (err) {
20845
+ * const e = interpretError(err);
20846
+ * if (e.isNetwork) {
20847
+ * this.toast.show({ message: e.message, color: 'dark' });
20848
+ * } else {
20849
+ * this.errorCode.set(e.code);
20850
+ * }
20851
+ * }
20852
+ * ```
20853
+ */
20854
+ function interpretError(err) {
20855
+ // 1. HttpErrorResponse — el caso más común al hablar con el backend.
20856
+ if (isHttpErrorResponse(err)) {
20857
+ const status = typeof err.status === 'number' ? err.status : undefined;
20858
+ // 1a. Fallo de red: status 0 o sin body de respuesta del servidor.
20859
+ if (status === 0) {
20860
+ return {
20861
+ code: NETWORK_CODE,
20862
+ message: NETWORK_MESSAGE,
20863
+ status: 0,
20864
+ isNetwork: true,
20865
+ };
20866
+ }
20867
+ // 1b. Body del backend `{ code, message, operationId }`.
20868
+ const body = err.error;
20869
+ if (typeof body === 'object' && body !== null) {
20870
+ const b = body;
20871
+ return {
20872
+ code: asNonEmptyString(b.code) ?? UNKNOWN_CODE,
20873
+ message: asNonEmptyString(b.message) ?? UNKNOWN_MESSAGE,
20874
+ operationId: asNonEmptyString(b.operationId),
20875
+ status,
20876
+ isNetwork: false,
20877
+ };
20878
+ }
20879
+ // 1c. HttpErrorResponse sin body estructurado (ej. body string / null).
20880
+ return {
20881
+ code: UNKNOWN_CODE,
20882
+ message: asNonEmptyString(err.message) ?? UNKNOWN_MESSAGE,
20883
+ status,
20884
+ isNetwork: false,
20885
+ };
20886
+ }
20887
+ // 2. AuthError aplanado u objeto con `code`/`message` top-level.
20888
+ if (typeof err === 'object' && err !== null) {
20889
+ const o = err;
20890
+ const code = asNonEmptyString(o.code);
20891
+ const message = asNonEmptyString(o.message);
20892
+ if (code || message) {
20893
+ return {
20894
+ code: code ?? UNKNOWN_CODE,
20895
+ message: message ?? UNKNOWN_MESSAGE,
20896
+ operationId: asNonEmptyString(o.operationId),
20897
+ isNetwork: false,
20898
+ };
20899
+ }
20900
+ // 2b. Error plano de JS (instancia de Error sin code) — `message` ya
20901
+ // cubierto arriba; este branch atrapa Error con message vacío.
20902
+ if (err instanceof Error) {
20903
+ return {
20904
+ code: UNKNOWN_CODE,
20905
+ message: asNonEmptyString(err.message) ?? UNKNOWN_MESSAGE,
20906
+ isNetwork: false,
20907
+ };
20908
+ }
20909
+ }
20910
+ // 3. Cualquier otra cosa: string, null, undefined, objeto raro.
20911
+ return {
20912
+ code: UNKNOWN_CODE,
20913
+ message: UNKNOWN_MESSAGE,
20914
+ isNetwork: false,
20915
+ };
20916
+ }
20917
+
20918
+ /**
20919
+ * Error interpretation helpers.
20920
+ *
20921
+ * `interpretError` normaliza cualquier error (HttpErrorResponse crudo,
20922
+ * AuthError aplanado, Error de JS, o un valor arbitrario) a un
20923
+ * `InterpretedError` con forma estable. Función pura, sin Angular DI.
20924
+ */
20925
+
20784
20926
  /**
20785
20927
  * Tipos e interfaces para el servicio de autenticación de Valtech.
20786
20928
  * Alineados con el backend AuthV2.
@@ -25793,9 +25935,13 @@ class AuthService {
25793
25935
  }
25794
25936
  }
25795
25937
  handleAuthError(error) {
25938
+ // `interpretError` (helper compartido de la lib) normaliza el
25939
+ // HttpErrorResponse — incluyendo fallos de red (status 0). Aplanamos a
25940
+ // AuthError para mantener la API pública de AuthService estable.
25941
+ const interpreted = interpretError(error);
25796
25942
  const authError = {
25797
- code: error.error?.code || 'UNKNOWN_ERROR',
25798
- message: error.error?.message || 'Error de autenticación desconocido',
25943
+ code: interpreted.code,
25944
+ message: interpreted.message,
25799
25945
  };
25800
25946
  this.stateService.setError(authError);
25801
25947
  return throwError(() => authError);
@@ -29337,20 +29483,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
29337
29483
  * ```
29338
29484
  */
29339
29485
  class ChangePasswordModalComponent {
29340
- /**
29341
- * Controla la visibilidad del modal. Lo decide el componente padre. Cada vez
29342
- * que pasa de cerrado a abierto se resuelve el modo (change vs set).
29343
- */
29344
- set isOpen(value) {
29345
- const opening = value && !this._isOpen;
29346
- this._isOpen = value;
29347
- if (opening) {
29348
- this.resolveMode();
29349
- }
29350
- }
29351
- get isOpen() {
29352
- return this._isOpen;
29353
- }
29354
29486
  constructor() {
29355
29487
  this._isOpen = false;
29356
29488
  /** Emite al cambiar/crear la contraseña con éxito. El padre cierra el modal. */
@@ -29420,7 +29552,20 @@ class ChangePasswordModalComponent {
29420
29552
  state: this._formState(),
29421
29553
  });
29422
29554
  });
29423
- addIcons({ closeOutline });
29555
+ }
29556
+ /**
29557
+ * Controla la visibilidad del modal. Lo decide el componente padre. Cada vez
29558
+ * que pasa de cerrado a abierto se resuelve el modo (change vs set).
29559
+ */
29560
+ set isOpen(value) {
29561
+ const opening = value && !this._isOpen;
29562
+ this._isOpen = value;
29563
+ if (opening) {
29564
+ this.resolveMode();
29565
+ }
29566
+ }
29567
+ get isOpen() {
29568
+ return this._isOpen;
29424
29569
  }
29425
29570
  /** Traduce una clave del namespace `_auth`. */
29426
29571
  t(key) {
@@ -29537,12 +29682,12 @@ class ChangePasswordModalComponent {
29537
29682
  this.toast.show({ message, duration: 3500 });
29538
29683
  }
29539
29684
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChangePasswordModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
29540
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChangePasswordModalComponent, isStandalone: true, selector: "val-change-password-modal", inputs: { isOpen: "isOpen" }, outputs: { changed: "changed", dismissed: "dismissed" }, ngImport: i0, template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" (click)=\"close()\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content class=\"ion-padding\">\n <section class=\"modal-form-section\">\n @if (mode() === 'loading') {\n <div class=\"modal-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <val-form [props]=\"formProps()\" (onSubmit)=\"submitHandler($event)\" />\n }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".modal-form-section{display:flex;flex-direction:column;gap:16px;max-width:420px;margin:0 auto;padding-top:8px}.modal-loading{display:flex;justify-content:center;padding:40px 0}\n"], dependencies: [{ 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: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonModal, selector: "ion-modal" }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }] }); }
29685
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChangePasswordModalComponent, isStandalone: true, selector: "val-change-password-modal", inputs: { isOpen: "isOpen" }, outputs: { changed: "changed", dismissed: "dismissed" }, ngImport: i0, template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"close()\">\n <strong>{{ t('close') }}</strong>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content class=\"ion-padding\">\n <section class=\"modal-form-section\">\n @if (mode() === 'loading') {\n <div class=\"modal-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <val-form [props]=\"formProps()\" (onSubmit)=\"submitHandler($event)\" />\n }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".modal-form-section{display:flex;flex-direction:column;gap:16px;max-width:420px;margin:0 auto;padding-top:8px}.modal-loading{display:flex;justify-content:center;padding:40px 0}\n"], dependencies: [{ 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: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonModal, selector: "ion-modal" }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }] }); }
29541
29686
  }
29542
29687
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChangePasswordModalComponent, decorators: [{
29543
29688
  type: Component,
29544
- args: [{ selector: 'val-change-password-modal', standalone: true, imports: [IonButton, IonButtons, IonContent, IonHeader, IonIcon, IonModal, IonSpinner, IonToolbar, FormComponent], template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" (click)=\"close()\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content class=\"ion-padding\">\n <section class=\"modal-form-section\">\n @if (mode() === 'loading') {\n <div class=\"modal-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <val-form [props]=\"formProps()\" (onSubmit)=\"submitHandler($event)\" />\n }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".modal-form-section{display:flex;flex-direction:column;gap:16px;max-width:420px;margin:0 auto;padding-top:8px}.modal-loading{display:flex;justify-content:center;padding:40px 0}\n"] }]
29545
- }], ctorParameters: () => [], propDecorators: { isOpen: [{
29689
+ args: [{ selector: 'val-change-password-modal', standalone: true, imports: [IonButton, IonButtons, IonContent, IonHeader, IonModal, IonSpinner, IonToolbar, FormComponent], template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"close()\">\n <strong>{{ t('close') }}</strong>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content class=\"ion-padding\">\n <section class=\"modal-form-section\">\n @if (mode() === 'loading') {\n <div class=\"modal-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <val-form [props]=\"formProps()\" (onSubmit)=\"submitHandler($event)\" />\n }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".modal-form-section{display:flex;flex-direction:column;gap:16px;max-width:420px;margin:0 auto;padding-top:8px}.modal-loading{display:flex;justify-content:center;padding:40px 0}\n"] }]
29690
+ }], propDecorators: { isOpen: [{
29546
29691
  type: Input
29547
29692
  }], changed: [{
29548
29693
  type: Output
@@ -30602,7 +30747,7 @@ class MfaModalComponent {
30602
30747
  state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
30603
30748
  }));
30604
30749
  this.resendTimer = null;
30605
- addIcons({ closeOutline, informationCircleOutline });
30750
+ addIcons({ informationCircleOutline });
30606
30751
  }
30607
30752
  ngOnDestroy() {
30608
30753
  this.stopCooldown();
@@ -30906,7 +31051,7 @@ class MfaModalComponent {
30906
31051
  this.toast.show({ message, duration: 3500 });
30907
31052
  }
30908
31053
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MfaModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
30909
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MfaModalComponent, isStandalone: true, selector: "val-mfa-modal", inputs: { isOpen: "isOpen", prefillCode: "prefillCode" }, outputs: { changed: "changed", dismissed: "dismissed" }, ngImport: i0, template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" (click)=\"close()\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n\n <ion-content class=\"ion-padding\">\n <section class=\"mfa-modal\">\n @switch (step()) { @case ('loading') {\n <div class=\"mfa-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @case ('status') {\n <h2 class=\"mfa-title\">{{ t('mfaManageTitle') }}</h2>\n\n @if (mfaEnabled()) {\n <p class=\"mfa-status mfa-status--on\">{{ t('mfaEnabledLabel') }} \u00B7 {{ methodLabel(mfaMethod()) }}</p>\n\n @if (mfaMethod() === 'TOTP') {\n <div class=\"mfa-block\">\n <h3>{{ t('mfaBackupCodesTitle') }}</h3>\n <p class=\"mfa-text\">{{ t('mfaBackupCodesAvailable') }}: {{ backupCodesCount() }}</p>\n\n @if (regeneratedCodes(); as codes) {\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesSaveWarning') }} {{ t('mfaBackupCodesExplain') }}</p>\n </div>\n <div class=\"mfa-codes\">\n @for (code of codes; track code) {\n <code class=\"mfa-code\">{{ code }}</code>\n }\n </div>\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"copyCodes(codes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n } @else { @if (backupCodesCount() < 3) {\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesLow') }}</p>\n </div>\n }\n <ion-button\n expand=\"block\"\n color=\"dark\"\n fill=\"outline\"\n [disabled]=\"working()\"\n (click)=\"regenerateBackupCodes()\"\n >\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaRegenerateCodes') }} }\n </ion-button>\n }\n </div>\n }\n\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"goToDisable()\">\n {{ t('mfaDisableButton') }}\n </ion-button>\n } @else {\n <p class=\"mfa-status mfa-status--off\">{{ t('mfaDisabledLabel') }}</p>\n <p class=\"mfa-text\">{{ t('mfaDisabledHint') }}</p>\n <ion-button expand=\"block\" (click)=\"goToMethodSelect()\"> {{ t('mfaEnableButton') }} </ion-button>\n } } @case ('method-select') {\n <h2 class=\"mfa-title\">{{ t('mfaEnableTitle') }}</h2>\n <p class=\"mfa-text\">{{ t('mfaMethodPrompt') }}</p>\n\n <ion-radio-group [value]=\"selectedMethod()\" (ionChange)=\"selectedMethod.set($event.detail.value)\">\n <ion-item>\n <ion-radio slot=\"start\" value=\"TOTP\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodTotp') }}</strong>\n <p>{{ t('mfaMethodTotpHint') }}</p>\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-radio slot=\"start\" value=\"EMAIL\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodEmail') }}</strong>\n <p>{{ t('mfaMethodEmailHint') }}</p>\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-radio slot=\"start\" value=\"SMS\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodSms') }}</strong>\n <p>{{ t('mfaMethodSmsHint') }}</p>\n </ion-label>\n </ion-item>\n </ion-radio-group>\n\n @if (selectedMethod() === 'SMS' && !userPhone()) {\n <ion-input\n label=\"{{ t('mfaPhoneLabel') }}\"\n labelPlacement=\"floating\"\n fill=\"outline\"\n type=\"tel\"\n placeholder=\"+56912345678\"\n [formControl]=\"phoneControl\"\n ></ion-input>\n @if (phoneControl.invalid && phoneControl.touched) {\n <p class=\"mfa-error\">{{ t('mfaPhoneInvalid') }}</p>\n } } @if (selectedMethod() === 'SMS' && userPhone(); as phone) {\n <p class=\"mfa-text\">{{ t('mfaPhoneRegistered') }}: {{ phone }}</p>\n }\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"proceedWithMethod()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaContinue') }} }\n </ion-button>\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('totp-setup') {\n <h2 class=\"mfa-title\">{{ t('mfaTotpSetupTitle') }}</h2>\n <p class=\"mfa-text\">{{ t('mfaTotpStep1') }}</p>\n\n @if (totpQr(); as qr) {\n <div class=\"mfa-qr\">\n <val-qr-code [props]=\"{ qr: qr }\" />\n </div>\n } @if (totpSetup(); as setup) {\n <p class=\"mfa-text\">{{ t('mfaTotpManualEntry') }}</p>\n <code class=\"mfa-secret\">{{ setup.secret }}</code>\n\n <p class=\"mfa-text\">{{ t('mfaTotpStep2') }}</p>\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"verifyTotp()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaTotpVerify') }} }\n </ion-button>\n\n <div class=\"mfa-backup\">\n <h3>{{ t('mfaBackupCodesTitle') }}</h3>\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesSaveWarning') }} {{ t('mfaBackupCodesExplain') }}</p>\n </div>\n <div class=\"mfa-codes\">\n @for (code of setup.backupCodes; track code) {\n <code class=\"mfa-code\">{{ code }}</code>\n }\n </div>\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"copyCodes(setup.backupCodes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n </div>\n }\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('code-confirm') {\n <h2 class=\"mfa-title\">{{ t('mfaConfirmTitle') }}</h2>\n <p class=\"mfa-text\">\n {{ selectedMethod() === 'EMAIL' ? t('mfaConfirmPromptEmail') : t('mfaConfirmPromptSms') }}\n </p>\n\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"confirmCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaConfirmButton') }} }\n </ion-button>\n\n <p class=\"mfa-resend\">\n {{ t('mfaNoCode') }} @if (resendCooldown() > 0) {\n <span class=\"mfa-text\">{{ t('mfaResendIn') }} {{ resendCooldown() }}s</span>\n } @else {\n <a (click)=\"resendCode()\">{{ t('mfaResend') }}</a>\n }\n </p>\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('disable') {\n <val-form [props]=\"disableFormProps()\" (onSubmit)=\"onDisableSubmit($event)\" />\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".mfa-modal{display:flex;flex-direction:column;gap:14px;padding-top:4px}.mfa-loading{display:flex;justify-content:center;padding:40px 0}.mfa-title{font-size:18px;font-weight:700;margin:0;color:var(--ion-color-dark)}.mfa-status{font-weight:600;margin:0}.mfa-status--on{color:var(--ion-color-success-shade)}.mfa-status--off{color:var(--ion-color-dark)}.mfa-text{color:var(--ion-color-dark);font-size:14px;margin:0}.mfa-error{color:var(--ion-color-danger);font-size:13px;margin:4px 0 0}.mfa-block,.mfa-backup{display:flex;flex-direction:column;gap:12px}.mfa-block{padding:16px;border-radius:14px;background:var(--ion-color-light)}.mfa-block h3,.mfa-backup h3{font-size:15px;font-weight:600;margin:0;color:var(--ion-color-dark)}.mfa-alert{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:10px;background:var(--ion-color-light);border-left:3px solid var(--ion-color-primary)}.mfa-alert ion-icon{font-size:20px;color:var(--ion-color-primary);flex-shrink:0;margin-top:1px}.mfa-alert p{margin:0;font-size:13px;line-height:1.45;color:var(--ion-color-dark)}.mfa-qr{display:flex;justify-content:center;padding:8px 0}.mfa-secret{display:block;padding:10px 12px;border-radius:8px;background:var(--ion-color-light);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:15px;letter-spacing:.04em;word-break:break-all;text-align:center}.mfa-pin{display:flex;justify-content:center;padding:8px 0}.mfa-codes{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:12px;border-radius:12px;background:var(--ion-color-light)}.mfa-code{padding:8px;border-radius:6px;background:var(--ion-background-color, #fff);color:var(--ion-color-dark);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;text-align:center}.mfa-resend{text-align:center;font-size:14px;color:var(--ion-color-dark);margin:4px 0}.mfa-resend a{color:var(--ion-color-primary);cursor:pointer;font-weight:600}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonModal, selector: "ion-modal" }, { kind: "component", type: IonRadio, selector: "ion-radio", inputs: ["alignment", "color", "disabled", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonRadioGroup, selector: "ion-radio-group", inputs: ["allowEmptySelection", "compareWith", "errorText", "helperText", "name", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: QrCodeComponent, selector: "val-qr-code", inputs: ["props"], outputs: ["actionComplete", "imageLoad", "imageError"] }, { kind: "component", type: PinInputComponent, selector: "val-pin-input", inputs: ["props"] }] }); }
31054
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MfaModalComponent, isStandalone: true, selector: "val-mfa-modal", inputs: { isOpen: "isOpen", prefillCode: "prefillCode" }, outputs: { changed: "changed", dismissed: "dismissed" }, ngImport: i0, template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"close()\">\n <strong>{{ t('close') }}</strong>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n\n <ion-content class=\"ion-padding\">\n <section class=\"mfa-modal\">\n @switch (step()) { @case ('loading') {\n <div class=\"mfa-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @case ('status') {\n <h2 class=\"mfa-title\">{{ t('mfaManageTitle') }}</h2>\n\n @if (mfaEnabled()) {\n <p class=\"mfa-status mfa-status--on\">{{ t('mfaEnabledLabel') }} \u00B7 {{ methodLabel(mfaMethod()) }}</p>\n\n @if (mfaMethod() === 'TOTP') {\n <div class=\"mfa-block\">\n <h3>{{ t('mfaBackupCodesTitle') }}</h3>\n <p class=\"mfa-text\">{{ t('mfaBackupCodesAvailable') }}: {{ backupCodesCount() }}</p>\n\n @if (regeneratedCodes(); as codes) {\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesSaveWarning') }} {{ t('mfaBackupCodesExplain') }}</p>\n </div>\n <div class=\"mfa-codes\">\n @for (code of codes; track code) {\n <code class=\"mfa-code\">{{ code }}</code>\n }\n </div>\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"copyCodes(codes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n } @else { @if (backupCodesCount() < 3) {\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesLow') }}</p>\n </div>\n }\n <ion-button\n expand=\"block\"\n color=\"dark\"\n fill=\"outline\"\n [disabled]=\"working()\"\n (click)=\"regenerateBackupCodes()\"\n >\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaRegenerateCodes') }} }\n </ion-button>\n }\n </div>\n }\n\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"goToDisable()\">\n {{ t('mfaDisableButton') }}\n </ion-button>\n } @else {\n <p class=\"mfa-status mfa-status--off\">{{ t('mfaDisabledLabel') }}</p>\n <p class=\"mfa-text\">{{ t('mfaDisabledHint') }}</p>\n <ion-button expand=\"block\" (click)=\"goToMethodSelect()\"> {{ t('mfaEnableButton') }} </ion-button>\n } } @case ('method-select') {\n <h2 class=\"mfa-title\">{{ t('mfaEnableTitle') }}</h2>\n <p class=\"mfa-text\">{{ t('mfaMethodPrompt') }}</p>\n\n <ion-radio-group [value]=\"selectedMethod()\" (ionChange)=\"selectedMethod.set($event.detail.value)\">\n <ion-item>\n <ion-radio slot=\"start\" value=\"TOTP\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodTotp') }}</strong>\n <p>{{ t('mfaMethodTotpHint') }}</p>\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-radio slot=\"start\" value=\"EMAIL\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodEmail') }}</strong>\n <p>{{ t('mfaMethodEmailHint') }}</p>\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-radio slot=\"start\" value=\"SMS\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodSms') }}</strong>\n <p>{{ t('mfaMethodSmsHint') }}</p>\n </ion-label>\n </ion-item>\n </ion-radio-group>\n\n @if (selectedMethod() === 'SMS' && !userPhone()) {\n <ion-input\n label=\"{{ t('mfaPhoneLabel') }}\"\n labelPlacement=\"floating\"\n fill=\"outline\"\n type=\"tel\"\n placeholder=\"+56912345678\"\n [formControl]=\"phoneControl\"\n ></ion-input>\n @if (phoneControl.invalid && phoneControl.touched) {\n <p class=\"mfa-error\">{{ t('mfaPhoneInvalid') }}</p>\n } } @if (selectedMethod() === 'SMS' && userPhone(); as phone) {\n <p class=\"mfa-text\">{{ t('mfaPhoneRegistered') }}: {{ phone }}</p>\n }\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"proceedWithMethod()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaContinue') }} }\n </ion-button>\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('totp-setup') {\n <h2 class=\"mfa-title\">{{ t('mfaTotpSetupTitle') }}</h2>\n <p class=\"mfa-text\">{{ t('mfaTotpStep1') }}</p>\n\n @if (totpQr(); as qr) {\n <div class=\"mfa-qr\">\n <val-qr-code [props]=\"{ qr: qr }\" />\n </div>\n } @if (totpSetup(); as setup) {\n <p class=\"mfa-text\">{{ t('mfaTotpManualEntry') }}</p>\n <code class=\"mfa-secret\">{{ setup.secret }}</code>\n\n <p class=\"mfa-text\">{{ t('mfaTotpStep2') }}</p>\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"verifyTotp()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaTotpVerify') }} }\n </ion-button>\n\n <div class=\"mfa-backup\">\n <h3>{{ t('mfaBackupCodesTitle') }}</h3>\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesSaveWarning') }} {{ t('mfaBackupCodesExplain') }}</p>\n </div>\n <div class=\"mfa-codes\">\n @for (code of setup.backupCodes; track code) {\n <code class=\"mfa-code\">{{ code }}</code>\n }\n </div>\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"copyCodes(setup.backupCodes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n </div>\n }\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('code-confirm') {\n <h2 class=\"mfa-title\">{{ t('mfaConfirmTitle') }}</h2>\n <p class=\"mfa-text\">\n {{ selectedMethod() === 'EMAIL' ? t('mfaConfirmPromptEmail') : t('mfaConfirmPromptSms') }}\n </p>\n\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"confirmCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaConfirmButton') }} }\n </ion-button>\n\n <p class=\"mfa-resend\">\n {{ t('mfaNoCode') }} @if (resendCooldown() > 0) {\n <span class=\"mfa-text\">{{ t('mfaResendIn') }} {{ resendCooldown() }}s</span>\n } @else {\n <a (click)=\"resendCode()\">{{ t('mfaResend') }}</a>\n }\n </p>\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('disable') {\n <val-form [props]=\"disableFormProps()\" (onSubmit)=\"onDisableSubmit($event)\" />\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".mfa-modal{display:flex;flex-direction:column;gap:14px;padding-top:4px}.mfa-loading{display:flex;justify-content:center;padding:40px 0}.mfa-title{font-size:18px;font-weight:700;margin:0;color:var(--ion-color-dark)}.mfa-status{font-weight:600;margin:0}.mfa-status--on{color:var(--ion-color-success-shade)}.mfa-status--off{color:var(--ion-color-dark)}.mfa-text{color:var(--ion-color-dark);font-size:14px;margin:0}.mfa-error{color:var(--ion-color-danger);font-size:13px;margin:4px 0 0}.mfa-block,.mfa-backup{display:flex;flex-direction:column;gap:12px}.mfa-block{padding:16px;border-radius:14px;background:var(--ion-color-light)}.mfa-block h3,.mfa-backup h3{font-size:15px;font-weight:600;margin:0;color:var(--ion-color-dark)}.mfa-alert{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:10px;background:var(--ion-color-light);border-left:3px solid var(--ion-color-primary)}.mfa-alert ion-icon{font-size:20px;color:var(--ion-color-primary);flex-shrink:0;margin-top:1px}.mfa-alert p{margin:0;font-size:13px;line-height:1.45;color:var(--ion-color-dark)}.mfa-qr{display:flex;justify-content:center;padding:8px 0}.mfa-secret{display:block;padding:10px 12px;border-radius:8px;background:var(--ion-color-light);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:15px;letter-spacing:.04em;word-break:break-all;text-align:center}.mfa-pin{display:flex;justify-content:center;padding:8px 0}.mfa-codes{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:12px;border-radius:12px;background:var(--ion-color-light)}.mfa-code{padding:8px;border-radius:6px;background:var(--ion-background-color, #fff);color:var(--ion-color-dark);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;text-align:center}.mfa-resend{text-align:center;font-size:14px;color:var(--ion-color-dark);margin:4px 0}.mfa-resend a{color:var(--ion-color-primary);cursor:pointer;font-weight:600}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonModal, selector: "ion-modal" }, { kind: "component", type: IonRadio, selector: "ion-radio", inputs: ["alignment", "color", "disabled", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonRadioGroup, selector: "ion-radio-group", inputs: ["allowEmptySelection", "compareWith", "errorText", "helperText", "name", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: QrCodeComponent, selector: "val-qr-code", inputs: ["props"], outputs: ["actionComplete", "imageLoad", "imageError"] }, { kind: "component", type: PinInputComponent, selector: "val-pin-input", inputs: ["props"] }] }); }
30910
31055
  }
30911
31056
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MfaModalComponent, decorators: [{
30912
31057
  type: Component,
@@ -30928,7 +31073,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
30928
31073
  FormComponent,
30929
31074
  QrCodeComponent,
30930
31075
  PinInputComponent,
30931
- ], template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" (click)=\"close()\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n\n <ion-content class=\"ion-padding\">\n <section class=\"mfa-modal\">\n @switch (step()) { @case ('loading') {\n <div class=\"mfa-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @case ('status') {\n <h2 class=\"mfa-title\">{{ t('mfaManageTitle') }}</h2>\n\n @if (mfaEnabled()) {\n <p class=\"mfa-status mfa-status--on\">{{ t('mfaEnabledLabel') }} \u00B7 {{ methodLabel(mfaMethod()) }}</p>\n\n @if (mfaMethod() === 'TOTP') {\n <div class=\"mfa-block\">\n <h3>{{ t('mfaBackupCodesTitle') }}</h3>\n <p class=\"mfa-text\">{{ t('mfaBackupCodesAvailable') }}: {{ backupCodesCount() }}</p>\n\n @if (regeneratedCodes(); as codes) {\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesSaveWarning') }} {{ t('mfaBackupCodesExplain') }}</p>\n </div>\n <div class=\"mfa-codes\">\n @for (code of codes; track code) {\n <code class=\"mfa-code\">{{ code }}</code>\n }\n </div>\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"copyCodes(codes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n } @else { @if (backupCodesCount() < 3) {\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesLow') }}</p>\n </div>\n }\n <ion-button\n expand=\"block\"\n color=\"dark\"\n fill=\"outline\"\n [disabled]=\"working()\"\n (click)=\"regenerateBackupCodes()\"\n >\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaRegenerateCodes') }} }\n </ion-button>\n }\n </div>\n }\n\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"goToDisable()\">\n {{ t('mfaDisableButton') }}\n </ion-button>\n } @else {\n <p class=\"mfa-status mfa-status--off\">{{ t('mfaDisabledLabel') }}</p>\n <p class=\"mfa-text\">{{ t('mfaDisabledHint') }}</p>\n <ion-button expand=\"block\" (click)=\"goToMethodSelect()\"> {{ t('mfaEnableButton') }} </ion-button>\n } } @case ('method-select') {\n <h2 class=\"mfa-title\">{{ t('mfaEnableTitle') }}</h2>\n <p class=\"mfa-text\">{{ t('mfaMethodPrompt') }}</p>\n\n <ion-radio-group [value]=\"selectedMethod()\" (ionChange)=\"selectedMethod.set($event.detail.value)\">\n <ion-item>\n <ion-radio slot=\"start\" value=\"TOTP\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodTotp') }}</strong>\n <p>{{ t('mfaMethodTotpHint') }}</p>\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-radio slot=\"start\" value=\"EMAIL\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodEmail') }}</strong>\n <p>{{ t('mfaMethodEmailHint') }}</p>\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-radio slot=\"start\" value=\"SMS\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodSms') }}</strong>\n <p>{{ t('mfaMethodSmsHint') }}</p>\n </ion-label>\n </ion-item>\n </ion-radio-group>\n\n @if (selectedMethod() === 'SMS' && !userPhone()) {\n <ion-input\n label=\"{{ t('mfaPhoneLabel') }}\"\n labelPlacement=\"floating\"\n fill=\"outline\"\n type=\"tel\"\n placeholder=\"+56912345678\"\n [formControl]=\"phoneControl\"\n ></ion-input>\n @if (phoneControl.invalid && phoneControl.touched) {\n <p class=\"mfa-error\">{{ t('mfaPhoneInvalid') }}</p>\n } } @if (selectedMethod() === 'SMS' && userPhone(); as phone) {\n <p class=\"mfa-text\">{{ t('mfaPhoneRegistered') }}: {{ phone }}</p>\n }\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"proceedWithMethod()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaContinue') }} }\n </ion-button>\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('totp-setup') {\n <h2 class=\"mfa-title\">{{ t('mfaTotpSetupTitle') }}</h2>\n <p class=\"mfa-text\">{{ t('mfaTotpStep1') }}</p>\n\n @if (totpQr(); as qr) {\n <div class=\"mfa-qr\">\n <val-qr-code [props]=\"{ qr: qr }\" />\n </div>\n } @if (totpSetup(); as setup) {\n <p class=\"mfa-text\">{{ t('mfaTotpManualEntry') }}</p>\n <code class=\"mfa-secret\">{{ setup.secret }}</code>\n\n <p class=\"mfa-text\">{{ t('mfaTotpStep2') }}</p>\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"verifyTotp()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaTotpVerify') }} }\n </ion-button>\n\n <div class=\"mfa-backup\">\n <h3>{{ t('mfaBackupCodesTitle') }}</h3>\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesSaveWarning') }} {{ t('mfaBackupCodesExplain') }}</p>\n </div>\n <div class=\"mfa-codes\">\n @for (code of setup.backupCodes; track code) {\n <code class=\"mfa-code\">{{ code }}</code>\n }\n </div>\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"copyCodes(setup.backupCodes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n </div>\n }\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('code-confirm') {\n <h2 class=\"mfa-title\">{{ t('mfaConfirmTitle') }}</h2>\n <p class=\"mfa-text\">\n {{ selectedMethod() === 'EMAIL' ? t('mfaConfirmPromptEmail') : t('mfaConfirmPromptSms') }}\n </p>\n\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"confirmCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaConfirmButton') }} }\n </ion-button>\n\n <p class=\"mfa-resend\">\n {{ t('mfaNoCode') }} @if (resendCooldown() > 0) {\n <span class=\"mfa-text\">{{ t('mfaResendIn') }} {{ resendCooldown() }}s</span>\n } @else {\n <a (click)=\"resendCode()\">{{ t('mfaResend') }}</a>\n }\n </p>\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('disable') {\n <val-form [props]=\"disableFormProps()\" (onSubmit)=\"onDisableSubmit($event)\" />\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".mfa-modal{display:flex;flex-direction:column;gap:14px;padding-top:4px}.mfa-loading{display:flex;justify-content:center;padding:40px 0}.mfa-title{font-size:18px;font-weight:700;margin:0;color:var(--ion-color-dark)}.mfa-status{font-weight:600;margin:0}.mfa-status--on{color:var(--ion-color-success-shade)}.mfa-status--off{color:var(--ion-color-dark)}.mfa-text{color:var(--ion-color-dark);font-size:14px;margin:0}.mfa-error{color:var(--ion-color-danger);font-size:13px;margin:4px 0 0}.mfa-block,.mfa-backup{display:flex;flex-direction:column;gap:12px}.mfa-block{padding:16px;border-radius:14px;background:var(--ion-color-light)}.mfa-block h3,.mfa-backup h3{font-size:15px;font-weight:600;margin:0;color:var(--ion-color-dark)}.mfa-alert{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:10px;background:var(--ion-color-light);border-left:3px solid var(--ion-color-primary)}.mfa-alert ion-icon{font-size:20px;color:var(--ion-color-primary);flex-shrink:0;margin-top:1px}.mfa-alert p{margin:0;font-size:13px;line-height:1.45;color:var(--ion-color-dark)}.mfa-qr{display:flex;justify-content:center;padding:8px 0}.mfa-secret{display:block;padding:10px 12px;border-radius:8px;background:var(--ion-color-light);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:15px;letter-spacing:.04em;word-break:break-all;text-align:center}.mfa-pin{display:flex;justify-content:center;padding:8px 0}.mfa-codes{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:12px;border-radius:12px;background:var(--ion-color-light)}.mfa-code{padding:8px;border-radius:6px;background:var(--ion-background-color, #fff);color:var(--ion-color-dark);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;text-align:center}.mfa-resend{text-align:center;font-size:14px;color:var(--ion-color-dark);margin:4px 0}.mfa-resend a{color:var(--ion-color-primary);cursor:pointer;font-weight:600}\n"] }]
31076
+ ], template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"close()\">\n <strong>{{ t('close') }}</strong>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n\n <ion-content class=\"ion-padding\">\n <section class=\"mfa-modal\">\n @switch (step()) { @case ('loading') {\n <div class=\"mfa-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @case ('status') {\n <h2 class=\"mfa-title\">{{ t('mfaManageTitle') }}</h2>\n\n @if (mfaEnabled()) {\n <p class=\"mfa-status mfa-status--on\">{{ t('mfaEnabledLabel') }} \u00B7 {{ methodLabel(mfaMethod()) }}</p>\n\n @if (mfaMethod() === 'TOTP') {\n <div class=\"mfa-block\">\n <h3>{{ t('mfaBackupCodesTitle') }}</h3>\n <p class=\"mfa-text\">{{ t('mfaBackupCodesAvailable') }}: {{ backupCodesCount() }}</p>\n\n @if (regeneratedCodes(); as codes) {\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesSaveWarning') }} {{ t('mfaBackupCodesExplain') }}</p>\n </div>\n <div class=\"mfa-codes\">\n @for (code of codes; track code) {\n <code class=\"mfa-code\">{{ code }}</code>\n }\n </div>\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"copyCodes(codes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n } @else { @if (backupCodesCount() < 3) {\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesLow') }}</p>\n </div>\n }\n <ion-button\n expand=\"block\"\n color=\"dark\"\n fill=\"outline\"\n [disabled]=\"working()\"\n (click)=\"regenerateBackupCodes()\"\n >\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaRegenerateCodes') }} }\n </ion-button>\n }\n </div>\n }\n\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"goToDisable()\">\n {{ t('mfaDisableButton') }}\n </ion-button>\n } @else {\n <p class=\"mfa-status mfa-status--off\">{{ t('mfaDisabledLabel') }}</p>\n <p class=\"mfa-text\">{{ t('mfaDisabledHint') }}</p>\n <ion-button expand=\"block\" (click)=\"goToMethodSelect()\"> {{ t('mfaEnableButton') }} </ion-button>\n } } @case ('method-select') {\n <h2 class=\"mfa-title\">{{ t('mfaEnableTitle') }}</h2>\n <p class=\"mfa-text\">{{ t('mfaMethodPrompt') }}</p>\n\n <ion-radio-group [value]=\"selectedMethod()\" (ionChange)=\"selectedMethod.set($event.detail.value)\">\n <ion-item>\n <ion-radio slot=\"start\" value=\"TOTP\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodTotp') }}</strong>\n <p>{{ t('mfaMethodTotpHint') }}</p>\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-radio slot=\"start\" value=\"EMAIL\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodEmail') }}</strong>\n <p>{{ t('mfaMethodEmailHint') }}</p>\n </ion-label>\n </ion-item>\n <ion-item>\n <ion-radio slot=\"start\" value=\"SMS\"></ion-radio>\n <ion-label>\n <strong>{{ t('mfaMethodSms') }}</strong>\n <p>{{ t('mfaMethodSmsHint') }}</p>\n </ion-label>\n </ion-item>\n </ion-radio-group>\n\n @if (selectedMethod() === 'SMS' && !userPhone()) {\n <ion-input\n label=\"{{ t('mfaPhoneLabel') }}\"\n labelPlacement=\"floating\"\n fill=\"outline\"\n type=\"tel\"\n placeholder=\"+56912345678\"\n [formControl]=\"phoneControl\"\n ></ion-input>\n @if (phoneControl.invalid && phoneControl.touched) {\n <p class=\"mfa-error\">{{ t('mfaPhoneInvalid') }}</p>\n } } @if (selectedMethod() === 'SMS' && userPhone(); as phone) {\n <p class=\"mfa-text\">{{ t('mfaPhoneRegistered') }}: {{ phone }}</p>\n }\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"proceedWithMethod()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaContinue') }} }\n </ion-button>\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('totp-setup') {\n <h2 class=\"mfa-title\">{{ t('mfaTotpSetupTitle') }}</h2>\n <p class=\"mfa-text\">{{ t('mfaTotpStep1') }}</p>\n\n @if (totpQr(); as qr) {\n <div class=\"mfa-qr\">\n <val-qr-code [props]=\"{ qr: qr }\" />\n </div>\n } @if (totpSetup(); as setup) {\n <p class=\"mfa-text\">{{ t('mfaTotpManualEntry') }}</p>\n <code class=\"mfa-secret\">{{ setup.secret }}</code>\n\n <p class=\"mfa-text\">{{ t('mfaTotpStep2') }}</p>\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"verifyTotp()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaTotpVerify') }} }\n </ion-button>\n\n <div class=\"mfa-backup\">\n <h3>{{ t('mfaBackupCodesTitle') }}</h3>\n <div class=\"mfa-alert\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>{{ t('mfaBackupCodesSaveWarning') }} {{ t('mfaBackupCodesExplain') }}</p>\n </div>\n <div class=\"mfa-codes\">\n @for (code of setup.backupCodes; track code) {\n <code class=\"mfa-code\">{{ code }}</code>\n }\n </div>\n <ion-button expand=\"block\" color=\"dark\" fill=\"outline\" (click)=\"copyCodes(setup.backupCodes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n </div>\n }\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('code-confirm') {\n <h2 class=\"mfa-title\">{{ t('mfaConfirmTitle') }}</h2>\n <p class=\"mfa-text\">\n {{ selectedMethod() === 'EMAIL' ? t('mfaConfirmPromptEmail') : t('mfaConfirmPromptSms') }}\n </p>\n\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"confirmCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaConfirmButton') }} }\n </ion-button>\n\n <p class=\"mfa-resend\">\n {{ t('mfaNoCode') }} @if (resendCooldown() > 0) {\n <span class=\"mfa-text\">{{ t('mfaResendIn') }} {{ resendCooldown() }}s</span>\n } @else {\n <a (click)=\"resendCode()\">{{ t('mfaResend') }}</a>\n }\n </p>\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('disable') {\n <val-form [props]=\"disableFormProps()\" (onSubmit)=\"onDisableSubmit($event)\" />\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".mfa-modal{display:flex;flex-direction:column;gap:14px;padding-top:4px}.mfa-loading{display:flex;justify-content:center;padding:40px 0}.mfa-title{font-size:18px;font-weight:700;margin:0;color:var(--ion-color-dark)}.mfa-status{font-weight:600;margin:0}.mfa-status--on{color:var(--ion-color-success-shade)}.mfa-status--off{color:var(--ion-color-dark)}.mfa-text{color:var(--ion-color-dark);font-size:14px;margin:0}.mfa-error{color:var(--ion-color-danger);font-size:13px;margin:4px 0 0}.mfa-block,.mfa-backup{display:flex;flex-direction:column;gap:12px}.mfa-block{padding:16px;border-radius:14px;background:var(--ion-color-light)}.mfa-block h3,.mfa-backup h3{font-size:15px;font-weight:600;margin:0;color:var(--ion-color-dark)}.mfa-alert{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:10px;background:var(--ion-color-light);border-left:3px solid var(--ion-color-primary)}.mfa-alert ion-icon{font-size:20px;color:var(--ion-color-primary);flex-shrink:0;margin-top:1px}.mfa-alert p{margin:0;font-size:13px;line-height:1.45;color:var(--ion-color-dark)}.mfa-qr{display:flex;justify-content:center;padding:8px 0}.mfa-secret{display:block;padding:10px 12px;border-radius:8px;background:var(--ion-color-light);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:15px;letter-spacing:.04em;word-break:break-all;text-align:center}.mfa-pin{display:flex;justify-content:center;padding:8px 0}.mfa-codes{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:12px;border-radius:12px;background:var(--ion-color-light)}.mfa-code{padding:8px;border-radius:6px;background:var(--ion-background-color, #fff);color:var(--ion-color-dark);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;text-align:center}.mfa-resend{text-align:center;font-size:14px;color:var(--ion-color-dark);margin:4px 0}.mfa-resend a{color:var(--ion-color-primary);cursor:pointer;font-weight:600}\n"] }]
30932
31077
  }], ctorParameters: () => [], propDecorators: { isOpen: [{
30933
31078
  type: Input
30934
31079
  }], prefillCode: [{
@@ -47382,5 +47527,5 @@ function buildFooterLinks(links, t, resolver) {
47382
47527
  * Generated bundle index. Do not edit.
47383
47528
  */
47384
47529
 
47385
- export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_SPACING, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AppConfigService, AppVersionService, ArticleBuilder, ArticleComponent, AuthBackgroundComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentLoaderComponent, ContentReactionComponent, ContentTransformer, CookieBannerComponent, CountdownComponent, 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, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EmailInputComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FormSkeletonComponent, FunHeaderComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoComponent, InputI18nHelper, InputType, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginComponent, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MenuComponent, MessagingService, MetaService, MfaModalComponent, ModalService, MultiSelectSearchComponent, NavigationService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgSwitchService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RightsFooterComponent, RotatingTextComponent, SHAPE_KEYS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SegmentControlComponent, SelectSearchComponent, SessionService, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, 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_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_SOCIAL_LINKS, VERSION, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, blogPost, buildFooterLinks, buildPath, collections, connectPageRefresh, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechDebugConsole, provideValtechDonations, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechPresets, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, storagePaths, superAdminGuard, toArticle };
47530
+ export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_SPACING, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AppConfigService, AppVersionService, ArticleBuilder, ArticleComponent, AuthBackgroundComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentLoaderComponent, ContentReactionComponent, ContentTransformer, CookieBannerComponent, CountdownComponent, 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, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EmailInputComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FormSkeletonComponent, FunHeaderComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoComponent, InputI18nHelper, InputType, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginComponent, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MenuComponent, MessagingService, MetaService, MfaModalComponent, ModalService, MultiSelectSearchComponent, NavigationService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgSwitchService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RightsFooterComponent, RotatingTextComponent, SHAPE_KEYS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SegmentControlComponent, SelectSearchComponent, SessionService, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, 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_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_SOCIAL_LINKS, VERSION, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, blogPost, buildFooterLinks, buildPath, collections, connectPageRefresh, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, 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, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechDebugConsole, provideValtechDonations, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechPresets, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, storagePaths, superAdminGuard, toArticle };
47386
47531
  //# sourceMappingURL=valtech-components.mjs.map