valtech-components 2.0.834 → 2.0.835
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/mfa-modal/mfa-modal.component.mjs +415 -0
- package/esm2022/lib/services/i18n/default-content.mjs +109 -1
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +2 -1
- package/fesm2022/valtech-components.mjs +512 -2
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/organisms/mfa-modal/mfa-modal.component.d.ts +114 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +1 -0
|
@@ -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.
|
|
56
|
+
const VERSION = '2.0.835';
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Servicio para gestionar presets de componentes.
|
|
@@ -4352,6 +4352,60 @@ const VALTECH_DEFAULT_CONTENT = {
|
|
|
4352
4352
|
setPasswordConfirmCancel: 'Cancelar',
|
|
4353
4353
|
passwordSetSuccess: '¡Contraseña creada!',
|
|
4354
4354
|
errorPasswordAlreadySet: 'Esta cuenta ya tiene una contraseña.',
|
|
4355
|
+
// MFA — autenticación en dos pasos
|
|
4356
|
+
mfaManageTitle: 'Autenticación en dos pasos',
|
|
4357
|
+
mfaEnabledLabel: 'MFA habilitado',
|
|
4358
|
+
mfaDisabledLabel: 'MFA deshabilitado',
|
|
4359
|
+
mfaDisabledHint: 'Habilita MFA para mayor seguridad en tu cuenta.',
|
|
4360
|
+
mfaEnableButton: 'Habilitar MFA',
|
|
4361
|
+
mfaDisableButton: 'Deshabilitar MFA',
|
|
4362
|
+
mfaEnableTitle: 'Habilitar MFA',
|
|
4363
|
+
mfaMethodPrompt: 'Elige el método de verificación:',
|
|
4364
|
+
mfaMethodTotp: 'App de autenticación',
|
|
4365
|
+
mfaMethodTotpHint: 'Usa Google Authenticator u otra app (recomendado).',
|
|
4366
|
+
mfaMethodEmail: 'Correo electrónico',
|
|
4367
|
+
mfaMethodEmailHint: 'Recibe códigos en tu correo.',
|
|
4368
|
+
mfaMethodSms: 'SMS',
|
|
4369
|
+
mfaMethodSmsHint: 'Recibe códigos por mensaje de texto.',
|
|
4370
|
+
mfaPhoneLabel: 'Teléfono',
|
|
4371
|
+
mfaPhoneInvalid: 'Ingresa un número válido en formato E.164 (ej: +56912345678).',
|
|
4372
|
+
mfaPhoneRegistered: 'Teléfono registrado',
|
|
4373
|
+
mfaContinue: 'Continuar',
|
|
4374
|
+
mfaCancel: 'Cancelar',
|
|
4375
|
+
mfaTotpSetupTitle: 'Configurar app de autenticación',
|
|
4376
|
+
mfaTotpStep1: 'Paso 1 — Escanea el código QR con tu app de autenticación.',
|
|
4377
|
+
mfaTotpManualEntry: '¿No puedes escanear? Ingresa este código manualmente:',
|
|
4378
|
+
mfaTotpStep2: 'Paso 2 — Ingresa el código de 6 dígitos de tu app:',
|
|
4379
|
+
mfaTotpVerify: 'Verificar y activar',
|
|
4380
|
+
mfaConfirmTitle: 'Confirmar MFA',
|
|
4381
|
+
mfaConfirmPromptEmail: 'Ingresa el código de 6 dígitos enviado a tu correo.',
|
|
4382
|
+
mfaConfirmPromptSms: 'Ingresa el código de 6 dígitos enviado a tu teléfono.',
|
|
4383
|
+
mfaConfirmButton: 'Confirmar',
|
|
4384
|
+
mfaNoCode: '¿No recibiste el código?',
|
|
4385
|
+
mfaResend: 'Reenviar',
|
|
4386
|
+
mfaResendIn: 'Reenviar en',
|
|
4387
|
+
mfaBackupCodesTitle: 'Códigos de respaldo',
|
|
4388
|
+
mfaBackupCodesAvailable: 'Códigos disponibles',
|
|
4389
|
+
mfaBackupCodesLow: 'Te quedan pocos códigos. Considera regenerarlos.',
|
|
4390
|
+
mfaBackupCodesSaveWarning: 'Importante: guarda estos códigos de respaldo.',
|
|
4391
|
+
mfaBackupCodesExplain: 'Si pierdes acceso a tu app, estos códigos te permiten entrar. Cada uno se usa una sola vez.',
|
|
4392
|
+
mfaCopyCodes: 'Copiar códigos',
|
|
4393
|
+
mfaRegenerateCodes: 'Regenerar códigos de respaldo',
|
|
4394
|
+
mfaDisableTitle: 'Deshabilitar MFA',
|
|
4395
|
+
mfaDisablePrompt: 'Ingresa tu contraseña para deshabilitar MFA.',
|
|
4396
|
+
mfaPasswordLabel: 'Contraseña',
|
|
4397
|
+
mfaCodeInvalid: 'Ingresa un código válido de 6 dígitos.',
|
|
4398
|
+
mfaPasswordRequired: 'Ingresa tu contraseña.',
|
|
4399
|
+
mfaEnabledOk: '¡MFA habilitado correctamente!',
|
|
4400
|
+
mfaDisabledOk: 'MFA deshabilitado correctamente.',
|
|
4401
|
+
mfaCodesCopied: 'Códigos copiados al portapapeles.',
|
|
4402
|
+
mfaErrorInvalidCode: 'Código incorrecto.',
|
|
4403
|
+
mfaErrorExpiredCode: 'El código ha expirado. Solicita uno nuevo.',
|
|
4404
|
+
mfaErrorCodeUsed: 'Este código ya fue utilizado.',
|
|
4405
|
+
mfaErrorAlreadyActive: 'MFA ya está habilitado.',
|
|
4406
|
+
mfaErrorNotEnabled: 'MFA no está habilitado.',
|
|
4407
|
+
mfaErrorPhoneRequired: 'Número de teléfono requerido.',
|
|
4408
|
+
mfaErrorPhoneExists: 'Este teléfono ya está en uso.',
|
|
4355
4409
|
// Legal
|
|
4356
4410
|
legalPrefix: 'Utilizamos los servicios de',
|
|
4357
4411
|
legalSuffix: 'para ofrecerte una experiencia segura. Al iniciar sesión, aceptas nuestros',
|
|
@@ -4452,6 +4506,60 @@ const VALTECH_DEFAULT_CONTENT = {
|
|
|
4452
4506
|
setPasswordConfirmCancel: 'Cancel',
|
|
4453
4507
|
passwordSetSuccess: 'Password created!',
|
|
4454
4508
|
errorPasswordAlreadySet: 'This account already has a password.',
|
|
4509
|
+
// MFA — two-factor authentication
|
|
4510
|
+
mfaManageTitle: 'Two-factor authentication',
|
|
4511
|
+
mfaEnabledLabel: 'MFA enabled',
|
|
4512
|
+
mfaDisabledLabel: 'MFA disabled',
|
|
4513
|
+
mfaDisabledHint: 'Enable MFA for extra account security.',
|
|
4514
|
+
mfaEnableButton: 'Enable MFA',
|
|
4515
|
+
mfaDisableButton: 'Disable MFA',
|
|
4516
|
+
mfaEnableTitle: 'Enable MFA',
|
|
4517
|
+
mfaMethodPrompt: 'Choose your verification method:',
|
|
4518
|
+
mfaMethodTotp: 'Authenticator app',
|
|
4519
|
+
mfaMethodTotpHint: 'Use Google Authenticator or another app (recommended).',
|
|
4520
|
+
mfaMethodEmail: 'Email',
|
|
4521
|
+
mfaMethodEmailHint: 'Receive codes in your email.',
|
|
4522
|
+
mfaMethodSms: 'SMS',
|
|
4523
|
+
mfaMethodSmsHint: 'Receive codes by text message.',
|
|
4524
|
+
mfaPhoneLabel: 'Phone',
|
|
4525
|
+
mfaPhoneInvalid: 'Enter a valid number in E.164 format (e.g. +56912345678).',
|
|
4526
|
+
mfaPhoneRegistered: 'Registered phone',
|
|
4527
|
+
mfaContinue: 'Continue',
|
|
4528
|
+
mfaCancel: 'Cancel',
|
|
4529
|
+
mfaTotpSetupTitle: 'Set up authenticator app',
|
|
4530
|
+
mfaTotpStep1: 'Step 1 — Scan the QR code with your authenticator app.',
|
|
4531
|
+
mfaTotpManualEntry: "Can't scan? Enter this code manually:",
|
|
4532
|
+
mfaTotpStep2: 'Step 2 — Enter the 6-digit code from your app:',
|
|
4533
|
+
mfaTotpVerify: 'Verify and activate',
|
|
4534
|
+
mfaConfirmTitle: 'Confirm MFA',
|
|
4535
|
+
mfaConfirmPromptEmail: 'Enter the 6-digit code sent to your email.',
|
|
4536
|
+
mfaConfirmPromptSms: 'Enter the 6-digit code sent to your phone.',
|
|
4537
|
+
mfaConfirmButton: 'Confirm',
|
|
4538
|
+
mfaNoCode: "Didn't receive the code?",
|
|
4539
|
+
mfaResend: 'Resend',
|
|
4540
|
+
mfaResendIn: 'Resend in',
|
|
4541
|
+
mfaBackupCodesTitle: 'Backup codes',
|
|
4542
|
+
mfaBackupCodesAvailable: 'Available codes',
|
|
4543
|
+
mfaBackupCodesLow: 'You have few codes left. Consider regenerating them.',
|
|
4544
|
+
mfaBackupCodesSaveWarning: 'Important: save these backup codes.',
|
|
4545
|
+
mfaBackupCodesExplain: 'If you lose access to your app, these codes let you sign in. Each can be used once.',
|
|
4546
|
+
mfaCopyCodes: 'Copy codes',
|
|
4547
|
+
mfaRegenerateCodes: 'Regenerate backup codes',
|
|
4548
|
+
mfaDisableTitle: 'Disable MFA',
|
|
4549
|
+
mfaDisablePrompt: 'Enter your password to disable MFA.',
|
|
4550
|
+
mfaPasswordLabel: 'Password',
|
|
4551
|
+
mfaCodeInvalid: 'Enter a valid 6-digit code.',
|
|
4552
|
+
mfaPasswordRequired: 'Enter your password.',
|
|
4553
|
+
mfaEnabledOk: 'MFA enabled successfully!',
|
|
4554
|
+
mfaDisabledOk: 'MFA disabled successfully.',
|
|
4555
|
+
mfaCodesCopied: 'Codes copied to clipboard.',
|
|
4556
|
+
mfaErrorInvalidCode: 'Incorrect code.',
|
|
4557
|
+
mfaErrorExpiredCode: 'The code has expired. Request a new one.',
|
|
4558
|
+
mfaErrorCodeUsed: 'This code was already used.',
|
|
4559
|
+
mfaErrorAlreadyActive: 'MFA is already enabled.',
|
|
4560
|
+
mfaErrorNotEnabled: 'MFA is not enabled.',
|
|
4561
|
+
mfaErrorPhoneRequired: 'Phone number required.',
|
|
4562
|
+
mfaErrorPhoneExists: 'This phone is already in use.',
|
|
4455
4563
|
// Legal
|
|
4456
4564
|
legalPrefix: 'We use the services of',
|
|
4457
4565
|
legalSuffix: 'to offer you a secure experience. By signing in, you accept our',
|
|
@@ -30126,6 +30234,408 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
30126
30234
|
type: Output
|
|
30127
30235
|
}] } });
|
|
30128
30236
|
|
|
30237
|
+
/** Segundos de espera antes de poder reenviar el código EMAIL/SMS. */
|
|
30238
|
+
const RESEND_COOLDOWN_SECONDS = 30;
|
|
30239
|
+
/**
|
|
30240
|
+
* `val-mfa-modal` — modal de gestión de autenticación de dos factores (MFA)
|
|
30241
|
+
* para un usuario autenticado. Mismo patrón que `val-change-password-modal`.
|
|
30242
|
+
*
|
|
30243
|
+
* Flujo (máquina de estados interna):
|
|
30244
|
+
* - `loading` → `getProfile()` para conocer el estado MFA.
|
|
30245
|
+
* - `status` → muestra MFA habilitado/deshabilitado. Si está habilitado:
|
|
30246
|
+
* gestión de backup codes (TOTP) + deshabilitar. Si no: botón habilitar.
|
|
30247
|
+
* - `method-select` → elegir TOTP / EMAIL / SMS.
|
|
30248
|
+
* - `totp-setup` → QR + secreto manual + backup codes → verificar código.
|
|
30249
|
+
* - `code-confirm` → (EMAIL/SMS) ingresar código recibido, con reenvío.
|
|
30250
|
+
* - `disable` → contraseña para deshabilitar MFA.
|
|
30251
|
+
*
|
|
30252
|
+
* El QR se genera **client-side** (`QrGeneratorService`) — el secreto TOTP
|
|
30253
|
+
* nunca sale del navegador.
|
|
30254
|
+
*
|
|
30255
|
+
* Self-contained: inyecta `AuthService` y llama los endpoints directo. La app
|
|
30256
|
+
* controla `[isOpen]` y reacciona a `(changed)` / `(dismissed)`.
|
|
30257
|
+
*
|
|
30258
|
+
* i18n: namespace compartido `_auth`.
|
|
30259
|
+
*
|
|
30260
|
+
* @example
|
|
30261
|
+
* ```html
|
|
30262
|
+
* <val-mfa-modal
|
|
30263
|
+
* [isOpen]="isModalOpen()"
|
|
30264
|
+
* (dismissed)="isModalOpen.set(false)"
|
|
30265
|
+
* />
|
|
30266
|
+
* ```
|
|
30267
|
+
*/
|
|
30268
|
+
class MfaModalComponent {
|
|
30269
|
+
/** Controla la visibilidad. Cada apertura re-resuelve el estado MFA. */
|
|
30270
|
+
set isOpen(value) {
|
|
30271
|
+
const opening = value && !this._isOpen;
|
|
30272
|
+
this._isOpen = value;
|
|
30273
|
+
if (opening) {
|
|
30274
|
+
this.resolveStatus();
|
|
30275
|
+
}
|
|
30276
|
+
}
|
|
30277
|
+
get isOpen() {
|
|
30278
|
+
return this._isOpen;
|
|
30279
|
+
}
|
|
30280
|
+
constructor() {
|
|
30281
|
+
this._isOpen = false;
|
|
30282
|
+
/** Emite cuando el estado MFA cambia (habilitado / deshabilitado). */
|
|
30283
|
+
this.changed = new EventEmitter();
|
|
30284
|
+
/** Emite cuando el user cierra el modal (botón X o backdrop). */
|
|
30285
|
+
this.dismissed = new EventEmitter();
|
|
30286
|
+
this.auth = inject(AuthService);
|
|
30287
|
+
this.toast = inject(ToastService);
|
|
30288
|
+
this.i18n = inject(I18nService);
|
|
30289
|
+
this.qrGen = inject(QrGeneratorService);
|
|
30290
|
+
this._step = signal('loading');
|
|
30291
|
+
/** Paso actual del flujo. */
|
|
30292
|
+
this.step = this._step.asReadonly();
|
|
30293
|
+
/** `true` mientras una llamada al backend está en curso. */
|
|
30294
|
+
this.working = signal(false);
|
|
30295
|
+
// Estado MFA actual.
|
|
30296
|
+
this.mfaEnabled = signal(false);
|
|
30297
|
+
this.mfaMethod = signal(null);
|
|
30298
|
+
this.userPhone = signal(null);
|
|
30299
|
+
this.backupCodesCount = signal(0);
|
|
30300
|
+
// Estado del flujo de habilitación.
|
|
30301
|
+
this.selectedMethod = signal('TOTP');
|
|
30302
|
+
this.totpSetup = signal(null);
|
|
30303
|
+
this.totpQr = signal(null);
|
|
30304
|
+
/** Códigos de respaldo recién regenerados — se muestran una sola vez. */
|
|
30305
|
+
this.regeneratedCodes = signal(null);
|
|
30306
|
+
this.resendCooldown = signal(0);
|
|
30307
|
+
this.pinControl = new FormControl('', [Validators.required, Validators.minLength(6), Validators.maxLength(6)]);
|
|
30308
|
+
this.passwordControl = new FormControl('', [Validators.required]);
|
|
30309
|
+
this.phoneControl = new FormControl('', [Validators.required, Validators.pattern(/^\+[1-9]\d{6,14}$/)]);
|
|
30310
|
+
this.pinInputProps = {
|
|
30311
|
+
control: this.pinControl,
|
|
30312
|
+
token: 'mfa-code',
|
|
30313
|
+
length: 6,
|
|
30314
|
+
allowNumbersOnly: true,
|
|
30315
|
+
autoFocus: true,
|
|
30316
|
+
};
|
|
30317
|
+
this.resendTimer = null;
|
|
30318
|
+
addIcons({ closeOutline });
|
|
30319
|
+
}
|
|
30320
|
+
ngOnDestroy() {
|
|
30321
|
+
this.stopCooldown();
|
|
30322
|
+
}
|
|
30323
|
+
/** Traduce una clave del namespace `_auth`. */
|
|
30324
|
+
t(key) {
|
|
30325
|
+
return this.i18n.t(key, '_auth');
|
|
30326
|
+
}
|
|
30327
|
+
/** Cierre iniciado por el user (X / backdrop). */
|
|
30328
|
+
close() {
|
|
30329
|
+
this.dismissed.emit();
|
|
30330
|
+
}
|
|
30331
|
+
// ===========================================================================
|
|
30332
|
+
// Carga de estado
|
|
30333
|
+
// ===========================================================================
|
|
30334
|
+
/** Consulta el perfil para conocer el estado MFA y posicionar el flujo. */
|
|
30335
|
+
resolveStatus() {
|
|
30336
|
+
this._step.set('loading');
|
|
30337
|
+
this.resetFlow();
|
|
30338
|
+
this.auth.getProfile().subscribe({
|
|
30339
|
+
next: profile => {
|
|
30340
|
+
this.mfaEnabled.set(profile.mfaEnabled);
|
|
30341
|
+
this.mfaMethod.set(profile.mfaMethod ?? null);
|
|
30342
|
+
this.userPhone.set(profile.phone ?? null);
|
|
30343
|
+
if (profile.mfaEnabled && profile.mfaMethod === 'TOTP') {
|
|
30344
|
+
this.loadBackupCount();
|
|
30345
|
+
}
|
|
30346
|
+
this._step.set('status');
|
|
30347
|
+
},
|
|
30348
|
+
error: () => {
|
|
30349
|
+
// Fallback: usar el signal de usuario en sesión.
|
|
30350
|
+
const user = this.auth.user();
|
|
30351
|
+
this.mfaEnabled.set(user?.mfaEnabled ?? false);
|
|
30352
|
+
this.mfaMethod.set(user?.mfaMethod ?? null);
|
|
30353
|
+
this._step.set('status');
|
|
30354
|
+
},
|
|
30355
|
+
});
|
|
30356
|
+
}
|
|
30357
|
+
loadBackupCount() {
|
|
30358
|
+
this.auth.getBackupCodesCount().subscribe({
|
|
30359
|
+
next: res => this.backupCodesCount.set(res.count),
|
|
30360
|
+
error: () => this.backupCodesCount.set(0),
|
|
30361
|
+
});
|
|
30362
|
+
}
|
|
30363
|
+
// ===========================================================================
|
|
30364
|
+
// Navegación entre pasos
|
|
30365
|
+
// ===========================================================================
|
|
30366
|
+
goToMethodSelect() {
|
|
30367
|
+
this.regeneratedCodes.set(null);
|
|
30368
|
+
this._step.set('method-select');
|
|
30369
|
+
}
|
|
30370
|
+
goToDisable() {
|
|
30371
|
+
this.passwordControl.reset();
|
|
30372
|
+
this._step.set('disable');
|
|
30373
|
+
}
|
|
30374
|
+
backToStatus() {
|
|
30375
|
+
this.stopCooldown();
|
|
30376
|
+
this.resolveStatus();
|
|
30377
|
+
}
|
|
30378
|
+
// ===========================================================================
|
|
30379
|
+
// Habilitar MFA
|
|
30380
|
+
// ===========================================================================
|
|
30381
|
+
/** Continúa desde el selector de método al setup correspondiente. */
|
|
30382
|
+
proceedWithMethod() {
|
|
30383
|
+
const method = this.selectedMethod();
|
|
30384
|
+
if (method === 'TOTP') {
|
|
30385
|
+
this.setupTotp();
|
|
30386
|
+
return;
|
|
30387
|
+
}
|
|
30388
|
+
let phone;
|
|
30389
|
+
if (method === 'SMS' && !this.userPhone()) {
|
|
30390
|
+
if (this.phoneControl.invalid) {
|
|
30391
|
+
this.phoneControl.markAsTouched();
|
|
30392
|
+
this.showToast(this.t('mfaPhoneInvalid'));
|
|
30393
|
+
return;
|
|
30394
|
+
}
|
|
30395
|
+
phone = this.phoneControl.value ?? undefined;
|
|
30396
|
+
}
|
|
30397
|
+
this.working.set(true);
|
|
30398
|
+
this.auth.setupMFA(method, phone).subscribe({
|
|
30399
|
+
next: res => {
|
|
30400
|
+
this.working.set(false);
|
|
30401
|
+
if (res.codeSent) {
|
|
30402
|
+
this.pinControl.reset();
|
|
30403
|
+
this._step.set('code-confirm');
|
|
30404
|
+
this.startCooldown();
|
|
30405
|
+
}
|
|
30406
|
+
},
|
|
30407
|
+
error: err => {
|
|
30408
|
+
this.working.set(false);
|
|
30409
|
+
this.showToast(this.resolveError(err));
|
|
30410
|
+
},
|
|
30411
|
+
});
|
|
30412
|
+
}
|
|
30413
|
+
setupTotp() {
|
|
30414
|
+
this.working.set(true);
|
|
30415
|
+
this.auth.setupTOTP().subscribe({
|
|
30416
|
+
next: res => {
|
|
30417
|
+
this.totpSetup.set(res);
|
|
30418
|
+
this.pinControl.reset();
|
|
30419
|
+
this.qrGen
|
|
30420
|
+
.generate({ data: res.qrCodeUrl, width: 220 })
|
|
30421
|
+
.then(qr => this.totpQr.set(qr))
|
|
30422
|
+
.catch(() => this.totpQr.set(null));
|
|
30423
|
+
this.working.set(false);
|
|
30424
|
+
this._step.set('totp-setup');
|
|
30425
|
+
},
|
|
30426
|
+
error: err => {
|
|
30427
|
+
this.working.set(false);
|
|
30428
|
+
this.showToast(this.resolveError(err));
|
|
30429
|
+
},
|
|
30430
|
+
});
|
|
30431
|
+
}
|
|
30432
|
+
/** Verifica el código TOTP de la app de autenticación y activa MFA. */
|
|
30433
|
+
verifyTotp() {
|
|
30434
|
+
const code = this.pinControl.value;
|
|
30435
|
+
if (!code || code.length !== 6) {
|
|
30436
|
+
this.showToast(this.t('mfaCodeInvalid'));
|
|
30437
|
+
return;
|
|
30438
|
+
}
|
|
30439
|
+
this.working.set(true);
|
|
30440
|
+
this.auth.verifyTOTPSetup(code).subscribe({
|
|
30441
|
+
next: res => {
|
|
30442
|
+
this.working.set(false);
|
|
30443
|
+
if (res.enabled) {
|
|
30444
|
+
this.showToast(this.t('mfaEnabledOk'));
|
|
30445
|
+
this.changed.emit();
|
|
30446
|
+
this.resolveStatus();
|
|
30447
|
+
}
|
|
30448
|
+
},
|
|
30449
|
+
error: err => {
|
|
30450
|
+
this.working.set(false);
|
|
30451
|
+
this.showToast(this.resolveError(err));
|
|
30452
|
+
},
|
|
30453
|
+
});
|
|
30454
|
+
}
|
|
30455
|
+
/** Confirma el código EMAIL/SMS y activa MFA. */
|
|
30456
|
+
confirmCode() {
|
|
30457
|
+
const code = this.pinControl.value;
|
|
30458
|
+
if (!code || code.length !== 6) {
|
|
30459
|
+
this.showToast(this.t('mfaCodeInvalid'));
|
|
30460
|
+
return;
|
|
30461
|
+
}
|
|
30462
|
+
this.working.set(true);
|
|
30463
|
+
this.auth.confirmMFA(code).subscribe({
|
|
30464
|
+
next: res => {
|
|
30465
|
+
this.working.set(false);
|
|
30466
|
+
if (res.mfaEnabled) {
|
|
30467
|
+
this.showToast(this.t('mfaEnabledOk'));
|
|
30468
|
+
this.changed.emit();
|
|
30469
|
+
this.resolveStatus();
|
|
30470
|
+
}
|
|
30471
|
+
},
|
|
30472
|
+
error: err => {
|
|
30473
|
+
this.working.set(false);
|
|
30474
|
+
this.showToast(this.resolveError(err));
|
|
30475
|
+
},
|
|
30476
|
+
});
|
|
30477
|
+
}
|
|
30478
|
+
/** Reenvía el código EMAIL/SMS (re-ejecuta el setup). */
|
|
30479
|
+
resendCode() {
|
|
30480
|
+
if (this.resendCooldown() > 0) {
|
|
30481
|
+
return;
|
|
30482
|
+
}
|
|
30483
|
+
this.proceedWithMethod();
|
|
30484
|
+
}
|
|
30485
|
+
// ===========================================================================
|
|
30486
|
+
// Gestión (MFA habilitado)
|
|
30487
|
+
// ===========================================================================
|
|
30488
|
+
/** Regenera los códigos de respaldo TOTP y los muestra una vez. */
|
|
30489
|
+
regenerateBackupCodes() {
|
|
30490
|
+
this.working.set(true);
|
|
30491
|
+
this.auth.regenerateBackupCodes().subscribe({
|
|
30492
|
+
next: res => {
|
|
30493
|
+
this.working.set(false);
|
|
30494
|
+
this.backupCodesCount.set(res.backupCodes.length);
|
|
30495
|
+
this.regeneratedCodes.set(res.backupCodes);
|
|
30496
|
+
},
|
|
30497
|
+
error: err => {
|
|
30498
|
+
this.working.set(false);
|
|
30499
|
+
this.showToast(this.resolveError(err));
|
|
30500
|
+
},
|
|
30501
|
+
});
|
|
30502
|
+
}
|
|
30503
|
+
/** Deshabilita MFA — requiere la contraseña de la cuenta. */
|
|
30504
|
+
confirmDisable() {
|
|
30505
|
+
const password = this.passwordControl.value;
|
|
30506
|
+
if (!password) {
|
|
30507
|
+
this.showToast(this.t('mfaPasswordRequired'));
|
|
30508
|
+
return;
|
|
30509
|
+
}
|
|
30510
|
+
this.working.set(true);
|
|
30511
|
+
this.auth.disableMFA(password).subscribe({
|
|
30512
|
+
next: res => {
|
|
30513
|
+
this.working.set(false);
|
|
30514
|
+
if (res.mfaDisabled) {
|
|
30515
|
+
this.showToast(this.t('mfaDisabledOk'));
|
|
30516
|
+
this.changed.emit();
|
|
30517
|
+
this.resolveStatus();
|
|
30518
|
+
}
|
|
30519
|
+
},
|
|
30520
|
+
error: err => {
|
|
30521
|
+
this.working.set(false);
|
|
30522
|
+
this.showToast(this.resolveError(err));
|
|
30523
|
+
},
|
|
30524
|
+
});
|
|
30525
|
+
}
|
|
30526
|
+
/** Copia una lista de códigos de respaldo al portapapeles. */
|
|
30527
|
+
async copyCodes(codes) {
|
|
30528
|
+
try {
|
|
30529
|
+
await navigator.clipboard.writeText(codes.join('\n'));
|
|
30530
|
+
this.showToast(this.t('mfaCodesCopied'));
|
|
30531
|
+
}
|
|
30532
|
+
catch {
|
|
30533
|
+
/* sin recurso de copia disponible */
|
|
30534
|
+
}
|
|
30535
|
+
}
|
|
30536
|
+
/** Etiqueta i18n legible para un método MFA. */
|
|
30537
|
+
methodLabel(method) {
|
|
30538
|
+
switch (method) {
|
|
30539
|
+
case 'TOTP':
|
|
30540
|
+
return this.t('mfaMethodTotp');
|
|
30541
|
+
case 'EMAIL':
|
|
30542
|
+
return this.t('mfaMethodEmail');
|
|
30543
|
+
case 'SMS':
|
|
30544
|
+
return this.t('mfaMethodSms');
|
|
30545
|
+
default:
|
|
30546
|
+
return '';
|
|
30547
|
+
}
|
|
30548
|
+
}
|
|
30549
|
+
// ===========================================================================
|
|
30550
|
+
// Helpers
|
|
30551
|
+
// ===========================================================================
|
|
30552
|
+
resetFlow() {
|
|
30553
|
+
this.pinControl.reset();
|
|
30554
|
+
this.passwordControl.reset();
|
|
30555
|
+
this.phoneControl.reset();
|
|
30556
|
+
this.totpSetup.set(null);
|
|
30557
|
+
this.totpQr.set(null);
|
|
30558
|
+
this.regeneratedCodes.set(null);
|
|
30559
|
+
this.selectedMethod.set('TOTP');
|
|
30560
|
+
this.stopCooldown();
|
|
30561
|
+
}
|
|
30562
|
+
startCooldown() {
|
|
30563
|
+
this.stopCooldown();
|
|
30564
|
+
this.resendCooldown.set(RESEND_COOLDOWN_SECONDS);
|
|
30565
|
+
this.resendTimer = setInterval(() => {
|
|
30566
|
+
this.resendCooldown.update(v => v - 1);
|
|
30567
|
+
if (this.resendCooldown() <= 0) {
|
|
30568
|
+
this.stopCooldown();
|
|
30569
|
+
}
|
|
30570
|
+
}, 1000);
|
|
30571
|
+
}
|
|
30572
|
+
stopCooldown() {
|
|
30573
|
+
if (this.resendTimer) {
|
|
30574
|
+
clearInterval(this.resendTimer);
|
|
30575
|
+
this.resendTimer = null;
|
|
30576
|
+
}
|
|
30577
|
+
this.resendCooldown.set(0);
|
|
30578
|
+
}
|
|
30579
|
+
/** Mapea los códigos de error MFA del backend a mensajes del namespace `_auth`. */
|
|
30580
|
+
resolveError(err) {
|
|
30581
|
+
const code = err?.code;
|
|
30582
|
+
switch (code) {
|
|
30583
|
+
case 'AUTHV2_MFA_INVALID_CODE':
|
|
30584
|
+
return this.t('mfaErrorInvalidCode');
|
|
30585
|
+
case 'AUTHV2_EXPIRED_CODE':
|
|
30586
|
+
return this.t('mfaErrorExpiredCode');
|
|
30587
|
+
case 'AUTHV2_CODE_USED':
|
|
30588
|
+
return this.t('mfaErrorCodeUsed');
|
|
30589
|
+
case 'AUTHV2_MFA_ALREADY_ACTIVE':
|
|
30590
|
+
return this.t('mfaErrorAlreadyActive');
|
|
30591
|
+
case 'AUTHV2_MFA_NOT_ENABLED':
|
|
30592
|
+
return this.t('mfaErrorNotEnabled');
|
|
30593
|
+
case 'AUTHV2_PHONE_REQUIRED':
|
|
30594
|
+
return this.t('mfaErrorPhoneRequired');
|
|
30595
|
+
case 'AUTHV2_PHONE_EXISTS':
|
|
30596
|
+
return this.t('mfaErrorPhoneExists');
|
|
30597
|
+
case 'AUTHV2_TOO_MANY_ATTEMPTS':
|
|
30598
|
+
return this.t('errorTooManyAttempts');
|
|
30599
|
+
case 'AUTHV2_INVALID_CURRENT_PASSWORD':
|
|
30600
|
+
return this.t('errorCurrentPasswordWrong');
|
|
30601
|
+
default:
|
|
30602
|
+
return this.t('errorGeneric');
|
|
30603
|
+
}
|
|
30604
|
+
}
|
|
30605
|
+
showToast(message) {
|
|
30606
|
+
this.toast.show({ message, duration: 3500 });
|
|
30607
|
+
}
|
|
30608
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MfaModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
30609
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MfaModalComponent, isStandalone: true, selector: "val-mfa-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\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-muted\">{{ t('mfaBackupCodesAvailable') }}: {{ backupCodesCount() }}</p>\n @if (backupCodesCount() < 3) {\n <p class=\"mfa-warn\">{{ t('mfaBackupCodesLow') }}</p>\n } @if (regeneratedCodes(); as codes) {\n <p class=\"mfa-warn\">{{ t('mfaBackupCodesSaveWarning') }}</p>\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\" fill=\"outline\" (click)=\"copyCodes(codes)\"> {{ t('mfaCopyCodes') }} </ion-button>\n } @else {\n <ion-button expand=\"block\" fill=\"outline\" [disabled]=\"working()\" (click)=\"regenerateBackupCodes()\">\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=\"danger\" 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-muted\">{{ 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-muted\">{{ 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 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-muted\">{{ 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=\"medium\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('totp-setup') {\n <h2 class=\"mfa-title\">{{ t('mfaTotpSetupTitle') }}</h2>\n <p class=\"mfa-muted\">{{ 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-muted\">{{ t('mfaTotpManualEntry') }}</p>\n <code class=\"mfa-secret\">{{ setup.secret }}</code>\n\n <p class=\"mfa-muted\">{{ 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-block mfa-block--warn\">\n <p class=\"mfa-warn\">{{ t('mfaBackupCodesSaveWarning') }}</p>\n <p class=\"mfa-muted\">{{ t('mfaBackupCodesExplain') }}</p>\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\" fill=\"outline\" (click)=\"copyCodes(setup.backupCodes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n </div>\n }\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"medium\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('code-confirm') {\n <h2 class=\"mfa-title\">{{ t('mfaConfirmTitle') }}</h2>\n <p class=\"mfa-muted\">\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-muted\">{{ 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=\"medium\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('disable') {\n <h2 class=\"mfa-title\">{{ t('mfaDisableTitle') }}</h2>\n <p class=\"mfa-muted\">{{ t('mfaDisablePrompt') }}</p>\n\n <ion-input\n label=\"{{ t('mfaPasswordLabel') }}\"\n labelPlacement=\"floating\"\n type=\"password\"\n [formControl]=\"passwordControl\"\n ></ion-input>\n\n <ion-button\n expand=\"block\"\n color=\"danger\"\n [disabled]=\"working() || passwordControl.invalid\"\n (click)=\"confirmDisable()\"\n >\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaDisableButton') }} }\n </ion-button>\n <ion-button expand=\"block\" fill=\"clear\" color=\"medium\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".mfa-modal{max-width:460px;margin:0 auto;display:flex;flex-direction:column;gap:12px;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-muted{color:var(--ion-color-medium);font-size:14px;margin:0}.mfa-warn{color:var(--ion-color-warning-shade);font-size:14px;font-weight:600;margin:0}.mfa-error{color:var(--ion-color-danger);font-size:13px;margin:4px 0 0}.mfa-block{display:flex;flex-direction:column;gap:10px;padding:14px;border-radius:12px;background:var(--ion-color-light)}.mfa-block--warn{background:var(--ion-color-warning-tint, rgba(255, 196, 9, .12));border:1px solid var(--ion-color-warning)}.mfa-block h3{font-size:15px;font-weight:600;margin:0;color:var(--ion-color-dark)}.mfa-qr{display:flex;justify-content:center;padding:12px 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}.mfa-code{padding:8px;border-radius:6px;background:var(--ion-background-color, #fff);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-medium);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: QrCodeComponent, selector: "val-qr-code", inputs: ["props"], outputs: ["actionComplete", "imageLoad", "imageError"] }, { kind: "component", type: PinInputComponent, selector: "val-pin-input", inputs: ["props"] }] }); }
|
|
30610
|
+
}
|
|
30611
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MfaModalComponent, decorators: [{
|
|
30612
|
+
type: Component,
|
|
30613
|
+
args: [{ selector: 'val-mfa-modal', standalone: true, imports: [
|
|
30614
|
+
ReactiveFormsModule,
|
|
30615
|
+
IonButton,
|
|
30616
|
+
IonButtons,
|
|
30617
|
+
IonContent,
|
|
30618
|
+
IonHeader,
|
|
30619
|
+
IonIcon,
|
|
30620
|
+
IonInput,
|
|
30621
|
+
IonItem,
|
|
30622
|
+
IonLabel,
|
|
30623
|
+
IonModal,
|
|
30624
|
+
IonRadio,
|
|
30625
|
+
IonRadioGroup,
|
|
30626
|
+
IonSpinner,
|
|
30627
|
+
IonToolbar,
|
|
30628
|
+
QrCodeComponent,
|
|
30629
|
+
PinInputComponent,
|
|
30630
|
+
], 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-muted\">{{ t('mfaBackupCodesAvailable') }}: {{ backupCodesCount() }}</p>\n @if (backupCodesCount() < 3) {\n <p class=\"mfa-warn\">{{ t('mfaBackupCodesLow') }}</p>\n } @if (regeneratedCodes(); as codes) {\n <p class=\"mfa-warn\">{{ t('mfaBackupCodesSaveWarning') }}</p>\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\" fill=\"outline\" (click)=\"copyCodes(codes)\"> {{ t('mfaCopyCodes') }} </ion-button>\n } @else {\n <ion-button expand=\"block\" fill=\"outline\" [disabled]=\"working()\" (click)=\"regenerateBackupCodes()\">\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=\"danger\" 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-muted\">{{ 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-muted\">{{ 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 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-muted\">{{ 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=\"medium\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('totp-setup') {\n <h2 class=\"mfa-title\">{{ t('mfaTotpSetupTitle') }}</h2>\n <p class=\"mfa-muted\">{{ 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-muted\">{{ t('mfaTotpManualEntry') }}</p>\n <code class=\"mfa-secret\">{{ setup.secret }}</code>\n\n <p class=\"mfa-muted\">{{ 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-block mfa-block--warn\">\n <p class=\"mfa-warn\">{{ t('mfaBackupCodesSaveWarning') }}</p>\n <p class=\"mfa-muted\">{{ t('mfaBackupCodesExplain') }}</p>\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\" fill=\"outline\" (click)=\"copyCodes(setup.backupCodes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n </div>\n }\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"medium\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('code-confirm') {\n <h2 class=\"mfa-title\">{{ t('mfaConfirmTitle') }}</h2>\n <p class=\"mfa-muted\">\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-muted\">{{ 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=\"medium\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('disable') {\n <h2 class=\"mfa-title\">{{ t('mfaDisableTitle') }}</h2>\n <p class=\"mfa-muted\">{{ t('mfaDisablePrompt') }}</p>\n\n <ion-input\n label=\"{{ t('mfaPasswordLabel') }}\"\n labelPlacement=\"floating\"\n type=\"password\"\n [formControl]=\"passwordControl\"\n ></ion-input>\n\n <ion-button\n expand=\"block\"\n color=\"danger\"\n [disabled]=\"working() || passwordControl.invalid\"\n (click)=\"confirmDisable()\"\n >\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaDisableButton') }} }\n </ion-button>\n <ion-button expand=\"block\" fill=\"clear\" color=\"medium\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".mfa-modal{max-width:460px;margin:0 auto;display:flex;flex-direction:column;gap:12px;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-muted{color:var(--ion-color-medium);font-size:14px;margin:0}.mfa-warn{color:var(--ion-color-warning-shade);font-size:14px;font-weight:600;margin:0}.mfa-error{color:var(--ion-color-danger);font-size:13px;margin:4px 0 0}.mfa-block{display:flex;flex-direction:column;gap:10px;padding:14px;border-radius:12px;background:var(--ion-color-light)}.mfa-block--warn{background:var(--ion-color-warning-tint, rgba(255, 196, 9, .12));border:1px solid var(--ion-color-warning)}.mfa-block h3{font-size:15px;font-weight:600;margin:0;color:var(--ion-color-dark)}.mfa-qr{display:flex;justify-content:center;padding:12px 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}.mfa-code{padding:8px;border-radius:6px;background:var(--ion-background-color, #fff);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-medium);margin:4px 0}.mfa-resend a{color:var(--ion-color-primary);cursor:pointer;font-weight:600}\n"] }]
|
|
30631
|
+
}], ctorParameters: () => [], propDecorators: { isOpen: [{
|
|
30632
|
+
type: Input
|
|
30633
|
+
}], changed: [{
|
|
30634
|
+
type: Output
|
|
30635
|
+
}], dismissed: [{
|
|
30636
|
+
type: Output
|
|
30637
|
+
}] } });
|
|
30638
|
+
|
|
30129
30639
|
/**
|
|
30130
30640
|
* ItemListComponent
|
|
30131
30641
|
*
|
|
@@ -46562,5 +47072,5 @@ function buildFooterLinks(links, t, resolver) {
|
|
|
46562
47072
|
* Generated bundle index. Do not edit.
|
|
46563
47073
|
*/
|
|
46564
47074
|
|
|
46565
|
-
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, 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 };
|
|
47075
|
+
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 };
|
|
46566
47076
|
//# sourceMappingURL=valtech-components.mjs.map
|