valtech-components 4.0.25 → 4.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +31 -262
  2. package/esm2022/lib/components/molecules/content-reaction/content-reaction.component.mjs +4 -7
  3. package/esm2022/lib/components/molecules/modal-shell/modal-shell.component.mjs +3 -3
  4. package/esm2022/lib/components/organisms/api-key-create-modal/api-key-create-modal.component.mjs +7 -7
  5. package/esm2022/lib/components/organisms/api-keys-modal/api-keys-modal.component.mjs +13 -13
  6. package/esm2022/lib/components/organisms/avatar-upload/avatar-upload.component.mjs +3 -3
  7. package/esm2022/lib/components/organisms/change-password-modal/change-password-modal.component.mjs +5 -5
  8. package/esm2022/lib/components/organisms/create-org-modal/create-org-modal.component.mjs +5 -5
  9. package/esm2022/lib/components/organisms/delete-account-modal/delete-account-modal.component.mjs +4 -3
  10. package/esm2022/lib/components/organisms/edit-org-modal/edit-org-modal.component.mjs +5 -5
  11. package/esm2022/lib/components/organisms/invite-member-modal/invite-member-modal.component.mjs +7 -5
  12. package/esm2022/lib/components/organisms/login-attempt-modal/login-attempt-modal.component.mjs +6 -3
  13. package/esm2022/lib/components/organisms/member-detail-modal/member-detail-modal.component.mjs +6 -5
  14. package/esm2022/lib/components/organisms/member-import-modal/member-import-modal.component.mjs +9 -9
  15. package/esm2022/lib/components/organisms/mfa-modal/mfa-modal.component.mjs +3 -3
  16. package/esm2022/lib/components/organisms/org-info-sheet/org-info-sheet.component.mjs +37 -48
  17. package/esm2022/lib/components/organisms/organization-view/organization-view.component.mjs +8 -2
  18. package/esm2022/lib/components/organisms/organization-view/organization-view.i18n.mjs +15 -3
  19. package/esm2022/lib/components/organisms/session-list-modal/session-list-modal.component.mjs +4 -3
  20. package/esm2022/lib/components/organisms/switch-org-modal/switch-org-modal.component.mjs +3 -3
  21. package/esm2022/lib/components/organisms/transfer-ownership-modal/transfer-ownership-modal.component.mjs +7 -7
  22. package/esm2022/lib/services/firebase/messaging.service.mjs +2 -2
  23. package/esm2022/lib/services/refreshable-stream/refreshable-stream.mjs +2 -2
  24. package/esm2022/lib/version.mjs +2 -2
  25. package/fesm2022/valtech-components.mjs +136 -122
  26. package/fesm2022/valtech-components.mjs.map +1 -1
  27. package/lib/components/organisms/org-info-sheet/org-info-sheet.component.d.ts +12 -1
  28. package/lib/version.d.ts +1 -1
  29. package/package.json +1 -1
  30. package/src/lib/services/firebase/firebase-messaging-sw.js +0 -145
@@ -25,7 +25,7 @@ import { getApps, getApp, initializeApp as initializeApp$1 } from 'firebase/app'
25
25
  import * as i2$1 from '@ionic/angular/standalone';
26
26
  import { AlertController, IonAvatar, IonCard, IonIcon, IonButton, IonSpinner, IonText, IonModal, IonHeader, IonToolbar, IonContent, IonButtons, IonTitle, IonProgressBar, IonSkeletonText, IonFab, IonFabButton, IonFabList, IonLabel, IonCardContent, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCheckbox, IonRadio, IonRadioGroup, IonTextarea, IonDatetime, IonSelect, IonSelectOption, IonPopover, IonList, IonItem, IonInput, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonRippleEffect, IonAccordion, IonAccordionGroup, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonChip, IonNote, IonDatetimeButton, ToastController as ToastController$1, IonCol, IonRow, IonRefresher, IonRefresherContent, IonMenuButton, IonFooter, IonListHeader, IonInfiniteScroll, IonInfiniteScrollContent, IonGrid, MenuController, IonMenu, IonMenuToggle, IonSplitPane } from '@ionic/angular/standalone';
27
27
  import { addIcons } from 'ionicons';
28
- import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, peopleOutline, phonePortraitOutline, refreshOutline, documentTextOutline, lockClosedOutline, cloudUploadOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, ellipse, starOutline, starHalf, heartHalf, checkmarkCircle, timeOutline, flag, trendingUp, trendingDown, remove, analytics, people, cash, cart, eye, chatbubbleOutline, thumbsUpOutline, thumbsUp, happyOutline, happy, sadOutline, sad, chevronUp, pin, pencil, callOutline, logoWhatsapp, paperPlaneOutline, mailOutline, chevronDownCircleOutline, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, cloudOfflineOutline, documentOutline, attachOutline, cameraOutline, closeCircleOutline, imageOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, laptopOutline, swapHorizontalOutline, notificationsOffOutline, alarmOutline, chatboxEllipsesOutline, checkmarkDoneOutline, cogOutline, shareSocialOutline, helpCircleOutline, menuOutline, stopCircleOutline, videocamOffOutline } from 'ionicons/icons';
28
+ import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, peopleOutline, phonePortraitOutline, refreshOutline, documentTextOutline, lockClosedOutline, cloudUploadOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, ellipse, starOutline, starHalf, heartHalf, checkmarkCircle, timeOutline, flag, trendingUp, trendingDown, remove, analytics, people, cash, cart, eye, chatbubbleOutline, thumbsUpOutline, thumbsUp, happyOutline, happy, sadOutline, sad, chevronUp, pin, pencil, callOutline, logoWhatsapp, paperPlaneOutline, mailOutline, chevronDownCircleOutline, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, cloudOfflineOutline, documentOutline, attachOutline, cameraOutline, closeCircleOutline, imageOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, laptopOutline, swapHorizontalOutline, codeSlashOutline, notificationsOffOutline, alarmOutline, chatboxEllipsesOutline, checkmarkDoneOutline, cogOutline, shareSocialOutline, helpCircleOutline, menuOutline, stopCircleOutline, videocamOffOutline } from 'ionicons/icons';
29
29
  import { Browser } from '@capacitor/browser';
30
30
  import * as i1$6 from '@angular/platform-browser';
31
31
  import { DomSanitizer, Meta, Title } from '@angular/platform-browser';
@@ -56,7 +56,7 @@ import { BrowserMultiFormatReader } from '@zxing/browser';
56
56
  * Current version of valtech-components.
57
57
  * This is automatically updated during the publish process.
58
58
  */
59
- const VERSION = '4.0.25';
59
+ const VERSION = '4.0.27';
60
60
 
61
61
  // Control de estado de refresco (singleton a nivel de módulo)
62
62
  let isRefreshing = false;
@@ -5499,7 +5499,7 @@ class MessagingService {
5499
5499
  const token = await this.requestPermission();
5500
5500
  if (!token) {
5501
5501
  // requestPermission devuelve null tanto por permiso denegado como por
5502
- // fallo al obtener el token. Distinguimos por el estado del permiso.
5502
+ // fallo al obtener el token. Distivdsmos por el estado del permiso.
5503
5503
  const permission = this.getPermissionState();
5504
5504
  if (permission === 'denied' || permission === 'default') {
5505
5505
  return { status: 'denied', reason: 'Permiso de notificaciones denegado' };
@@ -29795,7 +29795,7 @@ class ModalShellComponent {
29795
29795
  <ion-toolbar>
29796
29796
  @if (showClose()) {
29797
29797
  <ion-buttons slot="end">
29798
- <ion-button fill="clear" color="dark" (click)="onClose()">
29798
+ <ion-button fill="clear" color="dark" shape="round" (click)="onClose()">
29799
29799
  <strong>{{ resolvedCloseLabel() }}</strong>
29800
29800
  </ion-button>
29801
29801
  </ion-buttons>
@@ -29820,7 +29820,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
29820
29820
  <ion-toolbar>
29821
29821
  @if (showClose()) {
29822
29822
  <ion-buttons slot="end">
29823
- <ion-button fill="clear" color="dark" (click)="onClose()">
29823
+ <ion-button fill="clear" color="dark" shape="round" (click)="onClose()">
29824
29824
  <strong>{{ resolvedCloseLabel() }}</strong>
29825
29825
  </ion-button>
29826
29826
  </ion-buttons>
@@ -33391,7 +33391,7 @@ class ChangePasswordModalComponent {
33391
33391
  },
33392
33392
  ],
33393
33393
  actions: {
33394
- ...button({ text: '', type: 'submit', fill: 'solid', expand: 'block' }),
33394
+ ...button({ text: '', type: 'submit', fill: 'solid', expand: 'block', shape: 'round' }),
33395
33395
  token: 'set-submit',
33396
33396
  textKey: 'setPasswordSubmit',
33397
33397
  },
@@ -33425,7 +33425,7 @@ class ChangePasswordModalComponent {
33425
33425
  },
33426
33426
  ],
33427
33427
  actions: {
33428
- ...button({ text: '', type: 'submit', fill: 'solid', expand: 'block' }),
33428
+ ...button({ text: '', type: 'submit', fill: 'solid', expand: 'block', shape: 'round' }),
33429
33429
  token: 'change-submit',
33430
33430
  textKey: 'changePasswordSubmit',
33431
33431
  },
@@ -33564,11 +33564,11 @@ class ChangePasswordModalComponent {
33564
33564
  this.toast.show({ message, duration: 3500 });
33565
33565
  }
33566
33566
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChangePasswordModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
33567
- 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"] }] }); }
33567
+ 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\" shape=\"round\" (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"] }] }); }
33568
33568
  }
33569
33569
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChangePasswordModalComponent, decorators: [{
33570
33570
  type: Component,
33571
- 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"] }]
33571
+ 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\" shape=\"round\" (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"] }]
33572
33572
  }], propDecorators: { isOpen: [{
33573
33573
  type: Input
33574
33574
  }], changed: [{
@@ -35789,7 +35789,7 @@ class MfaModalComponent {
35789
35789
  this.toast.show({ message, duration: 3500 });
35790
35790
  }
35791
35791
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MfaModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
35792
- 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", enabledViaDeeplink: "enabledViaDeeplink", dismissed: "dismissed" }, ngImport: i0, template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\" cssClass=\"val-modal-adaptive\">\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 <val-display [props]=\"{ content: t('mfaManageTitle'), size: 'small', color: 'dark' }\" />\n\n @switch (step()) { @case ('loading') {\n <div class=\"mfa-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @case ('status') { @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 <val-title [props]=\"{ size: 'small', color: 'dark', bold: true, content: t('mfaBackupCodesTitle') }\" />\n <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaBackupCodesAvailable') + ': ' + backupCodesCount() }\"\n />\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 <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisabledHint') }\" />\n <ion-button expand=\"block\" (click)=\"goToMethodSelect()\"> {{ t('mfaEnableButton') }} </ion-button>\n } } @case ('method-select') {\n <val-title [props]=\"{ content: t('mfaEnableTitle'), size: 'large', color: 'dark', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaMethodPrompt') }\" />\n\n <div class=\"mfa-method-list\" role=\"radiogroup\">\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'TOTP'\"\n (click)=\"selectedMethod.set('TOTP')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'TOTP'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodTotp') }}</strong>\n <span>{{ t('mfaMethodTotpHint') }}</span>\n </span>\n </button>\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'EMAIL'\"\n (click)=\"selectedMethod.set('EMAIL')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'EMAIL'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodEmail') }}</strong>\n <span>{{ t('mfaMethodEmailHint') }}</span>\n </span>\n </button>\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'SMS'\"\n (click)=\"selectedMethod.set('SMS')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'SMS'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodSms') }}</strong>\n <span>{{ t('mfaMethodSmsHint') }}</span>\n </span>\n </button>\n </div>\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 <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaPhoneRegistered') + ': ' + phone }\"\n />\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 <val-title [props]=\"{ content: t('mfaTotpSetupTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpStep1') }\" />\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 <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpManualEntry') }\" />\n <div class=\"mfa-secret-row\">\n <code class=\"mfa-secret\">{{ setup.secret }}</code>\n <ion-button fill=\"clear\" size=\"small\" (click)=\"copySecret(setup.secret)\" [attr.aria-label]=\"t('copy')\">\n @if (copiedSecret()) {\n <ion-icon name=\"checkmark-outline\"></ion-icon>\n } @else {\n <ion-icon name=\"copy-outline\"></ion-icon>\n }\n </ion-button>\n </div>\n\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpStep2') }\" />\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 <val-title [props]=\"{ size: 'small', color: 'dark', bold: true, content: t('mfaBackupCodesTitle') }\" />\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 <val-title [props]=\"{ content: t('mfaConfirmTitle'), size: 'large', color: '', bold: false }\" />\n <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: selectedMethod() === 'EMAIL' ? t('mfaConfirmPromptEmail') : t('mfaConfirmPromptSms') }\"\n />\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>{{ 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') { @if (hasPassword()) {\n <val-form [props]=\"disableFormProps()\" (onSubmit)=\"onDisableSubmit($event)\" />\n } @else if (mfaMethod() === 'TOTP') {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableTotpPrompt') }\" />\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"disableWithMfaCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaDisableButton') }} }\n </ion-button>\n } @else if (mfaMethod() === 'EMAIL' || mfaMethod() === 'SMS') {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n @if (!disableCodeSent()) {\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableCodePrompt') }\" />\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"sendDisableCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaDisableSendCode') }} }\n </ion-button>\n } @else {\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableCodePrompt') }\" />\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"disableWithMfaCode()\">\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\" [disabled]=\"working()\" (click)=\"sendDisableCode()\">\n {{ t('mfaDisableResendCode') }}\n </ion-button>\n } } @else {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableNeedsPassword') }\" />\n }\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-status{display:inline-flex;align-items:center;width:fit-content;padding:4px 10px;border-radius:999px;font-size:.8125rem;font-weight:600;margin:0}.mfa-status--on{color:var(--ion-color-success-shade);background:rgba(var(--ion-color-success-rgb),.12)}.mfa-status--off{color:var(--ion-color-medium-shade);background:var(--ion-color-light)}.mfa-secret-row{display:flex;align-items:center;gap:8px}.mfa-secret-row .mfa-secret{flex:1;margin:0}.mfa-secret-row ion-button{flex-shrink:0;--color: var(--ion-color-dark);margin:0}.mfa-error{color:var(--ion-color-danger);font-size:.8125rem;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-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:1.25rem;color:var(--ion-color-primary);flex-shrink:0;margin-top:1px}.mfa-alert p{margin:0;font-size:.8125rem;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:.9375rem;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:.8125rem;text-align:center}.mfa-method-list{display:flex;flex-direction:column;gap:8px}.mfa-method-card{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;border-radius:12px;border:1.5px solid var(--ion-border-color, rgba(var(--ion-color-dark-rgb), .14));background:transparent;cursor:pointer;text-align:left;width:100%;transition:border-color .15s ease,background .15s ease}.mfa-method-card--active{border-color:var(--ion-color-primary);background:rgba(var(--ion-color-primary-rgb),.07)}.mfa-method-card__dot{flex-shrink:0;width:18px;height:18px;border-radius:50%;border:2px solid var(--ion-color-medium);margin-top:2px;transition:border-color .15s,box-shadow .15s}.mfa-method-card--active .mfa-method-card__dot{border-color:var(--ion-color-primary);box-shadow:inset 0 0 0 4px var(--ion-color-primary)}.mfa-method-card__body{display:flex;flex-direction:column;gap:3px}.mfa-method-card__body strong{font-size:.9375rem;font-weight:600;color:var(--ion-color-dark)}.mfa-method-card__body span{font-size:.8125rem;color:var(--ion-color-medium);line-height:1.4}.mfa-resend{text-align:center;font-size:.875rem;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$7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$7.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: 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: DisplayComponent, selector: "val-display", inputs: ["props"] }, { 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: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: PinInputComponent, selector: "val-pin-input", inputs: ["props"] }] }); }
35792
+ 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", enabledViaDeeplink: "enabledViaDeeplink", dismissed: "dismissed" }, ngImport: i0, template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\" cssClass=\"val-modal-adaptive\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" color=\"dark\" shape=\"round\" (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 <val-display [props]=\"{ content: t('mfaManageTitle'), size: 'small', color: 'dark' }\" />\n\n @switch (step()) { @case ('loading') {\n <div class=\"mfa-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @case ('status') { @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 <val-title [props]=\"{ size: 'small', color: 'dark', bold: true, content: t('mfaBackupCodesTitle') }\" />\n <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaBackupCodesAvailable') + ': ' + backupCodesCount() }\"\n />\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\" shape=\"round\" (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 shape=\"round\"\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\" shape=\"round\" (click)=\"goToDisable()\">\n {{ t('mfaDisableButton') }}\n </ion-button>\n } @else {\n <p class=\"mfa-status mfa-status--off\">{{ t('mfaDisabledLabel') }}</p>\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisabledHint') }\" />\n <ion-button expand=\"block\" shape=\"round\" (click)=\"goToMethodSelect()\"> {{ t('mfaEnableButton') }} </ion-button>\n } } @case ('method-select') {\n <val-title [props]=\"{ content: t('mfaEnableTitle'), size: 'large', color: 'dark', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaMethodPrompt') }\" />\n\n <div class=\"mfa-method-list\" role=\"radiogroup\">\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'TOTP'\"\n (click)=\"selectedMethod.set('TOTP')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'TOTP'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodTotp') }}</strong>\n <span>{{ t('mfaMethodTotpHint') }}</span>\n </span>\n </button>\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'EMAIL'\"\n (click)=\"selectedMethod.set('EMAIL')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'EMAIL'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodEmail') }}</strong>\n <span>{{ t('mfaMethodEmailHint') }}</span>\n </span>\n </button>\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'SMS'\"\n (click)=\"selectedMethod.set('SMS')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'SMS'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodSms') }}</strong>\n <span>{{ t('mfaMethodSmsHint') }}</span>\n </span>\n </button>\n </div>\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 <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaPhoneRegistered') + ': ' + phone }\"\n />\n }\n\n <ion-button expand=\"block\" shape=\"round\" [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\" shape=\"round\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('totp-setup') {\n <val-title [props]=\"{ content: t('mfaTotpSetupTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpStep1') }\" />\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 <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpManualEntry') }\" />\n <div class=\"mfa-secret-row\">\n <code class=\"mfa-secret\">{{ setup.secret }}</code>\n <ion-button fill=\"clear\" size=\"small\" (click)=\"copySecret(setup.secret)\" [attr.aria-label]=\"t('copy')\">\n @if (copiedSecret()) {\n <ion-icon name=\"checkmark-outline\"></ion-icon>\n } @else {\n <ion-icon name=\"copy-outline\"></ion-icon>\n }\n </ion-button>\n </div>\n\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpStep2') }\" />\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" shape=\"round\" [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 <val-title [props]=\"{ size: 'small', color: 'dark', bold: true, content: t('mfaBackupCodesTitle') }\" />\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\" shape=\"round\" (click)=\"copyCodes(setup.backupCodes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n </div>\n }\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" shape=\"round\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('code-confirm') {\n <val-title [props]=\"{ content: t('mfaConfirmTitle'), size: 'large', color: '', bold: false }\" />\n <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: selectedMethod() === 'EMAIL' ? t('mfaConfirmPromptEmail') : t('mfaConfirmPromptSms') }\"\n />\n\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" shape=\"round\" [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>{{ 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\" shape=\"round\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('disable') { @if (hasPassword()) {\n <val-form [props]=\"disableFormProps()\" (onSubmit)=\"onDisableSubmit($event)\" />\n } @else if (mfaMethod() === 'TOTP') {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableTotpPrompt') }\" />\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"working()\" (click)=\"disableWithMfaCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaDisableButton') }} }\n </ion-button>\n } @else if (mfaMethod() === 'EMAIL' || mfaMethod() === 'SMS') {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n @if (!disableCodeSent()) {\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableCodePrompt') }\" />\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"working()\" (click)=\"sendDisableCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaDisableSendCode') }} }\n </ion-button>\n } @else {\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableCodePrompt') }\" />\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"working()\" (click)=\"disableWithMfaCode()\">\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\" shape=\"round\" [disabled]=\"working()\" (click)=\"sendDisableCode()\">\n {{ t('mfaDisableResendCode') }}\n </ion-button>\n } } @else {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableNeedsPassword') }\" />\n }\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" shape=\"round\" (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-status{display:inline-flex;align-items:center;width:fit-content;padding:4px 10px;border-radius:999px;font-size:.8125rem;font-weight:600;margin:0}.mfa-status--on{color:var(--ion-color-success-shade);background:rgba(var(--ion-color-success-rgb),.12)}.mfa-status--off{color:var(--ion-color-medium-shade);background:var(--ion-color-light)}.mfa-secret-row{display:flex;align-items:center;gap:8px}.mfa-secret-row .mfa-secret{flex:1;margin:0}.mfa-secret-row ion-button{flex-shrink:0;--color: var(--ion-color-dark);margin:0}.mfa-error{color:var(--ion-color-danger);font-size:.8125rem;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-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:1.25rem;color:var(--ion-color-primary);flex-shrink:0;margin-top:1px}.mfa-alert p{margin:0;font-size:.8125rem;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:.9375rem;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:.8125rem;text-align:center}.mfa-method-list{display:flex;flex-direction:column;gap:8px}.mfa-method-card{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;border-radius:12px;border:1.5px solid var(--ion-border-color, rgba(var(--ion-color-dark-rgb), .14));background:transparent;cursor:pointer;text-align:left;width:100%;transition:border-color .15s ease,background .15s ease}.mfa-method-card--active{border-color:var(--ion-color-primary);background:rgba(var(--ion-color-primary-rgb),.07)}.mfa-method-card__dot{flex-shrink:0;width:18px;height:18px;border-radius:50%;border:2px solid var(--ion-color-medium);margin-top:2px;transition:border-color .15s,box-shadow .15s}.mfa-method-card--active .mfa-method-card__dot{border-color:var(--ion-color-primary);box-shadow:inset 0 0 0 4px var(--ion-color-primary)}.mfa-method-card__body{display:flex;flex-direction:column;gap:3px}.mfa-method-card__body strong{font-size:.9375rem;font-weight:600;color:var(--ion-color-dark)}.mfa-method-card__body span{font-size:.8125rem;color:var(--ion-color-medium);line-height:1.4}.mfa-resend{text-align:center;font-size:.875rem;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$7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$7.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: 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: DisplayComponent, selector: "val-display", inputs: ["props"] }, { 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: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: PinInputComponent, selector: "val-pin-input", inputs: ["props"] }] }); }
35793
35793
  }
35794
35794
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MfaModalComponent, decorators: [{
35795
35795
  type: Component,
@@ -35814,7 +35814,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
35814
35814
  TextComponent,
35815
35815
  TitleComponent,
35816
35816
  PinInputComponent,
35817
- ], template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\" cssClass=\"val-modal-adaptive\">\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 <val-display [props]=\"{ content: t('mfaManageTitle'), size: 'small', color: 'dark' }\" />\n\n @switch (step()) { @case ('loading') {\n <div class=\"mfa-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @case ('status') { @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 <val-title [props]=\"{ size: 'small', color: 'dark', bold: true, content: t('mfaBackupCodesTitle') }\" />\n <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaBackupCodesAvailable') + ': ' + backupCodesCount() }\"\n />\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 <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisabledHint') }\" />\n <ion-button expand=\"block\" (click)=\"goToMethodSelect()\"> {{ t('mfaEnableButton') }} </ion-button>\n } } @case ('method-select') {\n <val-title [props]=\"{ content: t('mfaEnableTitle'), size: 'large', color: 'dark', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaMethodPrompt') }\" />\n\n <div class=\"mfa-method-list\" role=\"radiogroup\">\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'TOTP'\"\n (click)=\"selectedMethod.set('TOTP')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'TOTP'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodTotp') }}</strong>\n <span>{{ t('mfaMethodTotpHint') }}</span>\n </span>\n </button>\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'EMAIL'\"\n (click)=\"selectedMethod.set('EMAIL')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'EMAIL'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodEmail') }}</strong>\n <span>{{ t('mfaMethodEmailHint') }}</span>\n </span>\n </button>\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'SMS'\"\n (click)=\"selectedMethod.set('SMS')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'SMS'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodSms') }}</strong>\n <span>{{ t('mfaMethodSmsHint') }}</span>\n </span>\n </button>\n </div>\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 <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaPhoneRegistered') + ': ' + phone }\"\n />\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 <val-title [props]=\"{ content: t('mfaTotpSetupTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpStep1') }\" />\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 <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpManualEntry') }\" />\n <div class=\"mfa-secret-row\">\n <code class=\"mfa-secret\">{{ setup.secret }}</code>\n <ion-button fill=\"clear\" size=\"small\" (click)=\"copySecret(setup.secret)\" [attr.aria-label]=\"t('copy')\">\n @if (copiedSecret()) {\n <ion-icon name=\"checkmark-outline\"></ion-icon>\n } @else {\n <ion-icon name=\"copy-outline\"></ion-icon>\n }\n </ion-button>\n </div>\n\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpStep2') }\" />\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 <val-title [props]=\"{ size: 'small', color: 'dark', bold: true, content: t('mfaBackupCodesTitle') }\" />\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 <val-title [props]=\"{ content: t('mfaConfirmTitle'), size: 'large', color: '', bold: false }\" />\n <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: selectedMethod() === 'EMAIL' ? t('mfaConfirmPromptEmail') : t('mfaConfirmPromptSms') }\"\n />\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>{{ 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') { @if (hasPassword()) {\n <val-form [props]=\"disableFormProps()\" (onSubmit)=\"onDisableSubmit($event)\" />\n } @else if (mfaMethod() === 'TOTP') {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableTotpPrompt') }\" />\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"disableWithMfaCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaDisableButton') }} }\n </ion-button>\n } @else if (mfaMethod() === 'EMAIL' || mfaMethod() === 'SMS') {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n @if (!disableCodeSent()) {\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableCodePrompt') }\" />\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"sendDisableCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaDisableSendCode') }} }\n </ion-button>\n } @else {\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableCodePrompt') }\" />\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n <ion-button expand=\"block\" [disabled]=\"working()\" (click)=\"disableWithMfaCode()\">\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\" [disabled]=\"working()\" (click)=\"sendDisableCode()\">\n {{ t('mfaDisableResendCode') }}\n </ion-button>\n } } @else {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableNeedsPassword') }\" />\n }\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-status{display:inline-flex;align-items:center;width:fit-content;padding:4px 10px;border-radius:999px;font-size:.8125rem;font-weight:600;margin:0}.mfa-status--on{color:var(--ion-color-success-shade);background:rgba(var(--ion-color-success-rgb),.12)}.mfa-status--off{color:var(--ion-color-medium-shade);background:var(--ion-color-light)}.mfa-secret-row{display:flex;align-items:center;gap:8px}.mfa-secret-row .mfa-secret{flex:1;margin:0}.mfa-secret-row ion-button{flex-shrink:0;--color: var(--ion-color-dark);margin:0}.mfa-error{color:var(--ion-color-danger);font-size:.8125rem;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-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:1.25rem;color:var(--ion-color-primary);flex-shrink:0;margin-top:1px}.mfa-alert p{margin:0;font-size:.8125rem;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:.9375rem;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:.8125rem;text-align:center}.mfa-method-list{display:flex;flex-direction:column;gap:8px}.mfa-method-card{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;border-radius:12px;border:1.5px solid var(--ion-border-color, rgba(var(--ion-color-dark-rgb), .14));background:transparent;cursor:pointer;text-align:left;width:100%;transition:border-color .15s ease,background .15s ease}.mfa-method-card--active{border-color:var(--ion-color-primary);background:rgba(var(--ion-color-primary-rgb),.07)}.mfa-method-card__dot{flex-shrink:0;width:18px;height:18px;border-radius:50%;border:2px solid var(--ion-color-medium);margin-top:2px;transition:border-color .15s,box-shadow .15s}.mfa-method-card--active .mfa-method-card__dot{border-color:var(--ion-color-primary);box-shadow:inset 0 0 0 4px var(--ion-color-primary)}.mfa-method-card__body{display:flex;flex-direction:column;gap:3px}.mfa-method-card__body strong{font-size:.9375rem;font-weight:600;color:var(--ion-color-dark)}.mfa-method-card__body span{font-size:.8125rem;color:var(--ion-color-medium);line-height:1.4}.mfa-resend{text-align:center;font-size:.875rem;color:var(--ion-color-dark);margin:4px 0}.mfa-resend a{color:var(--ion-color-primary);cursor:pointer;font-weight:600}\n"] }]
35817
+ ], template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\" cssClass=\"val-modal-adaptive\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" color=\"dark\" shape=\"round\" (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 <val-display [props]=\"{ content: t('mfaManageTitle'), size: 'small', color: 'dark' }\" />\n\n @switch (step()) { @case ('loading') {\n <div class=\"mfa-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @case ('status') { @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 <val-title [props]=\"{ size: 'small', color: 'dark', bold: true, content: t('mfaBackupCodesTitle') }\" />\n <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaBackupCodesAvailable') + ': ' + backupCodesCount() }\"\n />\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\" shape=\"round\" (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 shape=\"round\"\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\" shape=\"round\" (click)=\"goToDisable()\">\n {{ t('mfaDisableButton') }}\n </ion-button>\n } @else {\n <p class=\"mfa-status mfa-status--off\">{{ t('mfaDisabledLabel') }}</p>\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisabledHint') }\" />\n <ion-button expand=\"block\" shape=\"round\" (click)=\"goToMethodSelect()\"> {{ t('mfaEnableButton') }} </ion-button>\n } } @case ('method-select') {\n <val-title [props]=\"{ content: t('mfaEnableTitle'), size: 'large', color: 'dark', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaMethodPrompt') }\" />\n\n <div class=\"mfa-method-list\" role=\"radiogroup\">\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'TOTP'\"\n (click)=\"selectedMethod.set('TOTP')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'TOTP'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodTotp') }}</strong>\n <span>{{ t('mfaMethodTotpHint') }}</span>\n </span>\n </button>\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'EMAIL'\"\n (click)=\"selectedMethod.set('EMAIL')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'EMAIL'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodEmail') }}</strong>\n <span>{{ t('mfaMethodEmailHint') }}</span>\n </span>\n </button>\n <button\n type=\"button\"\n class=\"mfa-method-card\"\n [class.mfa-method-card--active]=\"selectedMethod() === 'SMS'\"\n (click)=\"selectedMethod.set('SMS')\"\n role=\"radio\"\n [attr.aria-checked]=\"selectedMethod() === 'SMS'\"\n >\n <span class=\"mfa-method-card__dot\"></span>\n <span class=\"mfa-method-card__body\">\n <strong>{{ t('mfaMethodSms') }}</strong>\n <span>{{ t('mfaMethodSmsHint') }}</span>\n </span>\n </button>\n </div>\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 <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaPhoneRegistered') + ': ' + phone }\"\n />\n }\n\n <ion-button expand=\"block\" shape=\"round\" [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\" shape=\"round\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('totp-setup') {\n <val-title [props]=\"{ content: t('mfaTotpSetupTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpStep1') }\" />\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 <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpManualEntry') }\" />\n <div class=\"mfa-secret-row\">\n <code class=\"mfa-secret\">{{ setup.secret }}</code>\n <ion-button fill=\"clear\" size=\"small\" (click)=\"copySecret(setup.secret)\" [attr.aria-label]=\"t('copy')\">\n @if (copiedSecret()) {\n <ion-icon name=\"checkmark-outline\"></ion-icon>\n } @else {\n <ion-icon name=\"copy-outline\"></ion-icon>\n }\n </ion-button>\n </div>\n\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaTotpStep2') }\" />\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" shape=\"round\" [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 <val-title [props]=\"{ size: 'small', color: 'dark', bold: true, content: t('mfaBackupCodesTitle') }\" />\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\" shape=\"round\" (click)=\"copyCodes(setup.backupCodes)\">\n {{ t('mfaCopyCodes') }}\n </ion-button>\n </div>\n }\n\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" shape=\"round\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('code-confirm') {\n <val-title [props]=\"{ content: t('mfaConfirmTitle'), size: 'large', color: '', bold: false }\" />\n <val-text\n [props]=\"{ size: 'medium', color: 'dark', bold: false, content: selectedMethod() === 'EMAIL' ? t('mfaConfirmPromptEmail') : t('mfaConfirmPromptSms') }\"\n />\n\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n\n <ion-button expand=\"block\" shape=\"round\" [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>{{ 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\" shape=\"round\" (click)=\"backToStatus()\">\n {{ t('mfaCancel') }}\n </ion-button>\n } @case ('disable') { @if (hasPassword()) {\n <val-form [props]=\"disableFormProps()\" (onSubmit)=\"onDisableSubmit($event)\" />\n } @else if (mfaMethod() === 'TOTP') {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableTotpPrompt') }\" />\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"working()\" (click)=\"disableWithMfaCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaDisableButton') }} }\n </ion-button>\n } @else if (mfaMethod() === 'EMAIL' || mfaMethod() === 'SMS') {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n @if (!disableCodeSent()) {\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableCodePrompt') }\" />\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"working()\" (click)=\"sendDisableCode()\">\n @if (working()) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ t('mfaDisableSendCode') }} }\n </ion-button>\n } @else {\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableCodePrompt') }\" />\n <div class=\"mfa-pin\">\n <val-pin-input [props]=\"pinInputProps\" />\n </div>\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"working()\" (click)=\"disableWithMfaCode()\">\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\" shape=\"round\" [disabled]=\"working()\" (click)=\"sendDisableCode()\">\n {{ t('mfaDisableResendCode') }}\n </ion-button>\n } } @else {\n <val-title [props]=\"{ content: t('mfaDisableTitle'), size: 'large', color: '', bold: false }\" />\n <val-text [props]=\"{ size: 'medium', color: 'dark', bold: false, content: t('mfaDisableNeedsPassword') }\" />\n }\n <ion-button expand=\"block\" fill=\"clear\" color=\"dark\" shape=\"round\" (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-status{display:inline-flex;align-items:center;width:fit-content;padding:4px 10px;border-radius:999px;font-size:.8125rem;font-weight:600;margin:0}.mfa-status--on{color:var(--ion-color-success-shade);background:rgba(var(--ion-color-success-rgb),.12)}.mfa-status--off{color:var(--ion-color-medium-shade);background:var(--ion-color-light)}.mfa-secret-row{display:flex;align-items:center;gap:8px}.mfa-secret-row .mfa-secret{flex:1;margin:0}.mfa-secret-row ion-button{flex-shrink:0;--color: var(--ion-color-dark);margin:0}.mfa-error{color:var(--ion-color-danger);font-size:.8125rem;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-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:1.25rem;color:var(--ion-color-primary);flex-shrink:0;margin-top:1px}.mfa-alert p{margin:0;font-size:.8125rem;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:.9375rem;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:.8125rem;text-align:center}.mfa-method-list{display:flex;flex-direction:column;gap:8px}.mfa-method-card{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;border-radius:12px;border:1.5px solid var(--ion-border-color, rgba(var(--ion-color-dark-rgb), .14));background:transparent;cursor:pointer;text-align:left;width:100%;transition:border-color .15s ease,background .15s ease}.mfa-method-card--active{border-color:var(--ion-color-primary);background:rgba(var(--ion-color-primary-rgb),.07)}.mfa-method-card__dot{flex-shrink:0;width:18px;height:18px;border-radius:50%;border:2px solid var(--ion-color-medium);margin-top:2px;transition:border-color .15s,box-shadow .15s}.mfa-method-card--active .mfa-method-card__dot{border-color:var(--ion-color-primary);box-shadow:inset 0 0 0 4px var(--ion-color-primary)}.mfa-method-card__body{display:flex;flex-direction:column;gap:3px}.mfa-method-card__body strong{font-size:.9375rem;font-weight:600;color:var(--ion-color-dark)}.mfa-method-card__body span{font-size:.8125rem;color:var(--ion-color-medium);line-height:1.4}.mfa-resend{text-align:center;font-size:.875rem;color:var(--ion-color-dark);margin:4px 0}.mfa-resend a{color:var(--ion-color-primary);cursor:pointer;font-weight:600}\n"] }]
35818
35818
  }], ctorParameters: () => [], propDecorators: { isOpen: [{
35819
35819
  type: Input
35820
35820
  }], prefillCode: [{
@@ -42280,7 +42280,7 @@ class AvatarUploadComponent {
42280
42280
  <ion-header [translucent]="true">
42281
42281
  <ion-toolbar>
42282
42282
  <ion-buttons slot="end">
42283
- <ion-button fill="clear" color="dark" (click)="showViewModal.set(false)">
42283
+ <ion-button fill="clear" color="dark" shape="round" (click)="showViewModal.set(false)">
42284
42284
  <strong>{{ closePhotoLabel() }}</strong>
42285
42285
  </ion-button>
42286
42286
  </ion-buttons>
@@ -42387,7 +42387,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
42387
42387
  <ion-header [translucent]="true">
42388
42388
  <ion-toolbar>
42389
42389
  <ion-buttons slot="end">
42390
- <ion-button fill="clear" color="dark" (click)="showViewModal.set(false)">
42390
+ <ion-button fill="clear" color="dark" shape="round" (click)="showViewModal.set(false)">
42391
42391
  <strong>{{ closePhotoLabel() }}</strong>
42392
42392
  </ion-button>
42393
42393
  </ion-buttons>
@@ -44805,6 +44805,7 @@ class LoginAttemptModalComponent {
44805
44805
  fill: 'outline',
44806
44806
  size: 'default',
44807
44807
  type: 'button',
44808
+ shape: 'round',
44808
44809
  state: ComponentStates.ENABLED,
44809
44810
  };
44810
44811
  }
@@ -44815,6 +44816,7 @@ class LoginAttemptModalComponent {
44815
44816
  fill: 'solid',
44816
44817
  size: 'default',
44817
44818
  type: 'button',
44819
+ shape: 'round',
44818
44820
  state: ComponentStates.ENABLED,
44819
44821
  };
44820
44822
  }
@@ -44825,6 +44827,7 @@ class LoginAttemptModalComponent {
44825
44827
  fill: 'solid',
44826
44828
  size: 'default',
44827
44829
  type: 'button',
44830
+ shape: 'round',
44828
44831
  state: ComponentStates.ENABLED,
44829
44832
  };
44830
44833
  }
@@ -44833,7 +44836,7 @@ class LoginAttemptModalComponent {
44833
44836
  <ion-header>
44834
44837
  <ion-toolbar>
44835
44838
  <ion-buttons slot="end">
44836
- <ion-button fill="clear" color="dark" (click)="close()">
44839
+ <ion-button fill="clear" color="dark" shape="round" (click)="close()">
44837
44840
  <strong>{{ closeLabel }}</strong>
44838
44841
  </ion-button>
44839
44842
  </ion-buttons>
@@ -44924,7 +44927,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
44924
44927
  <ion-header>
44925
44928
  <ion-toolbar>
44926
44929
  <ion-buttons slot="end">
44927
- <ion-button fill="clear" color="dark" (click)="close()">
44930
+ <ion-button fill="clear" color="dark" shape="round" (click)="close()">
44928
44931
  <strong>{{ closeLabel }}</strong>
44929
44932
  </ion-button>
44930
44933
  </ion-buttons>
@@ -45078,6 +45081,7 @@ class SessionListModalComponent {
45078
45081
  fill: 'clear',
45079
45082
  size: 'small',
45080
45083
  type: 'button',
45084
+ shape: 'round',
45081
45085
  state: ComponentStates.ENABLED,
45082
45086
  };
45083
45087
  }
@@ -45101,7 +45105,7 @@ class SessionListModalComponent {
45101
45105
  <ion-header>
45102
45106
  <ion-toolbar>
45103
45107
  <ion-buttons slot="end">
45104
- <ion-button fill="clear" color="dark" (click)="close()">
45108
+ <ion-button fill="clear" color="dark" shape="round" (click)="close()">
45105
45109
  <strong>{{ closeLabel }}</strong>
45106
45110
  </ion-button>
45107
45111
  </ion-buttons>
@@ -45161,7 +45165,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
45161
45165
  <ion-header>
45162
45166
  <ion-toolbar>
45163
45167
  <ion-buttons slot="end">
45164
- <ion-button fill="clear" color="dark" (click)="close()">
45168
+ <ion-button fill="clear" color="dark" shape="round" (click)="close()">
45165
45169
  <strong>{{ closeLabel }}</strong>
45166
45170
  </ion-button>
45167
45171
  </ion-buttons>
@@ -46822,7 +46826,7 @@ class CreateOrgModalComponent {
46822
46826
  <ion-header>
46823
46827
  <ion-toolbar>
46824
46828
  <ion-buttons slot="end">
46825
- <ion-button fill="clear" color="dark" (click)="dismiss()">
46829
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
46826
46830
  <strong>{{ t('close') }}</strong>
46827
46831
  </ion-button>
46828
46832
  </ion-buttons>
@@ -46840,7 +46844,7 @@ class CreateOrgModalComponent {
46840
46844
  />
46841
46845
  <val-form [props]="formMeta()" (onSubmit)="onSubmit($event)" />
46842
46846
  <div class="modal-close-bottom">
46843
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
46847
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
46844
46848
  {{ t('close') }}
46845
46849
  </ion-button>
46846
46850
  </div>
@@ -46853,7 +46857,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
46853
46857
  <ion-header>
46854
46858
  <ion-toolbar>
46855
46859
  <ion-buttons slot="end">
46856
- <ion-button fill="clear" color="dark" (click)="dismiss()">
46860
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
46857
46861
  <strong>{{ t('close') }}</strong>
46858
46862
  </ion-button>
46859
46863
  </ion-buttons>
@@ -46871,7 +46875,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
46871
46875
  />
46872
46876
  <val-form [props]="formMeta()" (onSubmit)="onSubmit($event)" />
46873
46877
  <div class="modal-close-bottom">
46874
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
46878
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
46875
46879
  {{ t('close') }}
46876
46880
  </ion-button>
46877
46881
  </div>
@@ -47035,6 +47039,7 @@ class DeleteAccountModalComponent {
47035
47039
  fill: 'solid',
47036
47040
  color: 'dark',
47037
47041
  size: 'default',
47042
+ shape: 'round',
47038
47043
  token,
47039
47044
  state: ComponentStates.ENABLED,
47040
47045
  };
@@ -47104,7 +47109,7 @@ class DeleteAccountModalComponent {
47104
47109
  <ion-header>
47105
47110
  <ion-toolbar>
47106
47111
  <ion-buttons slot="end">
47107
- <ion-button fill="clear" color="dark" (click)="close()">
47112
+ <ion-button fill="clear" color="dark" shape="round" (click)="close()">
47108
47113
  <strong>{{ t('close') }}</strong>
47109
47114
  </ion-button>
47110
47115
  </ion-buttons>
@@ -47145,7 +47150,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
47145
47150
  <ion-header>
47146
47151
  <ion-toolbar>
47147
47152
  <ion-buttons slot="end">
47148
- <ion-button fill="clear" color="dark" (click)="close()">
47153
+ <ion-button fill="clear" color="dark" shape="round" (click)="close()">
47149
47154
  <strong>{{ t('close') }}</strong>
47150
47155
  </ion-button>
47151
47156
  </ion-buttons>
@@ -47270,7 +47275,18 @@ class OrgInfoSheetComponent {
47270
47275
  ...this.closeProps,
47271
47276
  text: this.closeProps.text || this.resolvedCloseLabel(),
47272
47277
  }));
47273
- addIcons({ peopleOutline, swapHorizontalOutline, mailOutline });
47278
+ this.resolvedFeatures = computed(() => {
47279
+ if (this.features?.length)
47280
+ return this.features;
47281
+ if (!this.showFeatures)
47282
+ return [];
47283
+ return [
47284
+ { icon: 'people-outline', title: this.tt('featureCollabTitle'), desc: this.tt('featureCollabDesc') },
47285
+ { icon: 'swap-horizontal-outline', title: this.tt('featureSwitchTitle'), desc: this.tt('featureSwitchDesc') },
47286
+ { icon: 'mail-outline', title: this.tt('featureInviteTitle'), desc: this.tt('featureInviteDesc') },
47287
+ ];
47288
+ });
47289
+ addIcons({ peopleOutline, swapHorizontalOutline, mailOutline, codeSlashOutline, shieldCheckmarkOutline, warningOutline });
47274
47290
  if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$f)) {
47275
47291
  this.i18n.registerContent(DEFAULT_NAMESPACE$f, ORG_INFO_SHEET_I18N);
47276
47292
  }
@@ -47282,11 +47298,11 @@ class OrgInfoSheetComponent {
47282
47298
  return this.i18n.t(key, DEFAULT_NAMESPACE$f);
47283
47299
  }
47284
47300
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgInfoSheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
47285
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: OrgInfoSheetComponent, isStandalone: true, selector: "val-org-info-sheet", inputs: { title: "title", subtitle: "subtitle", body: "body", closeLabel: "closeLabel", showFeatures: "showFeatures", closeProps: "closeProps", _modalRef: "_modalRef" }, ngImport: i0, template: `
47301
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: OrgInfoSheetComponent, isStandalone: true, selector: "val-org-info-sheet", inputs: { title: "title", subtitle: "subtitle", body: "body", closeLabel: "closeLabel", showFeatures: "showFeatures", features: "features", closeProps: "closeProps", _modalRef: "_modalRef" }, ngImport: i0, template: `
47286
47302
  <ion-header>
47287
47303
  <ion-toolbar>
47288
47304
  <ion-buttons slot="end">
47289
- <ion-button fill="clear" color="dark" (click)="dismiss()">
47305
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
47290
47306
  <strong>{{ resolvedCloseLabel() }}</strong>
47291
47307
  </ion-button>
47292
47308
  </ion-buttons>
@@ -47297,29 +47313,17 @@ class OrgInfoSheetComponent {
47297
47313
  <val-display [props]="{ content: resolvedTitle(), size: 'small', color: 'dark' }" />
47298
47314
  <val-title [props]="{ content: resolvedSubtitle(), size: 'large', color: '', bold: false }" />
47299
47315
 
47300
- @if (showFeatures) {
47316
+ @if (resolvedFeatures().length > 0) {
47301
47317
  <div class="info-features">
47302
- <div class="info-feature">
47303
- <ion-icon name="people-outline" class="info-feature__icon" />
47304
- <div>
47305
- <div class="info-feature__title">{{ tt('featureCollabTitle') }}</div>
47306
- <div class="info-feature__desc">{{ tt('featureCollabDesc') }}</div>
47307
- </div>
47308
- </div>
47309
- <div class="info-feature">
47310
- <ion-icon name="swap-horizontal-outline" class="info-feature__icon" />
47311
- <div>
47312
- <div class="info-feature__title">{{ tt('featureSwitchTitle') }}</div>
47313
- <div class="info-feature__desc">{{ tt('featureSwitchDesc') }}</div>
47314
- </div>
47315
- </div>
47316
- <div class="info-feature">
47317
- <ion-icon name="mail-outline" class="info-feature__icon" />
47318
- <div>
47319
- <div class="info-feature__title">{{ tt('featureInviteTitle') }}</div>
47320
- <div class="info-feature__desc">{{ tt('featureInviteDesc') }}</div>
47318
+ @for (f of resolvedFeatures(); track $index) {
47319
+ <div class="info-feature">
47320
+ <ion-icon [name]="f.icon" class="info-feature__icon" />
47321
+ <div>
47322
+ <div class="info-feature__title">{{ f.title }}</div>
47323
+ <div class="info-feature__desc">{{ f.desc }}</div>
47324
+ </div>
47321
47325
  </div>
47322
- </div>
47326
+ }
47323
47327
  </div>
47324
47328
  }
47325
47329
 
@@ -47357,7 +47361,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
47357
47361
  <ion-header>
47358
47362
  <ion-toolbar>
47359
47363
  <ion-buttons slot="end">
47360
- <ion-button fill="clear" color="dark" (click)="dismiss()">
47364
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
47361
47365
  <strong>{{ resolvedCloseLabel() }}</strong>
47362
47366
  </ion-button>
47363
47367
  </ion-buttons>
@@ -47368,29 +47372,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
47368
47372
  <val-display [props]="{ content: resolvedTitle(), size: 'small', color: 'dark' }" />
47369
47373
  <val-title [props]="{ content: resolvedSubtitle(), size: 'large', color: '', bold: false }" />
47370
47374
 
47371
- @if (showFeatures) {
47375
+ @if (resolvedFeatures().length > 0) {
47372
47376
  <div class="info-features">
47373
- <div class="info-feature">
47374
- <ion-icon name="people-outline" class="info-feature__icon" />
47375
- <div>
47376
- <div class="info-feature__title">{{ tt('featureCollabTitle') }}</div>
47377
- <div class="info-feature__desc">{{ tt('featureCollabDesc') }}</div>
47378
- </div>
47379
- </div>
47380
- <div class="info-feature">
47381
- <ion-icon name="swap-horizontal-outline" class="info-feature__icon" />
47382
- <div>
47383
- <div class="info-feature__title">{{ tt('featureSwitchTitle') }}</div>
47384
- <div class="info-feature__desc">{{ tt('featureSwitchDesc') }}</div>
47385
- </div>
47386
- </div>
47387
- <div class="info-feature">
47388
- <ion-icon name="mail-outline" class="info-feature__icon" />
47389
- <div>
47390
- <div class="info-feature__title">{{ tt('featureInviteTitle') }}</div>
47391
- <div class="info-feature__desc">{{ tt('featureInviteDesc') }}</div>
47377
+ @for (f of resolvedFeatures(); track $index) {
47378
+ <div class="info-feature">
47379
+ <ion-icon [name]="f.icon" class="info-feature__icon" />
47380
+ <div>
47381
+ <div class="info-feature__title">{{ f.title }}</div>
47382
+ <div class="info-feature__desc">{{ f.desc }}</div>
47383
+ </div>
47392
47384
  </div>
47393
- </div>
47385
+ }
47394
47386
  </div>
47395
47387
  }
47396
47388
 
@@ -47420,6 +47412,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
47420
47412
  type: Input
47421
47413
  }], showFeatures: [{
47422
47414
  type: Input
47415
+ }], features: [{
47416
+ type: Input
47423
47417
  }], closeProps: [{
47424
47418
  type: Input
47425
47419
  }], _modalRef: [{
@@ -47545,7 +47539,7 @@ class SwitchOrgModalComponent {
47545
47539
  <ion-header>
47546
47540
  <ion-toolbar>
47547
47541
  <ion-buttons slot="end">
47548
- <ion-button fill="clear" color="dark" (click)="dismiss()">
47542
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
47549
47543
  <strong>{{ t('close') }}</strong>
47550
47544
  </ion-button>
47551
47545
  </ion-buttons>
@@ -47636,7 +47630,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
47636
47630
  <ion-header>
47637
47631
  <ion-toolbar>
47638
47632
  <ion-buttons slot="end">
47639
- <ion-button fill="clear" color="dark" (click)="dismiss()">
47633
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
47640
47634
  <strong>{{ t('close') }}</strong>
47641
47635
  </ion-button>
47642
47636
  </ion-buttons>
@@ -48803,7 +48797,7 @@ class EditOrgModalComponent {
48803
48797
  <ion-header>
48804
48798
  <ion-toolbar>
48805
48799
  <ion-buttons slot="end">
48806
- <ion-button fill="clear" color="dark" (click)="dismiss()">
48800
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
48807
48801
  <strong>{{ t('close') }}</strong>
48808
48802
  </ion-button>
48809
48803
  </ion-buttons>
@@ -48821,7 +48815,7 @@ class EditOrgModalComponent {
48821
48815
  />
48822
48816
  <val-form [props]="formMeta()" (onSubmit)="onFormSubmit($event)" />
48823
48817
  <div class="modal-close-bottom">
48824
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
48818
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
48825
48819
  {{ t('close') }}
48826
48820
  </ion-button>
48827
48821
  </div>
@@ -48834,7 +48828,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
48834
48828
  <ion-header>
48835
48829
  <ion-toolbar>
48836
48830
  <ion-buttons slot="end">
48837
- <ion-button fill="clear" color="dark" (click)="dismiss()">
48831
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
48838
48832
  <strong>{{ t('close') }}</strong>
48839
48833
  </ion-button>
48840
48834
  </ion-buttons>
@@ -48852,7 +48846,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
48852
48846
  />
48853
48847
  <val-form [props]="formMeta()" (onSubmit)="onFormSubmit($event)" />
48854
48848
  <div class="modal-close-bottom">
48855
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
48849
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
48856
48850
  {{ t('close') }}
48857
48851
  </ion-button>
48858
48852
  </div>
@@ -48982,6 +48976,7 @@ class InviteMemberModalComponent {
48982
48976
  fill: 'solid',
48983
48977
  expand: 'block',
48984
48978
  size: 'default',
48979
+ shape: 'round',
48985
48980
  type: 'button',
48986
48981
  state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
48987
48982
  }));
@@ -48992,6 +48987,7 @@ class InviteMemberModalComponent {
48992
48987
  fill: 'solid',
48993
48988
  expand: 'block',
48994
48989
  size: 'default',
48990
+ shape: 'round',
48995
48991
  type: 'button',
48996
48992
  state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
48997
48993
  }));
@@ -49164,7 +49160,7 @@ class InviteMemberModalComponent {
49164
49160
  <ion-header>
49165
49161
  <ion-toolbar>
49166
49162
  <ion-buttons slot="end">
49167
- <ion-button fill="clear" color="dark" (click)="dismiss()">
49163
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
49168
49164
  <strong>{{ t('close') }}</strong>
49169
49165
  </ion-button>
49170
49166
  </ion-buttons>
@@ -49276,7 +49272,7 @@ class InviteMemberModalComponent {
49276
49272
  </div>
49277
49273
 
49278
49274
  <div class="modal-close-bottom">
49279
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
49275
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
49280
49276
  {{ t('close') }}
49281
49277
  </ion-button>
49282
49278
  </div>
@@ -49305,7 +49301,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49305
49301
  <ion-header>
49306
49302
  <ion-toolbar>
49307
49303
  <ion-buttons slot="end">
49308
- <ion-button fill="clear" color="dark" (click)="dismiss()">
49304
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
49309
49305
  <strong>{{ t('close') }}</strong>
49310
49306
  </ion-button>
49311
49307
  </ion-buttons>
@@ -49417,7 +49413,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49417
49413
  </div>
49418
49414
 
49419
49415
  <div class="modal-close-bottom">
49420
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
49416
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
49421
49417
  {{ t('close') }}
49422
49418
  </ion-button>
49423
49419
  </div>
@@ -49697,6 +49693,7 @@ class MemberDetailModalComponent {
49697
49693
  fill: 'outline',
49698
49694
  expand: 'block',
49699
49695
  size: 'default',
49696
+ shape: 'round',
49700
49697
  type: 'button',
49701
49698
  state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
49702
49699
  }));
@@ -49814,7 +49811,7 @@ class MemberDetailModalComponent {
49814
49811
  <ion-header>
49815
49812
  <ion-toolbar>
49816
49813
  <ion-buttons slot="end">
49817
- <ion-button fill="clear" color="dark" (click)="dismiss()">
49814
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
49818
49815
  <strong>{{ t('close') }}</strong>
49819
49816
  </ion-button>
49820
49817
  </ion-buttons>
@@ -49885,7 +49882,7 @@ class MemberDetailModalComponent {
49885
49882
  }
49886
49883
 
49887
49884
  <div class="modal-close-bottom">
49888
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
49885
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
49889
49886
  {{ t('close') }}
49890
49887
  </ion-button>
49891
49888
  </div>
@@ -49911,7 +49908,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49911
49908
  <ion-header>
49912
49909
  <ion-toolbar>
49913
49910
  <ion-buttons slot="end">
49914
- <ion-button fill="clear" color="dark" (click)="dismiss()">
49911
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
49915
49912
  <strong>{{ t('close') }}</strong>
49916
49913
  </ion-button>
49917
49914
  </ion-buttons>
@@ -49982,7 +49979,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49982
49979
  }
49983
49980
 
49984
49981
  <div class="modal-close-bottom">
49985
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
49982
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
49986
49983
  {{ t('close') }}
49987
49984
  </ion-button>
49988
49985
  </div>
@@ -50094,7 +50091,7 @@ class TransferOwnershipModalComponent {
50094
50091
  <ion-header>
50095
50092
  <ion-toolbar>
50096
50093
  <ion-buttons slot="end">
50097
- <ion-button fill="clear" color="dark" (click)="dismiss()">
50094
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
50098
50095
  <strong>{{ t('close') }}</strong>
50099
50096
  </ion-button>
50100
50097
  </ion-buttons>
@@ -50154,13 +50151,13 @@ class TransferOwnershipModalComponent {
50154
50151
  }
50155
50152
 
50156
50153
  <div class="transfer-confirm-row">
50157
- <ion-button expand="block" color="dark" [disabled]="!selectedId()" (click)="confirm()">
50154
+ <ion-button expand="block" color="dark" shape="round" [disabled]="!selectedId()" (click)="confirm()">
50158
50155
  {{ t('confirmCta') }}
50159
50156
  </ion-button>
50160
50157
  </div>
50161
50158
 
50162
50159
  <div class="modal-close-bottom">
50163
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
50160
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
50164
50161
  {{ t('close') }}
50165
50162
  </ion-button>
50166
50163
  </div>
@@ -50183,7 +50180,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
50183
50180
  <ion-header>
50184
50181
  <ion-toolbar>
50185
50182
  <ion-buttons slot="end">
50186
- <ion-button fill="clear" color="dark" (click)="dismiss()">
50183
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
50187
50184
  <strong>{{ t('close') }}</strong>
50188
50185
  </ion-button>
50189
50186
  </ion-buttons>
@@ -50243,13 +50240,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
50243
50240
  }
50244
50241
 
50245
50242
  <div class="transfer-confirm-row">
50246
- <ion-button expand="block" color="dark" [disabled]="!selectedId()" (click)="confirm()">
50243
+ <ion-button expand="block" color="dark" shape="round" [disabled]="!selectedId()" (click)="confirm()">
50247
50244
  {{ t('confirmCta') }}
50248
50245
  </ion-button>
50249
50246
  </div>
50250
50247
 
50251
50248
  <div class="modal-close-bottom">
50252
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
50249
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
50253
50250
  {{ t('close') }}
50254
50251
  </ion-button>
50255
50252
  </div>
@@ -50506,7 +50503,7 @@ class MemberImportModalComponent {
50506
50503
  <ion-header>
50507
50504
  <ion-toolbar>
50508
50505
  <ion-buttons slot="end">
50509
- <ion-button fill="clear" color="dark" (click)="dismiss()">
50506
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
50510
50507
  <strong>{{ t('close') }}</strong>
50511
50508
  </ion-button>
50512
50509
  </ion-buttons>
@@ -50604,7 +50601,7 @@ class MemberImportModalComponent {
50604
50601
  }
50605
50602
 
50606
50603
  <div class="import-actions">
50607
- <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
50604
+ <ion-button expand="block" color="dark" shape="round" [disabled]="submitting()" (click)="submit()">
50608
50605
  @if (submitting()) {
50609
50606
  <ion-spinner name="dots" />
50610
50607
  &nbsp;{{ t('submitting') }}
@@ -50628,14 +50625,14 @@ class MemberImportModalComponent {
50628
50625
  }
50629
50626
  </div>
50630
50627
  <div class="import-actions">
50631
- <ion-button expand="block" fill="outline" color="dark" (click)="reset()">
50628
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="reset()">
50632
50629
  {{ t('again') }}
50633
50630
  </ion-button>
50634
50631
  </div>
50635
50632
  }
50636
50633
 
50637
50634
  <div class="modal-close-bottom">
50638
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
50635
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
50639
50636
  {{ t('close') }}
50640
50637
  </ion-button>
50641
50638
  </div>
@@ -50664,7 +50661,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
50664
50661
  <ion-header>
50665
50662
  <ion-toolbar>
50666
50663
  <ion-buttons slot="end">
50667
- <ion-button fill="clear" color="dark" (click)="dismiss()">
50664
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
50668
50665
  <strong>{{ t('close') }}</strong>
50669
50666
  </ion-button>
50670
50667
  </ion-buttons>
@@ -50762,7 +50759,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
50762
50759
  }
50763
50760
 
50764
50761
  <div class="import-actions">
50765
- <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
50762
+ <ion-button expand="block" color="dark" shape="round" [disabled]="submitting()" (click)="submit()">
50766
50763
  @if (submitting()) {
50767
50764
  <ion-spinner name="dots" />
50768
50765
  &nbsp;{{ t('submitting') }}
@@ -50786,14 +50783,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
50786
50783
  }
50787
50784
  </div>
50788
50785
  <div class="import-actions">
50789
- <ion-button expand="block" fill="outline" color="dark" (click)="reset()">
50786
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="reset()">
50790
50787
  {{ t('again') }}
50791
50788
  </ion-button>
50792
50789
  </div>
50793
50790
  }
50794
50791
 
50795
50792
  <div class="modal-close-bottom">
50796
- <ion-button expand="block" fill="outline" color="dark" (click)="dismiss()">
50793
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="dismiss()">
50797
50794
  {{ t('close') }}
50798
50795
  </ion-button>
50799
50796
  </div>
@@ -50873,8 +50870,14 @@ const ORGANIZATION_VIEW_I18N = {
50873
50870
  apiKeysMoreInfo: 'Más información',
50874
50871
  apiKeysInfoTitle: 'Integraciones por API',
50875
50872
  apiKeysInfoSubtitle: '¿Qué puedes hacer con una API Key?',
50876
- apiKeysInfoContent: 'Una API Key te permite automatizar operaciones en tu organización desde tus propios sistemas, sin abrir la app. Es ideal para integraciones máquina-a-máquina: sincronizar usuarios desde tu ERP, asignar roles en masa o consultar datos desde tus reportes internos.\n\nCon una key puedes dar de alta y gestionar miembros, asignar roles y permisos, leer datos de la organización y conectar flujos de trabajo existentes.\n\nCada key lleva solo los permisos que tú le asignes — concede el mínimo necesario y revócala en cualquier momento desde la sección Integraciones.\n\nImportante: el secreto se muestra una sola vez al crear la key. Guárdalo en un lugar seguro (gestor de secretos, variable de entorno). Si lo pierdes, genera una nueva key y revoca la anterior.',
50873
+ apiKeysInfoContent: 'Revoca cualquier key en cualquier momento desde la sección Integraciones.',
50877
50874
  apiKeysInfoClose: 'Cerrar',
50875
+ apiKeysFeatureAutoTitle: 'Automatización',
50876
+ apiKeysFeatureAutoDesc: 'Conecta tus sistemas externos directamente, sin abrir la app.',
50877
+ apiKeysFeaturePermsTitle: 'Permisos a medida',
50878
+ apiKeysFeaturePermsDesc: 'Cada key lleva solo los permisos que tú le asignes — concede el mínimo.',
50879
+ apiKeysFeatureSecretTitle: 'Secreto único',
50880
+ apiKeysFeatureSecretDesc: 'El secreto se muestra una sola vez. Guárdalo en un gestor de secretos.',
50878
50881
  orgsMoreInfo: 'Aprender más sobre organizaciones',
50879
50882
  orgsInfoTitle: '¿Qué son las organizaciones?',
50880
50883
  orgsInfoSubtitle: 'Todo lo que necesitas saber',
@@ -50956,8 +50959,14 @@ const ORGANIZATION_VIEW_I18N = {
50956
50959
  apiKeysMoreInfo: 'More information',
50957
50960
  apiKeysInfoTitle: 'API integrations',
50958
50961
  apiKeysInfoSubtitle: 'What can you do with an API Key?',
50959
- apiKeysInfoContent: 'An API Key lets you automate operations in your organization from your own systems, without opening the app. It is ideal for machine-to-machine integrations: sync users from your ERP, bulk-assign roles, or query data for your internal reports.\n\nWith a key you can provision and manage members, assign roles and permissions, read organization data, and connect existing workflows.\n\nEach key carries only the permissions you assign — grant the minimum needed and revoke it at any time from the Integrations section.\n\nImportant: the secret is shown only once when the key is created. Store it somewhere safe (secret manager, environment variable). If you lose it, create a new key and revoke the old one.',
50962
+ apiKeysInfoContent: 'Revoke any key at any time from the Integrations section.',
50960
50963
  apiKeysInfoClose: 'Close',
50964
+ apiKeysFeatureAutoTitle: 'Automation',
50965
+ apiKeysFeatureAutoDesc: 'Connect your external systems directly, without opening the app.',
50966
+ apiKeysFeaturePermsTitle: 'Custom permissions',
50967
+ apiKeysFeaturePermsDesc: 'Each key carries only the permissions you assign — grant the minimum.',
50968
+ apiKeysFeatureSecretTitle: 'One-time secret',
50969
+ apiKeysFeatureSecretDesc: 'The secret is shown only once. Store it in a secret manager.',
50961
50970
  orgsMoreInfo: 'Learn more about organizations',
50962
50971
  orgsInfoTitle: 'What are organizations?',
50963
50972
  orgsInfoSubtitle: 'Everything you need to know',
@@ -51187,7 +51196,7 @@ class OrganizationViewComponent {
51187
51196
  this.openApiKeysButtonProps = computed(() => ({
51188
51197
  text: this.tt('apiKeysOpen'),
51189
51198
  color: 'dark',
51190
- fill: 'clear',
51199
+ fill: 'outline',
51191
51200
  size: 'default',
51192
51201
  shape: 'round',
51193
51202
  type: 'button',
@@ -51332,6 +51341,11 @@ class OrganizationViewComponent {
51332
51341
  component: OrgInfoSheetComponent,
51333
51342
  componentProps: {
51334
51343
  showFeatures: false,
51344
+ features: [
51345
+ { icon: 'code-slash-outline', title: this.tt('apiKeysFeatureAutoTitle'), desc: this.tt('apiKeysFeatureAutoDesc') },
51346
+ { icon: 'shield-checkmark-outline', title: this.tt('apiKeysFeaturePermsTitle'), desc: this.tt('apiKeysFeaturePermsDesc') },
51347
+ { icon: 'warning-outline', title: this.tt('apiKeysFeatureSecretTitle'), desc: this.tt('apiKeysFeatureSecretDesc') },
51348
+ ],
51335
51349
  title: this.tt('apiKeysInfoTitle'),
51336
51350
  subtitle: this.tt('apiKeysInfoSubtitle'),
51337
51351
  body: this.tt('apiKeysInfoContent'),
@@ -51343,6 +51357,7 @@ class OrganizationViewComponent {
51343
51357
  size: 'default',
51344
51358
  expand: 'block',
51345
51359
  type: 'button',
51360
+ shape: 'round',
51346
51361
  },
51347
51362
  },
51348
51363
  breakpoints: { breakpoints: [0, 0.92], initialBreakpoint: 0.92, showHandle: true },
@@ -53824,7 +53839,7 @@ class ApiKeyCreateModalComponent {
53824
53839
  <ion-header>
53825
53840
  <ion-toolbar>
53826
53841
  <ion-buttons slot="end">
53827
- <ion-button fill="clear" color="dark" (click)="dismiss()">
53842
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
53828
53843
  <strong>{{ t('close') }}</strong>
53829
53844
  </ion-button>
53830
53845
  </ion-buttons>
@@ -53839,10 +53854,10 @@ class ApiKeyCreateModalComponent {
53839
53854
  <code>{{ s.secret }}</code>
53840
53855
  </div>
53841
53856
  <div class="apikey-actions">
53842
- <ion-button expand="block" color="dark" (click)="copy(s.secret)">
53857
+ <ion-button expand="block" color="dark" shape="round" (click)="copy(s.secret)">
53843
53858
  {{ copied() ? t('copied') : t('copy') }}
53844
53859
  </ion-button>
53845
- <ion-button expand="block" fill="outline" color="dark" (click)="finishSecret()">
53860
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="finishSecret()">
53846
53861
  {{ t('done') }}
53847
53862
  </ion-button>
53848
53863
  </div>
@@ -53882,7 +53897,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
53882
53897
  <ion-header>
53883
53898
  <ion-toolbar>
53884
53899
  <ion-buttons slot="end">
53885
- <ion-button fill="clear" color="dark" (click)="dismiss()">
53900
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
53886
53901
  <strong>{{ t('close') }}</strong>
53887
53902
  </ion-button>
53888
53903
  </ion-buttons>
@@ -53897,10 +53912,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
53897
53912
  <code>{{ s.secret }}</code>
53898
53913
  </div>
53899
53914
  <div class="apikey-actions">
53900
- <ion-button expand="block" color="dark" (click)="copy(s.secret)">
53915
+ <ion-button expand="block" color="dark" shape="round" (click)="copy(s.secret)">
53901
53916
  {{ copied() ? t('copied') : t('copy') }}
53902
53917
  </ion-button>
53903
- <ion-button expand="block" fill="outline" color="dark" (click)="finishSecret()">
53918
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="finishSecret()">
53904
53919
  {{ t('done') }}
53905
53920
  </ion-button>
53906
53921
  </div>
@@ -54458,7 +54473,7 @@ function createRefreshableStream(factory, options) {
54458
54473
  // sesión NUNCA se establece (token inválido, signInWithFirebase falló), el
54459
54474
  // gate igual se libera tras `authGateTimeoutMs` y la factory procede: si no
54460
54475
  // hay permiso terminará en estado `error`, jamás en skeleton infinito.
54461
- // Tag los dos inputs del `race` para distinguir en logs cuál ganó: la sesión
54476
+ // Tag los dos inputs del `race` para distivdsr en logs cuál ganó: la sesión
54462
54477
  // de Firebase Auth (`auth-ready`) o el timeout de seguridad (`timeout`).
54463
54478
  const authGate$ = firebase
54464
54479
  ? race(firebase.firebaseAuthReady$.pipe(map$1(() => 'auth-ready')), timer(authGateTimeoutMs).pipe(map$1(() => 'timeout'))).pipe(take(1), tap(winner => {
@@ -55427,7 +55442,6 @@ class ContentReactionComponent {
55427
55442
  return this.resolvedProps().showComment;
55428
55443
  });
55429
55444
  this.canSubmit = computed(() => this.state().selectedValue !== null && !this.state().isLoading);
55430
- addIcons({ closeOutline });
55431
55445
  // Auto-registro de defaults i18n (es/en) si el consumer no proveyó el
55432
55446
  // namespace. Garantiza que `t()` nunca caiga en key faltante. Ver
55433
55447
  // CONTENT_REACTION_I18N. Idempotente: solo la 1ª instancia registra.
@@ -55578,11 +55592,11 @@ class ContentReactionComponent {
55578
55592
  return this.i18n.t(key, 'ContentReaction') || translations[key] || key;
55579
55593
  }
55580
55594
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
55581
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ContentReactionComponent, isStandalone: true, selector: "val-content-reaction", inputs: { props: "props", _modalRef: "_modalRef" }, outputs: { reactionSubmit: "reactionSubmit", reactionChange: "reactionChange" }, usesOnChanges: true, ngImport: i0, template: "@if (isConfigured()) {\n<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Bot\u00F3n de cierre (solo en modo modal/sheet) -->\n @if (resolvedProps().showCloseButton) {\n <ion-button fill=\"clear\" color=\"dark\" class=\"close-button\" [attr.aria-label]=\"t('close')\" (click)=\"close()\">\n <ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n }\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n}\n", styles: [":host{display:block}.content-reaction{position:relative;padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.close-button{position:absolute;top:0;right:0;margin:0;--padding-start: 6px;--padding-end: 6px;--color: var(--ion-color-medium)}.close-button ion-icon{font-size:1.5rem}.question{font-size:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:.875rem}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }] }); }
55595
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ContentReactionComponent, isStandalone: true, selector: "val-content-reaction", inputs: { props: "props", _modalRef: "_modalRef" }, outputs: { reactionSubmit: "reactionSubmit", reactionChange: "reactionChange" }, usesOnChanges: true, ngImport: i0, template: "@if (isConfigured()) {\n<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Bot\u00F3n de cierre (solo en modo modal/sheet) -->\n @if (resolvedProps().showCloseButton) {\n <ion-button fill=\"clear\" color=\"dark\" shape=\"round\" class=\"close-button\" (click)=\"close()\">\n <strong>{{ t('close') }}</strong>\n </ion-button>\n }\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n}\n", styles: [":host{display:block}.content-reaction{position:relative;padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.close-button{position:absolute;top:0;right:0;margin:0;--padding-start: 6px;--padding-end: 6px;--color: var(--ion-color-medium)}.close-button ion-icon{font-size:1.5rem}.question{font-size:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:.875rem}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }] }); }
55582
55596
  }
55583
55597
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, decorators: [{
55584
55598
  type: Component,
55585
- args: [{ selector: 'val-content-reaction', standalone: true, imports: [CommonModule, FormsModule, IonButton, IonIcon, IonSpinner, IonTextarea], template: "@if (isConfigured()) {\n<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Bot\u00F3n de cierre (solo en modo modal/sheet) -->\n @if (resolvedProps().showCloseButton) {\n <ion-button fill=\"clear\" color=\"dark\" class=\"close-button\" [attr.aria-label]=\"t('close')\" (click)=\"close()\">\n <ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n }\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n}\n", styles: [":host{display:block}.content-reaction{position:relative;padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.close-button{position:absolute;top:0;right:0;margin:0;--padding-start: 6px;--padding-end: 6px;--color: var(--ion-color-medium)}.close-button ion-icon{font-size:1.5rem}.question{font-size:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:.875rem}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
55599
+ args: [{ selector: 'val-content-reaction', standalone: true, imports: [CommonModule, FormsModule, IonButton, IonSpinner, IonTextarea], template: "@if (isConfigured()) {\n<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Bot\u00F3n de cierre (solo en modo modal/sheet) -->\n @if (resolvedProps().showCloseButton) {\n <ion-button fill=\"clear\" color=\"dark\" shape=\"round\" class=\"close-button\" (click)=\"close()\">\n <strong>{{ t('close') }}</strong>\n </ion-button>\n }\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n}\n", styles: [":host{display:block}.content-reaction{position:relative;padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.close-button{position:absolute;top:0;right:0;margin:0;--padding-start: 6px;--padding-end: 6px;--color: var(--ion-color-medium)}.close-button ion-icon{font-size:1.5rem}.question{font-size:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:.875rem}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
55586
55600
  }], ctorParameters: () => [], propDecorators: { props: [{
55587
55601
  type: Input
55588
55602
  }], _modalRef: [{
@@ -56605,7 +56619,7 @@ class ApiKeysModalComponent {
56605
56619
  <ion-header>
56606
56620
  <ion-toolbar>
56607
56621
  <ion-buttons slot="end">
56608
- <ion-button fill="clear" color="dark" (click)="dismiss()">
56622
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
56609
56623
  <strong>{{ t('close') }}</strong>
56610
56624
  </ion-button>
56611
56625
  </ion-buttons>
@@ -56625,10 +56639,10 @@ class ApiKeysModalComponent {
56625
56639
  <code>{{ s.secret }}</code>
56626
56640
  </div>
56627
56641
  <div class="apikey-actions">
56628
- <ion-button expand="block" color="dark" (click)="copy(s.secret)">
56642
+ <ion-button expand="block" color="dark" shape="round" (click)="copy(s.secret)">
56629
56643
  {{ copied() ? t('copied') : t('copy') }}
56630
56644
  </ion-button>
56631
- <ion-button expand="block" fill="outline" color="dark" (click)="finishSecret()">
56645
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="finishSecret()">
56632
56646
  {{ t('done') }}
56633
56647
  </ion-button>
56634
56648
  </div>
@@ -56651,7 +56665,7 @@ class ApiKeysModalComponent {
56651
56665
  {{ k.lastUsedAt || t('never') }}
56652
56666
  </span>
56653
56667
  </div>
56654
- <ion-button fill="clear" color="danger" size="small" (click)="revoke(k)">
56668
+ <ion-button fill="clear" color="danger" size="small" shape="round" (click)="revoke(k)">
56655
56669
  {{ t('revoke') }}
56656
56670
  </ion-button>
56657
56671
  </div>
@@ -56674,13 +56688,13 @@ class ApiKeysModalComponent {
56674
56688
  (selectedChange)="selectedPerms.set($event)"
56675
56689
  />
56676
56690
  <val-form [props]="createForm()" (onSubmit)="onCreateSubmit($event)" />
56677
- <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
56691
+ <ion-button expand="block" fill="clear" color="medium" shape="round" (click)="showCreate.set(false)">
56678
56692
  {{ t('cancel') }}
56679
56693
  </ion-button>
56680
56694
  </div>
56681
56695
  } @else {
56682
56696
  <div class="apikey-actions">
56683
- <ion-button expand="block" color="primary" (click)="openCreate()">
56697
+ <ion-button expand="block" color="primary" shape="round" (click)="openCreate()">
56684
56698
  {{ t('newKey') }}
56685
56699
  </ion-button>
56686
56700
  </div>
@@ -56708,7 +56722,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
56708
56722
  <ion-header>
56709
56723
  <ion-toolbar>
56710
56724
  <ion-buttons slot="end">
56711
- <ion-button fill="clear" color="dark" (click)="dismiss()">
56725
+ <ion-button fill="clear" color="dark" shape="round" (click)="dismiss()">
56712
56726
  <strong>{{ t('close') }}</strong>
56713
56727
  </ion-button>
56714
56728
  </ion-buttons>
@@ -56728,10 +56742,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
56728
56742
  <code>{{ s.secret }}</code>
56729
56743
  </div>
56730
56744
  <div class="apikey-actions">
56731
- <ion-button expand="block" color="dark" (click)="copy(s.secret)">
56745
+ <ion-button expand="block" color="dark" shape="round" (click)="copy(s.secret)">
56732
56746
  {{ copied() ? t('copied') : t('copy') }}
56733
56747
  </ion-button>
56734
- <ion-button expand="block" fill="outline" color="dark" (click)="finishSecret()">
56748
+ <ion-button expand="block" fill="outline" color="dark" shape="round" (click)="finishSecret()">
56735
56749
  {{ t('done') }}
56736
56750
  </ion-button>
56737
56751
  </div>
@@ -56754,7 +56768,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
56754
56768
  {{ k.lastUsedAt || t('never') }}
56755
56769
  </span>
56756
56770
  </div>
56757
- <ion-button fill="clear" color="danger" size="small" (click)="revoke(k)">
56771
+ <ion-button fill="clear" color="danger" size="small" shape="round" (click)="revoke(k)">
56758
56772
  {{ t('revoke') }}
56759
56773
  </ion-button>
56760
56774
  </div>
@@ -56777,13 +56791,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
56777
56791
  (selectedChange)="selectedPerms.set($event)"
56778
56792
  />
56779
56793
  <val-form [props]="createForm()" (onSubmit)="onCreateSubmit($event)" />
56780
- <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
56794
+ <ion-button expand="block" fill="clear" color="medium" shape="round" (click)="showCreate.set(false)">
56781
56795
  {{ t('cancel') }}
56782
56796
  </ion-button>
56783
56797
  </div>
56784
56798
  } @else {
56785
56799
  <div class="apikey-actions">
56786
- <ion-button expand="block" color="primary" (click)="openCreate()">
56800
+ <ion-button expand="block" color="primary" shape="round" (click)="openCreate()">
56787
56801
  {{ t('newKey') }}
56788
56802
  </ion-button>
56789
56803
  </div>