valtech-components 2.0.987 → 2.0.989

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 (28) hide show
  1. package/esm2022/lib/components/organisms/edit-org-modal/edit-org-modal.component.mjs +229 -0
  2. package/esm2022/lib/components/organisms/edit-org-modal/edit-org-modal.i18n.mjs +38 -0
  3. package/esm2022/lib/components/organisms/invite-member-modal/invite-member-modal.component.mjs +519 -0
  4. package/esm2022/lib/components/organisms/invite-member-modal/invite-member-modal.i18n.mjs +54 -0
  5. package/esm2022/lib/components/organisms/member-detail-modal/member-detail-modal.component.mjs +406 -0
  6. package/esm2022/lib/components/organisms/member-detail-modal/member-detail-modal.i18n.mjs +72 -0
  7. package/esm2022/lib/components/organisms/member-detail-modal/types.mjs +2 -0
  8. package/esm2022/lib/components/organisms/transfer-ownership-modal/transfer-ownership-modal.component.mjs +231 -0
  9. package/esm2022/lib/components/organisms/transfer-ownership-modal/transfer-ownership-modal.i18n.mjs +27 -0
  10. package/esm2022/lib/components/organisms/transfer-ownership-modal/types.mjs +2 -0
  11. package/esm2022/lib/version.mjs +2 -2
  12. package/esm2022/public-api.mjs +7 -1
  13. package/fesm2022/valtech-components.mjs +1539 -28
  14. package/fesm2022/valtech-components.mjs.map +1 -1
  15. package/lib/components/organisms/edit-org-modal/edit-org-modal.component.d.ts +49 -0
  16. package/lib/components/organisms/edit-org-modal/edit-org-modal.i18n.d.ts +7 -0
  17. package/lib/components/organisms/invite-member-modal/invite-member-modal.component.d.ts +74 -0
  18. package/lib/components/organisms/invite-member-modal/invite-member-modal.i18n.d.ts +7 -0
  19. package/lib/components/organisms/member-detail-modal/member-detail-modal.component.d.ts +67 -0
  20. package/lib/components/organisms/member-detail-modal/member-detail-modal.i18n.d.ts +7 -0
  21. package/lib/components/organisms/member-detail-modal/types.d.ts +17 -0
  22. package/lib/components/organisms/transfer-ownership-modal/transfer-ownership-modal.component.d.ts +44 -0
  23. package/lib/components/organisms/transfer-ownership-modal/transfer-ownership-modal.i18n.d.ts +8 -0
  24. package/lib/components/organisms/transfer-ownership-modal/types.d.ts +11 -0
  25. package/lib/version.d.ts +1 -1
  26. package/package.json +1 -1
  27. package/public-api.d.ts +6 -0
  28. package/src/lib/services/firebase/firebase-messaging-sw.js +0 -145
@@ -1,11 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, InjectionToken, makeEnvironmentProviders, APP_INITIALIZER, ErrorHandler, Injectable, DestroyRef, Inject, signal, computed, PLATFORM_ID, isDevMode, Optional, runInInjectionContext, effect, Pipe, TemplateRef, ViewContainerRef, input, Directive, EventEmitter, Component, Input, Output, ChangeDetectionStrategy, HostBinding, HostListener, NgZone, ViewChild, ChangeDetectorRef, output, ContentChild, ElementRef, viewChild, untracked, Injector, isSignal, ViewEncapsulation } from '@angular/core';
3
3
  import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, shareReplay, race, timer, forkJoin } from 'rxjs';
4
- import { catchError, switchMap, finalize, filter, take, map as map$1, tap, retry, timeout, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
4
+ import { catchError, switchMap, finalize, filter, take, map as map$1, tap, distinctUntilChanged as distinctUntilChanged$1, retry, timeout, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
5
5
  import * as i1$3 from '@angular/common/http';
6
6
  import { provideHttpClient, withInterceptors, HttpClient, HttpErrorResponse } from '@angular/common/http';
7
7
  import * as i1$1 from '@angular/fire/firestore';
8
- import { provideFirestore, getFirestore, connectFirestoreEmulator, enableIndexedDbPersistence, doc, getDoc, collection, query as query$1, getDocs, getCountFromServer, limit, docData, collectionData, serverTimestamp, addDoc, setDoc, updateDoc, deleteDoc, writeBatch, arrayUnion, arrayRemove, increment, where, orderBy, startAfter, startAt, endBefore, endAt, Timestamp, onSnapshot } from '@angular/fire/firestore';
8
+ import { provideFirestore, getFirestore, connectFirestoreEmulator, enableIndexedDbPersistence, doc, getDoc, collection, query as query$1, getDocs, getCountFromServer, limit, docData, collectionData, serverTimestamp, addDoc, setDoc, updateDoc, deleteDoc, writeBatch, arrayUnion, arrayRemove, increment, where, orderBy, startAfter, startAt, endBefore, endAt, Timestamp, onSnapshot, Firestore } from '@angular/fire/firestore';
9
9
  import * as i1$5 from '@angular/common';
10
10
  import { isPlatformBrowser, CommonModule, NgStyle, NgFor, DOCUMENT, NgClass, TitleCasePipe } from '@angular/common';
11
11
  import { provideAnalytics, getAnalytics, Analytics, logEvent, setUserId, setUserProperties } from '@angular/fire/analytics';
@@ -54,7 +54,7 @@ import 'prismjs/components/prism-json';
54
54
  * Current version of valtech-components.
55
55
  * This is automatically updated during the publish process.
56
56
  */
57
- const VERSION = '2.0.987';
57
+ const VERSION = '2.0.989';
58
58
 
59
59
  // Control de estado de refresco (singleton a nivel de módulo)
60
60
  let isRefreshing = false;
@@ -41737,7 +41737,7 @@ const PROFILE_VIEW_I18N = {
41737
41737
  const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
41738
41738
  const HANDLE_MIN = 3;
41739
41739
  const HANDLE_MAX = 20;
41740
- const DEFAULT_NAMESPACE$7 = 'Settings.Profile';
41740
+ const DEFAULT_NAMESPACE$b = 'Settings.Profile';
41741
41741
  /**
41742
41742
  * `val-profile-view` — vista Perfil full-feature autocontenida (organism).
41743
41743
  *
@@ -41787,7 +41787,7 @@ class ProfileViewComponent {
41787
41787
  showPhone: merged.showPhone ?? true,
41788
41788
  showShareCta: merged.showShareCta ?? true,
41789
41789
  profileBaseUrl: merged.profileBaseUrl ?? '',
41790
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$7,
41790
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$b,
41791
41791
  onSaved: merged.onSaved,
41792
41792
  onAvatarUploaded: merged.onAvatarUploaded,
41793
41793
  };
@@ -42703,7 +42703,7 @@ const PREFERENCES_VIEW_I18N = {
42703
42703
  },
42704
42704
  };
42705
42705
 
42706
- const DEFAULT_NAMESPACE$6 = 'Settings.General';
42706
+ const DEFAULT_NAMESPACE$a = 'Settings.General';
42707
42707
  const DEFAULT_LANGUAGES = ['es', 'en'];
42708
42708
  /**
42709
42709
  * `val-preferences-view` — vista de preferencias de UI cross-app (tema +
@@ -42740,7 +42740,7 @@ class PreferencesViewComponent {
42740
42740
  showLanguage: merged.showLanguage ?? true,
42741
42741
  showFontSize: merged.showFontSize ?? true,
42742
42742
  supportedLanguages: merged.supportedLanguages ?? DEFAULT_LANGUAGES,
42743
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$6,
42743
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$a,
42744
42744
  onThemeChanged: merged.onThemeChanged,
42745
42745
  onLanguageChanged: merged.onLanguageChanged,
42746
42746
  onFontSizeChanged: merged.onFontSizeChanged,
@@ -44552,7 +44552,7 @@ const SECURITY_VIEW_I18N = {
44552
44552
  };
44553
44553
 
44554
44554
  addIcons({ laptopOutline, phonePortraitOutline });
44555
- const DEFAULT_NAMESPACE$5 = 'Settings.Security';
44555
+ const DEFAULT_NAMESPACE$9 = 'Settings.Security';
44556
44556
  /**
44557
44557
  * `val-security-view` — vista Seguridad full-feature autocontenida (organism).
44558
44558
  *
@@ -44601,7 +44601,7 @@ class SecurityViewComponent {
44601
44601
  showChangeEmail: merged.showChangeEmail ?? true,
44602
44602
  showMfa: merged.showMfa ?? true,
44603
44603
  showPolicies: merged.showPolicies ?? true,
44604
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$5,
44604
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$9,
44605
44605
  handleDeepLinks: merged.handleDeepLinks ?? true,
44606
44606
  homeRoute: merged.homeRoute ?? '/app/home',
44607
44607
  onMfaCompleted: merged.onMfaCompleted,
@@ -45507,7 +45507,7 @@ const CREATE_ORG_MODAL_I18N = {
45507
45507
  },
45508
45508
  };
45509
45509
 
45510
- const DEFAULT_NAMESPACE$4 = 'CreateOrgModal';
45510
+ const DEFAULT_NAMESPACE$8 = 'CreateOrgModal';
45511
45511
  /**
45512
45512
  * `val-create-org-modal` — modal de creación de organización (organism
45513
45513
  * compartido). Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por
@@ -45533,10 +45533,10 @@ class CreateOrgModalComponent {
45533
45533
  this.toast = inject(ToastService);
45534
45534
  this.errors = inject(ValtechErrorService);
45535
45535
  /** Namespace i18n con que la vista resuelve sus textos. */
45536
- this.i18nNamespace = DEFAULT_NAMESPACE$4;
45536
+ this.i18nNamespace = DEFAULT_NAMESPACE$8;
45537
45537
  this._busy = signal(false);
45538
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$4)) {
45539
- this.i18n.registerContent(DEFAULT_NAMESPACE$4, CREATE_ORG_MODAL_I18N);
45538
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$8)) {
45539
+ this.i18n.registerContent(DEFAULT_NAMESPACE$8, CREATE_ORG_MODAL_I18N);
45540
45540
  }
45541
45541
  this.formMeta = signal(this.buildFormMeta());
45542
45542
  effect(() => {
@@ -45772,7 +45772,7 @@ const DELETE_ACCOUNT_MODAL_I18N = {
45772
45772
  },
45773
45773
  };
45774
45774
 
45775
- const DEFAULT_NAMESPACE$3 = 'Settings.DeleteAccount';
45775
+ const DEFAULT_NAMESPACE$7 = 'Settings.DeleteAccount';
45776
45776
  /**
45777
45777
  * `val-delete-account-modal` — modal de eliminación de cuenta (organism,
45778
45778
  * cuenta-específico). Promovido desde `showcase` bajo el proceso de ADR-021.
@@ -45798,7 +45798,7 @@ class DeleteAccountModalComponent {
45798
45798
  this.i18n = inject(I18nService);
45799
45799
  this.toast = inject(ToastService);
45800
45800
  /** Namespace i18n con que la vista resuelve sus textos. */
45801
- this.i18nNamespace = DEFAULT_NAMESPACE$3;
45801
+ this.i18nNamespace = DEFAULT_NAMESPACE$7;
45802
45802
  this.hasPassword = signal(null);
45803
45803
  this.codeSent = signal(false);
45804
45804
  this.formState = signal(ComponentStates.ENABLED);
@@ -45865,8 +45865,8 @@ class DeleteAccountModalComponent {
45865
45865
  ],
45866
45866
  actions: this.submitBtn('delete-code-submit', 'confirmCta'),
45867
45867
  }));
45868
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$3)) {
45869
- this.i18n.registerContent(DEFAULT_NAMESPACE$3, DELETE_ACCOUNT_MODAL_I18N);
45868
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
45869
+ this.i18n.registerContent(DEFAULT_NAMESPACE$7, DELETE_ACCOUNT_MODAL_I18N);
45870
45870
  }
45871
45871
  }
45872
45872
  ngOnInit() {
@@ -46067,7 +46067,7 @@ const ORG_INFO_SHEET_I18N = {
46067
46067
  },
46068
46068
  };
46069
46069
 
46070
- const DEFAULT_NAMESPACE$2 = 'OrgInfoSheet';
46070
+ const DEFAULT_NAMESPACE$6 = 'OrgInfoSheet';
46071
46071
  /**
46072
46072
  * `val-org-info-sheet` — sheet informativo "¿Qué son las organizaciones?".
46073
46073
  * Extraído del `OrgInfoSheetComponent` inline de `AccountPage` bajo el proceso de
@@ -46112,15 +46112,15 @@ class OrgInfoSheetComponent {
46112
46112
  text: this.closeProps.text || this.resolvedCloseLabel(),
46113
46113
  }));
46114
46114
  addIcons({ peopleOutline, swapHorizontalOutline, mailOutline });
46115
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$2)) {
46116
- this.i18n.registerContent(DEFAULT_NAMESPACE$2, ORG_INFO_SHEET_I18N);
46115
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$6)) {
46116
+ this.i18n.registerContent(DEFAULT_NAMESPACE$6, ORG_INFO_SHEET_I18N);
46117
46117
  }
46118
46118
  }
46119
46119
  dismiss() {
46120
46120
  this._modalRef?.dismiss(null, 'cancel');
46121
46121
  }
46122
46122
  tt(key) {
46123
- return this.i18n.t(key, DEFAULT_NAMESPACE$2);
46123
+ return this.i18n.t(key, DEFAULT_NAMESPACE$6);
46124
46124
  }
46125
46125
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgInfoSheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
46126
46126
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: OrgInfoSheetComponent, isStandalone: true, selector: "val-org-info-sheet", inputs: { title: "title", subtitle: "subtitle", body: "body", closeLabel: "closeLabel", closeProps: "closeProps", _modalRef: "_modalRef" }, ngImport: i0, template: `
@@ -46287,7 +46287,7 @@ const SWITCH_ORG_MODAL_I18N = {
46287
46287
  },
46288
46288
  };
46289
46289
 
46290
- const DEFAULT_NAMESPACE$1 = 'Settings.SwitchOrg';
46290
+ const DEFAULT_NAMESPACE$5 = 'Settings.SwitchOrg';
46291
46291
  addIcons({ businessOutline, checkmarkCircleOutline });
46292
46292
  /**
46293
46293
  * `val-switch-org-modal` — modal de cambio de organización activa (organism
@@ -46313,7 +46313,7 @@ class SwitchOrgModalComponent {
46313
46313
  this.orgSwitch = inject(OrgSwitchService);
46314
46314
  this.errors = inject(ValtechErrorService);
46315
46315
  /** Namespace i18n con que la vista resuelve sus textos. */
46316
- this.i18nNamespace = DEFAULT_NAMESPACE$1;
46316
+ this.i18nNamespace = DEFAULT_NAMESPACE$5;
46317
46317
  this.orgs = signal([]);
46318
46318
  this.loading = signal(false);
46319
46319
  this.query = signal('');
@@ -46329,8 +46329,8 @@ class SwitchOrgModalComponent {
46329
46329
  return list;
46330
46330
  return list.filter(o => o.name.toLowerCase().includes(q));
46331
46331
  });
46332
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$1)) {
46333
- this.i18n.registerContent(DEFAULT_NAMESPACE$1, SWITCH_ORG_MODAL_I18N);
46332
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
46333
+ this.i18n.registerContent(DEFAULT_NAMESPACE$5, SWITCH_ORG_MODAL_I18N);
46334
46334
  }
46335
46335
  this.loadOrgs();
46336
46336
  }
@@ -46648,7 +46648,7 @@ const ACCOUNT_VIEW_I18N = {
46648
46648
  };
46649
46649
 
46650
46650
  addIcons({ peopleOutline, businessOutline, chevronForwardOutline, swapHorizontalOutline, mailOutline });
46651
- const DEFAULT_NAMESPACE = 'Settings.Account';
46651
+ const DEFAULT_NAMESPACE$4 = 'Settings.Account';
46652
46652
  const DEFAULT_MANAGE_ORG_ROUTE = '/app/settings/organization';
46653
46653
  /**
46654
46654
  * `val-account-view` — vista Cuenta full-feature autocontenida (organism).
@@ -46689,7 +46689,7 @@ class AccountViewComponent {
46689
46689
  showNewOrgCta: merged.showNewOrgCta ?? true,
46690
46690
  showLogout: merged.showLogout ?? true,
46691
46691
  showDeleteAccount: merged.showDeleteAccount ?? true,
46692
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
46692
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$4,
46693
46693
  manageOrgRoute: merged.manageOrgRoute ?? DEFAULT_MANAGE_ORG_ROUTE,
46694
46694
  onOrgSwitched: merged.onOrgSwitched,
46695
46695
  onOrgCreated: merged.onOrgCreated,
@@ -47376,6 +47376,1517 @@ function provideValtechAccountRoutes(opts) {
47376
47376
  ];
47377
47377
  }
47378
47378
 
47379
+ /**
47380
+ * Defaults i18n (es/en) embebidos en `val-invite-member-modal`. Auto-registrados
47381
+ * en el constructor del componente si el consumer no proveyó el namespace
47382
+ * (`InviteModal` por default). SOLO incluye las keys que el modal usa.
47383
+ */
47384
+ const INVITE_MEMBER_MODAL_I18N = {
47385
+ es: {
47386
+ title: 'Invitar miembro',
47387
+ subtitle: 'Busca por handle o escribe un correo para invitar.',
47388
+ searchPlaceholder: 'Handle (@usuario) o correo...',
47389
+ searchingLabel: 'Buscando...',
47390
+ noResults: 'No se encontraron usuarios.',
47391
+ inviteByEmail: 'Invitar por correo a:',
47392
+ sendInvite: 'Enviar invitación',
47393
+ sending: 'Enviando...',
47394
+ success: 'Invitación enviada.',
47395
+ successPlural: 'Invitaciones enviadas.',
47396
+ close: 'Cerrar',
47397
+ errorInvalid: 'Ingresa un handle o correo válido.',
47398
+ sendSelected: 'Invitar seleccionados',
47399
+ selectedLabel: 'Seleccionados',
47400
+ roleLabel: 'Rol',
47401
+ roleViewer: 'Visor',
47402
+ roleViewerDesc: 'Solo lectura',
47403
+ roleEditor: 'Editor',
47404
+ roleEditorDesc: 'Puede editar contenido',
47405
+ roleAdmin: 'Administrador',
47406
+ roleAdminDesc: 'Gestión completa',
47407
+ },
47408
+ en: {
47409
+ title: 'Invite member',
47410
+ subtitle: 'Search by handle or enter an email to invite.',
47411
+ searchPlaceholder: 'Handle (@user) or email...',
47412
+ searchingLabel: 'Searching...',
47413
+ noResults: 'No users found.',
47414
+ inviteByEmail: 'Invite by email:',
47415
+ sendInvite: 'Send invitation',
47416
+ sending: 'Sending...',
47417
+ success: 'Invitation sent.',
47418
+ successPlural: 'Invitations sent.',
47419
+ close: 'Close',
47420
+ errorInvalid: 'Enter a valid handle or email.',
47421
+ sendSelected: 'Invite selected',
47422
+ selectedLabel: 'Selected',
47423
+ roleLabel: 'Role',
47424
+ roleViewer: 'Viewer',
47425
+ roleViewerDesc: 'Read only',
47426
+ roleEditor: 'Editor',
47427
+ roleEditorDesc: 'Can edit content',
47428
+ roleAdmin: 'Admin',
47429
+ roleAdminDesc: 'Full management',
47430
+ },
47431
+ };
47432
+
47433
+ const DEFAULT_NAMESPACE$3 = 'InviteModal';
47434
+ addIcons({ personOutline, mailOutline, closeOutline, checkmarkCircleOutline });
47435
+ /**
47436
+ * `val-invite-member-modal` — invitación de miembros a una organización.
47437
+ * Promovido desde `showcase` bajo el proceso de ADR-021.
47438
+ *
47439
+ * Busca usuarios por handle en Firestore `/profiles` (multi-select) o invita por
47440
+ * email cuando el query es un correo válido. Asigna el rol seleccionado y envía la
47441
+ * invitación vía `OrgService.inviteUser`. Excluye al usuario actual y a los
47442
+ * `existingMemberIds` de los resultados.
47443
+ *
47444
+ * Abierto vía `ModalService` — recibe `_modalRef` por `componentProps` + callback
47445
+ * `onSuccess`. Header canónico (Regla #5): sin `ion-title`, botón Cerrar texto en
47446
+ * `slot=end`, título/subtítulo en el body con `val-display`/`val-title`.
47447
+ *
47448
+ * Auto-registra sus defaults i18n (es/en) si el consumer no proveyó el namespace
47449
+ * `InviteModal`.
47450
+ */
47451
+ class InviteMemberModalComponent {
47452
+ constructor() {
47453
+ this.orgService = inject(OrgService);
47454
+ this.auth = inject(AuthService);
47455
+ this.toast = inject(ToastService);
47456
+ this.errors = inject(ValtechErrorService);
47457
+ this.firestore = inject(Firestore);
47458
+ this.i18n = inject(I18nService);
47459
+ /** Organización a la que se invita. */
47460
+ this.orgId = '';
47461
+ /** Ids de miembros ya existentes (excluidos de los resultados). */
47462
+ this.existingMemberIds = [];
47463
+ /** Namespace i18n con que la vista resuelve sus textos. */
47464
+ this.i18nNamespace = DEFAULT_NAMESPACE$3;
47465
+ this.query = signal('');
47466
+ this.searching = signal(false);
47467
+ this.searchResults = signal([]);
47468
+ this.selectedUsers = signal([]);
47469
+ this.sending = signal(false);
47470
+ this.selectedRole = signal('viewer');
47471
+ this.availableRoles = signal([
47472
+ { id: 'viewer', name: 'viewer' },
47473
+ { id: 'editor', name: 'editor' },
47474
+ { id: 'admin', name: 'admin' },
47475
+ ]);
47476
+ this.roleControl = new FormControl('viewer');
47477
+ this.roleSelectProps = computed(() => ({
47478
+ control: this.roleControl,
47479
+ label: '',
47480
+ placeholder: this.t('roleLabel'),
47481
+ type: InputType.SELECT,
47482
+ state: ComponentStates.ENABLED,
47483
+ options: this.availableRoles().map(r => ({
47484
+ id: r.id,
47485
+ name: this.roleDisplayName(r),
47486
+ order: 0,
47487
+ })),
47488
+ }));
47489
+ this.isEmail = computed(() => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.query().trim()));
47490
+ this.sendButtonProps = computed(() => ({
47491
+ text: this.t('sendInvite'),
47492
+ token: 'invite-submit-email',
47493
+ color: 'primary',
47494
+ fill: 'solid',
47495
+ expand: 'block',
47496
+ size: 'default',
47497
+ type: 'button',
47498
+ state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
47499
+ }));
47500
+ this.sendSelectedProps = computed(() => ({
47501
+ text: `${this.t('sendSelected')} (${this.selectedUsers().length})`,
47502
+ token: 'invite-submit',
47503
+ color: 'primary',
47504
+ fill: 'solid',
47505
+ expand: 'block',
47506
+ size: 'default',
47507
+ type: 'button',
47508
+ state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
47509
+ }));
47510
+ this.searchSubject = new Subject();
47511
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$3)) {
47512
+ this.i18n.registerContent(DEFAULT_NAMESPACE$3, INVITE_MEMBER_MODAL_I18N);
47513
+ }
47514
+ this.loadRoles();
47515
+ this.roleSub = this.roleControl.valueChanges.subscribe(v => {
47516
+ if (v)
47517
+ this.selectedRole.set(v);
47518
+ });
47519
+ // val-searchbar already debounces -- pipeline only needs distinctUntilChanged + switchMap
47520
+ this.searchSubject
47521
+ .pipe(distinctUntilChanged$1(), switchMap(q => {
47522
+ if (!q || q.length < 2 || this.isEmail()) {
47523
+ this.searching.set(false);
47524
+ this.searchResults.set([]);
47525
+ return [];
47526
+ }
47527
+ const cleanQ = (q.startsWith('@') ? q.slice(1) : q).toLowerCase();
47528
+ const q$ = query$1(collection(this.firestore, 'profiles'), orderBy('handle'), startAt(cleanQ), endAt(cleanQ + ''), limit(8));
47529
+ return from(getDocs(q$)).pipe(map$1(snap => ({
47530
+ users: snap.docs.map(d => ({
47531
+ userId: d.id,
47532
+ handle: d.data()['handle'],
47533
+ name: d.data()['name'],
47534
+ })),
47535
+ })));
47536
+ }))
47537
+ .subscribe({
47538
+ next: res => {
47539
+ if (res && 'users' in res) {
47540
+ const currentUserId = this.auth.user()?.userId;
47541
+ const excluded = new Set([
47542
+ ...(currentUserId ? [currentUserId] : []),
47543
+ ...this.existingMemberIds,
47544
+ ]);
47545
+ this.searchResults.set(res.users.filter(u => !excluded.has(u.userId)));
47546
+ }
47547
+ this.searching.set(false);
47548
+ },
47549
+ error: () => {
47550
+ this.searchResults.set([]);
47551
+ this.searching.set(false);
47552
+ },
47553
+ });
47554
+ }
47555
+ onQueryChange(value) {
47556
+ this.query.set(value ?? '');
47557
+ if (!this.isEmail() && (value ?? '').length >= 2) {
47558
+ this.searching.set(true);
47559
+ }
47560
+ else {
47561
+ this.searching.set(false);
47562
+ this.searchResults.set([]);
47563
+ }
47564
+ this.searchSubject.next(value ?? '');
47565
+ }
47566
+ loadRoles() {
47567
+ if (!this.orgId)
47568
+ return;
47569
+ this.orgService.listOrgRoles(this.orgId).subscribe({
47570
+ next: roles => {
47571
+ if (roles.length)
47572
+ this.availableRoles.set(roles);
47573
+ },
47574
+ });
47575
+ }
47576
+ roleDisplayName(role) {
47577
+ const keyMap = {
47578
+ viewer: 'roleViewer',
47579
+ editor: 'roleEditor',
47580
+ admin: 'roleAdmin',
47581
+ };
47582
+ const key = keyMap[role.name] ?? role.name;
47583
+ const label = this.t(key);
47584
+ return label && !label.startsWith('[') ? label : role.name;
47585
+ }
47586
+ async onInviteByEmail() {
47587
+ const email = this.query().trim();
47588
+ if (!email || this.sending())
47589
+ return;
47590
+ this.sending.set(true);
47591
+ try {
47592
+ await firstValueFrom(this.orgService.inviteUser(this.orgId, {
47593
+ email,
47594
+ roleId: this.selectedRole(),
47595
+ }));
47596
+ this.toast.show({
47597
+ message: this.t('success'),
47598
+ color: 'dark',
47599
+ duration: 3500,
47600
+ });
47601
+ this.onSuccess?.();
47602
+ this.dismiss();
47603
+ }
47604
+ catch (err) {
47605
+ this.errors.handle(err, {
47606
+ context: 'invite.byEmail',
47607
+ fallbackKey: 'sendInvite',
47608
+ i18nNamespace: this.i18nNamespace,
47609
+ });
47610
+ }
47611
+ finally {
47612
+ this.sending.set(false);
47613
+ }
47614
+ }
47615
+ isSelected(userId) {
47616
+ return this.selectedUsers().some(u => u.userId === userId);
47617
+ }
47618
+ toggleUser(user) {
47619
+ if (this.isSelected(user.userId)) {
47620
+ this.selectedUsers.update(list => list.filter(u => u.userId !== user.userId));
47621
+ }
47622
+ else {
47623
+ this.selectedUsers.update(list => [...list, user]);
47624
+ }
47625
+ }
47626
+ async onSendSelected() {
47627
+ const users = this.selectedUsers();
47628
+ if (!users.length || this.sending())
47629
+ return;
47630
+ this.sending.set(true);
47631
+ try {
47632
+ await Promise.all(users.map(u => firstValueFrom(this.orgService.inviteUser(this.orgId, {
47633
+ userId: u.userId,
47634
+ roleId: this.selectedRole(),
47635
+ }))));
47636
+ this.toast.show({
47637
+ message: users.length === 1 ? this.t('success') : this.t('successPlural'),
47638
+ color: 'dark',
47639
+ duration: 3500,
47640
+ });
47641
+ this.selectedUsers.set([]);
47642
+ this.onSuccess?.();
47643
+ this.dismiss();
47644
+ }
47645
+ catch (err) {
47646
+ this.errors.handle(err, {
47647
+ context: 'invite.byUserId',
47648
+ fallbackKey: 'sendInvite',
47649
+ i18nNamespace: this.i18nNamespace,
47650
+ });
47651
+ }
47652
+ finally {
47653
+ this.sending.set(false);
47654
+ }
47655
+ }
47656
+ ngOnDestroy() {
47657
+ this.roleSub?.unsubscribe();
47658
+ }
47659
+ dismiss() {
47660
+ this._modalRef?.dismiss(null, 'cancel');
47661
+ }
47662
+ t(key) {
47663
+ return this.i18n.t(key, this.i18nNamespace);
47664
+ }
47665
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InviteMemberModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
47666
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: InviteMemberModalComponent, isStandalone: true, selector: "val-invite-member-modal", inputs: { _modalRef: "_modalRef", orgId: "orgId", existingMemberIds: "existingMemberIds", onSuccess: "onSuccess", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
47667
+ <ion-header>
47668
+ <ion-toolbar>
47669
+ <ion-buttons slot="end">
47670
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
47671
+ <strong>{{ t('close') }}</strong>
47672
+ </ion-button>
47673
+ </ion-buttons>
47674
+ </ion-toolbar>
47675
+ </ion-header>
47676
+
47677
+ <ion-content class="ion-padding">
47678
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
47679
+ <val-title
47680
+ [props]="{
47681
+ content: t('subtitle'),
47682
+ size: 'large',
47683
+ color: '',
47684
+ bold: false,
47685
+ }"
47686
+ />
47687
+
47688
+ <div class="role-select-wrap">
47689
+ <val-title
47690
+ [props]="{
47691
+ content: t('roleLabel'),
47692
+ size: 'small',
47693
+ color: 'dark',
47694
+ bold: false,
47695
+ }"
47696
+ />
47697
+ <val-select-input data-testid="invite-role-select" [props]="roleSelectProps()" />
47698
+ </div>
47699
+
47700
+ <div class="search-section">
47701
+ <val-searchbar
47702
+ data-testid="invite-search"
47703
+ [props]="{
47704
+ placeholder: t('searchPlaceholder'),
47705
+ debounce: 350,
47706
+ showCancelButton: 'never',
47707
+ }"
47708
+ (filterEvent)="onQueryChange($event)"
47709
+ />
47710
+
47711
+ @if (searching()) {
47712
+ <div class="search-state">
47713
+ <ion-spinner name="crescent" />
47714
+ <span>{{ t('searchingLabel') }}</span>
47715
+ </div>
47716
+ } @else if (isEmail()) {
47717
+ <div class="email-invite-card">
47718
+ <ion-icon name="mail-outline" class="email-icon" />
47719
+ <div class="email-invite-text">
47720
+ <span class="email-invite-label">{{ t('inviteByEmail') }}</span>
47721
+ <span class="email-invite-address">{{ query().trim() }}</span>
47722
+ </div>
47723
+ </div>
47724
+ <val-button [props]="sendButtonProps()" (click)="onInviteByEmail()" />
47725
+ } @else if (query().length >= 2 && searchResults().length === 0) {
47726
+ <val-empty-state
47727
+ [props]="{
47728
+ variant: 'empty',
47729
+ title: t('noResults'),
47730
+ icon: 'person-outline',
47731
+ }"
47732
+ />
47733
+ } @else if (searchResults().length > 0) {
47734
+ <div class="results-list">
47735
+ @for (user of searchResults(); track user.userId) {
47736
+ <div
47737
+ class="result-card"
47738
+ [attr.data-testid]="'invite-result-' + user.userId"
47739
+ [class.result-card--selected]="isSelected(user.userId)"
47740
+ (click)="toggleUser(user)"
47741
+ role="checkbox"
47742
+ [attr.aria-checked]="isSelected(user.userId)"
47743
+ >
47744
+ <div class="result-card__avatar">
47745
+ <ion-icon name="person-outline" />
47746
+ </div>
47747
+ <div class="result-card__body">
47748
+ <span class="result-card__name">{{ user.name || user.handle }}</span>
47749
+ @if (user.handle) {
47750
+ <span class="result-card__handle">&#64;{{ user.handle }}</span>
47751
+ }
47752
+ </div>
47753
+ <div class="result-card__check">
47754
+ @if (isSelected(user.userId)) {
47755
+ <ion-icon name="checkmark-circle-outline" class="check-on" />
47756
+ } @else {
47757
+ <div class="check-off"></div>
47758
+ }
47759
+ </div>
47760
+ </div>
47761
+ }
47762
+ </div>
47763
+ }
47764
+
47765
+ @if (selectedUsers().length > 0) {
47766
+ <div class="selected-section">
47767
+ <span class="selected-section__label">{{ t('selectedLabel') }} ({{ selectedUsers().length }})</span>
47768
+ <div class="selected-chips">
47769
+ @for (user of selectedUsers(); track user.userId) {
47770
+ <div class="selected-chip">
47771
+ <span>{{ user.handle ? '@' + user.handle : user.name || user.userId }}</span>
47772
+ <button class="selected-chip__remove" (click)="toggleUser(user)" aria-label="Quitar">&times;</button>
47773
+ </div>
47774
+ }
47775
+ </div>
47776
+ <val-button [props]="sendSelectedProps()" (click)="onSendSelected()" />
47777
+ </div>
47778
+ }
47779
+ </div>
47780
+ </ion-content>
47781
+ `, isInline: true, styles: ["ion-content{--padding-bottom: 24px}val-display{margin-bottom:4px;display:block}val-title{margin-bottom:8px;display:block}.role-select-wrap{margin-bottom:12px}.role-select{width:100%}val-searchbar{display:block;margin-bottom:4px}.search-section{display:flex;flex-direction:column;gap:14px}.search-state{display:flex;align-items:center;gap:10px;padding:12px 0;color:var(--ion-color-medium);font-size:.88rem}.email-invite-card{display:flex;align-items:center;gap:14px;padding:14px 16px;border-radius:12px;border:1.5px solid var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 8%,transparent)}.email-icon{font-size:24px;color:var(--ion-color-primary);flex-shrink:0}.email-invite-text{display:flex;flex-direction:column;gap:2px}.email-invite-label{font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.email-invite-address{font-weight:600;font-size:.95rem;color:var(--ion-color-dark);word-break:break-all}.results-list{display:flex;flex-direction:column;gap:8px}.result-card{display:flex;align-items:center;gap:14px;padding:12px 14px;border-radius:12px;background:var(--ion-color-light, #f4f5f8);cursor:pointer;transition:opacity .15s}.result-card:active{opacity:.7}:host-context(body.dark) .result-card,:host-context(html.ion-palette-dark) .result-card,:host-context([data-theme=\"dark\"]) .result-card{background:#ffffff0d}.result-card__avatar{width:40px;height:40px;border-radius:50%;background:color-mix(in srgb,var(--ion-color-primary) 15%,transparent);display:flex;align-items:center;justify-content:center;font-size:20px;color:var(--ion-color-primary);flex-shrink:0}.result-card__body{display:flex;flex-direction:column;gap:2px}.result-card__name{font-weight:600;font-size:.9rem;color:var(--ion-color-dark)}.result-card__handle{font-size:.78rem;color:var(--ion-color-medium)}.result-card--selected{border:1.5px solid var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 8%,transparent)}.result-card__check{flex-shrink:0;margin-left:auto}.check-on{font-size:22px;color:var(--ion-color-primary)}.check-off{width:22px;height:22px;border-radius:50%;border:2px solid var(--ion-color-medium)}.selected-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08));padding-top:14px;display:flex;flex-direction:column;gap:10px}.selected-section__label{font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.selected-chips{display:flex;flex-wrap:wrap;gap:6px}.selected-chip{display:flex;align-items:center;gap:4px;padding:4px 10px 4px 12px;border-radius:20px;background:color-mix(in srgb,var(--ion-color-primary) 12%,transparent);color:var(--ion-color-primary);font-size:.82rem;font-weight:600}.selected-chip__remove{border:none;background:none;cursor:pointer;font-size:1rem;line-height:1;color:var(--ion-color-primary);padding:0 2px;opacity:.7}.selected-chip__remove:hover{opacity:1}\n"], dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "component", type: SearchbarComponent, selector: "val-searchbar", inputs: ["preset", "props"], outputs: ["filterEvent", "focusEvent", "blurEvent"] }, { kind: "component", type: SearchSelectorComponent, selector: "val-select-input", inputs: ["preset", "props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
47782
+ }
47783
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InviteMemberModalComponent, decorators: [{
47784
+ type: Component,
47785
+ args: [{ selector: 'val-invite-member-modal', standalone: true, imports: [
47786
+ IonHeader,
47787
+ IonToolbar,
47788
+ IonButtons,
47789
+ IonButton,
47790
+ IonContent,
47791
+ IonSpinner,
47792
+ IonIcon,
47793
+ ReactiveFormsModule,
47794
+ ButtonComponent,
47795
+ DisplayComponent,
47796
+ EmptyStateComponent,
47797
+ SearchbarComponent,
47798
+ SearchSelectorComponent,
47799
+ TextComponent,
47800
+ TitleComponent,
47801
+ ], template: `
47802
+ <ion-header>
47803
+ <ion-toolbar>
47804
+ <ion-buttons slot="end">
47805
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
47806
+ <strong>{{ t('close') }}</strong>
47807
+ </ion-button>
47808
+ </ion-buttons>
47809
+ </ion-toolbar>
47810
+ </ion-header>
47811
+
47812
+ <ion-content class="ion-padding">
47813
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
47814
+ <val-title
47815
+ [props]="{
47816
+ content: t('subtitle'),
47817
+ size: 'large',
47818
+ color: '',
47819
+ bold: false,
47820
+ }"
47821
+ />
47822
+
47823
+ <div class="role-select-wrap">
47824
+ <val-title
47825
+ [props]="{
47826
+ content: t('roleLabel'),
47827
+ size: 'small',
47828
+ color: 'dark',
47829
+ bold: false,
47830
+ }"
47831
+ />
47832
+ <val-select-input data-testid="invite-role-select" [props]="roleSelectProps()" />
47833
+ </div>
47834
+
47835
+ <div class="search-section">
47836
+ <val-searchbar
47837
+ data-testid="invite-search"
47838
+ [props]="{
47839
+ placeholder: t('searchPlaceholder'),
47840
+ debounce: 350,
47841
+ showCancelButton: 'never',
47842
+ }"
47843
+ (filterEvent)="onQueryChange($event)"
47844
+ />
47845
+
47846
+ @if (searching()) {
47847
+ <div class="search-state">
47848
+ <ion-spinner name="crescent" />
47849
+ <span>{{ t('searchingLabel') }}</span>
47850
+ </div>
47851
+ } @else if (isEmail()) {
47852
+ <div class="email-invite-card">
47853
+ <ion-icon name="mail-outline" class="email-icon" />
47854
+ <div class="email-invite-text">
47855
+ <span class="email-invite-label">{{ t('inviteByEmail') }}</span>
47856
+ <span class="email-invite-address">{{ query().trim() }}</span>
47857
+ </div>
47858
+ </div>
47859
+ <val-button [props]="sendButtonProps()" (click)="onInviteByEmail()" />
47860
+ } @else if (query().length >= 2 && searchResults().length === 0) {
47861
+ <val-empty-state
47862
+ [props]="{
47863
+ variant: 'empty',
47864
+ title: t('noResults'),
47865
+ icon: 'person-outline',
47866
+ }"
47867
+ />
47868
+ } @else if (searchResults().length > 0) {
47869
+ <div class="results-list">
47870
+ @for (user of searchResults(); track user.userId) {
47871
+ <div
47872
+ class="result-card"
47873
+ [attr.data-testid]="'invite-result-' + user.userId"
47874
+ [class.result-card--selected]="isSelected(user.userId)"
47875
+ (click)="toggleUser(user)"
47876
+ role="checkbox"
47877
+ [attr.aria-checked]="isSelected(user.userId)"
47878
+ >
47879
+ <div class="result-card__avatar">
47880
+ <ion-icon name="person-outline" />
47881
+ </div>
47882
+ <div class="result-card__body">
47883
+ <span class="result-card__name">{{ user.name || user.handle }}</span>
47884
+ @if (user.handle) {
47885
+ <span class="result-card__handle">&#64;{{ user.handle }}</span>
47886
+ }
47887
+ </div>
47888
+ <div class="result-card__check">
47889
+ @if (isSelected(user.userId)) {
47890
+ <ion-icon name="checkmark-circle-outline" class="check-on" />
47891
+ } @else {
47892
+ <div class="check-off"></div>
47893
+ }
47894
+ </div>
47895
+ </div>
47896
+ }
47897
+ </div>
47898
+ }
47899
+
47900
+ @if (selectedUsers().length > 0) {
47901
+ <div class="selected-section">
47902
+ <span class="selected-section__label">{{ t('selectedLabel') }} ({{ selectedUsers().length }})</span>
47903
+ <div class="selected-chips">
47904
+ @for (user of selectedUsers(); track user.userId) {
47905
+ <div class="selected-chip">
47906
+ <span>{{ user.handle ? '@' + user.handle : user.name || user.userId }}</span>
47907
+ <button class="selected-chip__remove" (click)="toggleUser(user)" aria-label="Quitar">&times;</button>
47908
+ </div>
47909
+ }
47910
+ </div>
47911
+ <val-button [props]="sendSelectedProps()" (click)="onSendSelected()" />
47912
+ </div>
47913
+ }
47914
+ </div>
47915
+ </ion-content>
47916
+ `, styles: ["ion-content{--padding-bottom: 24px}val-display{margin-bottom:4px;display:block}val-title{margin-bottom:8px;display:block}.role-select-wrap{margin-bottom:12px}.role-select{width:100%}val-searchbar{display:block;margin-bottom:4px}.search-section{display:flex;flex-direction:column;gap:14px}.search-state{display:flex;align-items:center;gap:10px;padding:12px 0;color:var(--ion-color-medium);font-size:.88rem}.email-invite-card{display:flex;align-items:center;gap:14px;padding:14px 16px;border-radius:12px;border:1.5px solid var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 8%,transparent)}.email-icon{font-size:24px;color:var(--ion-color-primary);flex-shrink:0}.email-invite-text{display:flex;flex-direction:column;gap:2px}.email-invite-label{font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.email-invite-address{font-weight:600;font-size:.95rem;color:var(--ion-color-dark);word-break:break-all}.results-list{display:flex;flex-direction:column;gap:8px}.result-card{display:flex;align-items:center;gap:14px;padding:12px 14px;border-radius:12px;background:var(--ion-color-light, #f4f5f8);cursor:pointer;transition:opacity .15s}.result-card:active{opacity:.7}:host-context(body.dark) .result-card,:host-context(html.ion-palette-dark) .result-card,:host-context([data-theme=\"dark\"]) .result-card{background:#ffffff0d}.result-card__avatar{width:40px;height:40px;border-radius:50%;background:color-mix(in srgb,var(--ion-color-primary) 15%,transparent);display:flex;align-items:center;justify-content:center;font-size:20px;color:var(--ion-color-primary);flex-shrink:0}.result-card__body{display:flex;flex-direction:column;gap:2px}.result-card__name{font-weight:600;font-size:.9rem;color:var(--ion-color-dark)}.result-card__handle{font-size:.78rem;color:var(--ion-color-medium)}.result-card--selected{border:1.5px solid var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 8%,transparent)}.result-card__check{flex-shrink:0;margin-left:auto}.check-on{font-size:22px;color:var(--ion-color-primary)}.check-off{width:22px;height:22px;border-radius:50%;border:2px solid var(--ion-color-medium)}.selected-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08));padding-top:14px;display:flex;flex-direction:column;gap:10px}.selected-section__label{font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.selected-chips{display:flex;flex-wrap:wrap;gap:6px}.selected-chip{display:flex;align-items:center;gap:4px;padding:4px 10px 4px 12px;border-radius:20px;background:color-mix(in srgb,var(--ion-color-primary) 12%,transparent);color:var(--ion-color-primary);font-size:.82rem;font-weight:600}.selected-chip__remove{border:none;background:none;cursor:pointer;font-size:1rem;line-height:1;color:var(--ion-color-primary);padding:0 2px;opacity:.7}.selected-chip__remove:hover{opacity:1}\n"] }]
47917
+ }], ctorParameters: () => [], propDecorators: { _modalRef: [{
47918
+ type: Input
47919
+ }], orgId: [{
47920
+ type: Input
47921
+ }], existingMemberIds: [{
47922
+ type: Input
47923
+ }], onSuccess: [{
47924
+ type: Input
47925
+ }], i18nNamespace: [{
47926
+ type: Input
47927
+ }] } });
47928
+
47929
+ /**
47930
+ * Defaults i18n (es/en) embebidos en `val-member-detail-modal`. Auto-registrados
47931
+ * en el constructor del componente si el consumer no proveyó el namespace
47932
+ * (`Settings.MemberDetail` por default). SOLO incluye las keys que el modal usa.
47933
+ */
47934
+ const MEMBER_DETAIL_MODAL_I18N = {
47935
+ es: {
47936
+ close: 'Cerrar',
47937
+ subtitleSelf: 'Tú',
47938
+ roleLabel: 'Rol',
47939
+ roleHint: 'Cambia el rol de este miembro en la organización.',
47940
+ roleReadonly: 'Rol en la organización',
47941
+ roleViewer: 'Visor',
47942
+ roleEditor: 'Editor',
47943
+ roleAdmin: 'Admin',
47944
+ roleSuperAdmin: 'Super Admin',
47945
+ removeCta: 'Quitar de la organización',
47946
+ removeConfirmTitle: 'Quitar miembro',
47947
+ removeConfirmMessage: '¿Quitar a este miembro de la organización?',
47948
+ removeConfirmOk: 'Quitar',
47949
+ cancelCta: 'Cancelar',
47950
+ changeRoleSuccess: 'Rol actualizado.',
47951
+ changeRoleError: 'No se pudo cambiar el rol.',
47952
+ removeSuccess: 'Miembro eliminado.',
47953
+ removeError: 'No se pudo eliminar al miembro.',
47954
+ permissionsTitle: 'Permisos del rol',
47955
+ permAll: 'Todo',
47956
+ permUsers: 'Usuarios',
47957
+ permDocuments: 'Documentos',
47958
+ permRoles: 'Roles',
47959
+ permMedia: 'Media',
47960
+ permTemplates: 'Plantillas',
47961
+ permRead: 'Leer',
47962
+ permWrite: 'Escribir',
47963
+ permCreate: 'Crear',
47964
+ permDelete: 'Eliminar',
47965
+ permManage: 'Gestionar',
47966
+ },
47967
+ en: {
47968
+ close: 'Close',
47969
+ subtitleSelf: 'You',
47970
+ roleLabel: 'Role',
47971
+ roleHint: "Change this member's role in the organization.",
47972
+ roleReadonly: 'Role in the organization',
47973
+ roleViewer: 'Viewer',
47974
+ roleEditor: 'Editor',
47975
+ roleAdmin: 'Admin',
47976
+ roleSuperAdmin: 'Super Admin',
47977
+ removeCta: 'Remove from organization',
47978
+ removeConfirmTitle: 'Remove member',
47979
+ removeConfirmMessage: 'Remove this member from the organization?',
47980
+ removeConfirmOk: 'Remove',
47981
+ cancelCta: 'Cancel',
47982
+ changeRoleSuccess: 'Role updated.',
47983
+ changeRoleError: 'Could not change role.',
47984
+ removeSuccess: 'Member removed.',
47985
+ removeError: 'Could not remove member.',
47986
+ permissionsTitle: 'Role permissions',
47987
+ permAll: 'All',
47988
+ permUsers: 'Users',
47989
+ permDocuments: 'Documents',
47990
+ permRoles: 'Roles',
47991
+ permMedia: 'Media',
47992
+ permTemplates: 'Templates',
47993
+ permRead: 'Read',
47994
+ permWrite: 'Write',
47995
+ permCreate: 'Create',
47996
+ permDelete: 'Delete',
47997
+ permManage: 'Manage',
47998
+ },
47999
+ };
48000
+
48001
+ const DEFAULT_NAMESPACE$2 = 'Settings.MemberDetail';
48002
+ /**
48003
+ * `val-member-detail-modal` — detalle de miembro de organización: perfil, rol y
48004
+ * permisos. Promovido desde `showcase` bajo el proceso de ADR-021.
48005
+ *
48006
+ * Si `canManage` es true, permite cambiar el rol (vía `OrgService.changeMemberRole`)
48007
+ * y quitar al miembro (vía `OrgService.removeMember`, con confirmación destructiva).
48008
+ * Los permisos del rol actual se muestran cuando el consumer los provee en
48009
+ * `availableRoles`.
48010
+ *
48011
+ * Abierto vía `ModalService` — recibe `_modalRef` por `componentProps` + callback
48012
+ * `onChanged`. Header canónico (Regla #5): sin `ion-title`, botón Cerrar texto en
48013
+ * `slot=end`, identidad en el body con `val-user-avatar`/`val-display`/`val-title`.
48014
+ *
48015
+ * Auto-registra sus defaults i18n (es/en) si el consumer no proveyó el namespace
48016
+ * `Settings.MemberDetail`.
48017
+ */
48018
+ class MemberDetailModalComponent {
48019
+ constructor() {
48020
+ this.orgService = inject(OrgService);
48021
+ this.toast = inject(ToastService);
48022
+ this.errors = inject(ValtechErrorService);
48023
+ this.i18n = inject(I18nService);
48024
+ this.confirmDialog = inject(ConfirmationDialogService);
48025
+ /** Organización a la que pertenece el miembro. */
48026
+ this.orgId = '';
48027
+ /** Si true, habilita cambio de rol y eliminación. */
48028
+ this.canManage = false;
48029
+ /** Si true, el miembro mostrado es el propio usuario. */
48030
+ this.isSelf = false;
48031
+ /** Roles disponibles (con permisos opcionales) para el selector de rol. */
48032
+ this.availableRoles = [
48033
+ { id: 'viewer', name: 'viewer' },
48034
+ { id: 'editor', name: 'editor' },
48035
+ { id: 'admin', name: 'admin' },
48036
+ ];
48037
+ /** Namespace i18n con que la vista resuelve sus textos. */
48038
+ this.i18nNamespace = DEFAULT_NAMESPACE$2;
48039
+ this.working = signal(false);
48040
+ this.currentRole = signal('');
48041
+ this.roleControl = new FormControl('');
48042
+ this.displayName = computed(() => this.member?.name || this.member?.handle || this.member?.email || '—');
48043
+ this.subtitle = computed(() => {
48044
+ if (this.isSelf)
48045
+ return this.t('subtitleSelf');
48046
+ if (this.member?.handle)
48047
+ return '@' + this.member.handle;
48048
+ return this.member?.email ?? '';
48049
+ });
48050
+ this.currentRoleLabel = computed(() => this.roleLabel(this.currentRole()));
48051
+ this.permissionsForCurrentRole = computed(() => {
48052
+ const roleId = this.currentRole();
48053
+ const role = this.availableRoles.find(r => r.id === roleId || r.name === roleId);
48054
+ return role?.permissions ?? [];
48055
+ });
48056
+ this.roleSelectProps = computed(() => ({
48057
+ control: this.roleControl,
48058
+ label: '',
48059
+ placeholder: this.t('roleLabel'),
48060
+ type: InputType.SELECT,
48061
+ state: this.working() ? ComponentStates.DISABLED : ComponentStates.ENABLED,
48062
+ options: this.availableRoles.map(r => ({
48063
+ id: r.id,
48064
+ name: this.roleLabel(r.name),
48065
+ order: 0,
48066
+ })),
48067
+ }));
48068
+ this.removeButtonProps = computed(() => ({
48069
+ text: this.t('removeCta'),
48070
+ token: 'member-remove',
48071
+ color: 'danger',
48072
+ fill: 'outline',
48073
+ expand: 'block',
48074
+ size: 'default',
48075
+ type: 'button',
48076
+ state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
48077
+ }));
48078
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$2)) {
48079
+ this.i18n.registerContent(DEFAULT_NAMESPACE$2, MEMBER_DETAIL_MODAL_I18N);
48080
+ }
48081
+ }
48082
+ ngOnInit() {
48083
+ if (!this.i18n.hasNamespace(this.i18nNamespace)) {
48084
+ this.i18n.registerContent(this.i18nNamespace, MEMBER_DETAIL_MODAL_I18N);
48085
+ }
48086
+ const roleName = this.member?.roles?.[0] ?? '';
48087
+ // Resolve the role name to its UUID -- the select uses r.id (UUID) as value
48088
+ const matched = this.availableRoles.find(r => r.name === roleName);
48089
+ const roleId = matched?.id ?? roleName;
48090
+ this.currentRole.set(roleId);
48091
+ this.roleControl.setValue(roleId, { emitEvent: false });
48092
+ this.roleSub = this.roleControl.valueChanges.subscribe(v => {
48093
+ if (v && v !== this.currentRole())
48094
+ void this.onChangeRole(v);
48095
+ });
48096
+ }
48097
+ permissionLabel(perm) {
48098
+ if (perm === '*:*')
48099
+ return this.t('permAll');
48100
+ const [resource, action] = perm.split(':');
48101
+ const resKey = {
48102
+ users: 'permUsers',
48103
+ documents: 'permDocuments',
48104
+ roles: 'permRoles',
48105
+ media: 'permMedia',
48106
+ templates: 'permTemplates',
48107
+ '*': 'permAll',
48108
+ };
48109
+ const actKey = {
48110
+ read: 'permRead',
48111
+ write: 'permWrite',
48112
+ create: 'permCreate',
48113
+ delete: 'permDelete',
48114
+ manage: 'permManage',
48115
+ '*': 'permAll',
48116
+ };
48117
+ const res = this.t(resKey[resource] ?? resource);
48118
+ const act = this.t(actKey[action] ?? action);
48119
+ return `${res}: ${act}`;
48120
+ }
48121
+ async onChangeRole(newRoleId) {
48122
+ if (this.working())
48123
+ return;
48124
+ this.working.set(true);
48125
+ try {
48126
+ await firstValueFrom(this.orgService.changeMemberRole(this.orgId, this.member.userId, newRoleId));
48127
+ this.currentRole.set(newRoleId);
48128
+ this.member = { ...this.member, roles: [newRoleId] };
48129
+ this.toast.show({
48130
+ message: this.t('changeRoleSuccess'),
48131
+ color: 'dark',
48132
+ duration: 3000,
48133
+ });
48134
+ this.onChanged?.();
48135
+ }
48136
+ catch (err) {
48137
+ this.roleControl.setValue(this.currentRole(), { emitEvent: false });
48138
+ this.errors.handle(err, {
48139
+ context: 'memberDetail.changeRole',
48140
+ fallbackKey: 'changeRoleError',
48141
+ i18nNamespace: this.i18nNamespace,
48142
+ });
48143
+ }
48144
+ finally {
48145
+ this.working.set(false);
48146
+ }
48147
+ }
48148
+ async onRemove() {
48149
+ if (this.working())
48150
+ return;
48151
+ const result = await this.confirmDialog.confirmDestructive({
48152
+ title: this.t('removeConfirmTitle'),
48153
+ message: this.t('removeConfirmMessage'),
48154
+ confirmButton: { text: this.t('removeConfirmOk') },
48155
+ cancelButton: { text: this.t('cancelCta') },
48156
+ });
48157
+ if (!result.confirmed)
48158
+ return;
48159
+ this.working.set(true);
48160
+ try {
48161
+ await firstValueFrom(this.orgService.removeMember(this.orgId, this.member.userId));
48162
+ this.toast.show({
48163
+ message: this.t('removeSuccess'),
48164
+ color: 'dark',
48165
+ duration: 3000,
48166
+ });
48167
+ this.onChanged?.();
48168
+ this.dismiss();
48169
+ }
48170
+ catch (err) {
48171
+ this.errors.handle(err, {
48172
+ context: 'memberDetail.remove',
48173
+ fallbackKey: 'removeError',
48174
+ i18nNamespace: this.i18nNamespace,
48175
+ });
48176
+ }
48177
+ finally {
48178
+ this.working.set(false);
48179
+ }
48180
+ }
48181
+ roleLabel(roleIdOrName) {
48182
+ // Try to find by id first (post-change, UUID stored), then by name (initial load)
48183
+ const found = this.availableRoles.find(r => r.id === roleIdOrName || r.name === roleIdOrName);
48184
+ const name = found?.name ?? roleIdOrName;
48185
+ const keyMap = {
48186
+ viewer: 'roleViewer',
48187
+ editor: 'roleEditor',
48188
+ admin: 'roleAdmin',
48189
+ super_admin: 'roleSuperAdmin',
48190
+ };
48191
+ const key = keyMap[name] ?? name;
48192
+ const label = this.t(key);
48193
+ return label && !label.startsWith('[') ? label : name;
48194
+ }
48195
+ ngOnDestroy() {
48196
+ this.roleSub?.unsubscribe();
48197
+ }
48198
+ dismiss() {
48199
+ this._modalRef?.dismiss(null, 'cancel');
48200
+ }
48201
+ t(key) {
48202
+ return this.i18n.t(key, this.i18nNamespace);
48203
+ }
48204
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberDetailModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
48205
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MemberDetailModalComponent, isStandalone: true, selector: "val-member-detail-modal", inputs: { _modalRef: "_modalRef", member: "member", orgId: "orgId", canManage: "canManage", isSelf: "isSelf", availableRoles: "availableRoles", onChanged: "onChanged", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
48206
+ <ion-header>
48207
+ <ion-toolbar>
48208
+ <ion-buttons slot="end">
48209
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
48210
+ <strong>{{ t('close') }}</strong>
48211
+ </ion-button>
48212
+ </ion-buttons>
48213
+ </ion-toolbar>
48214
+ </ion-header>
48215
+
48216
+ <ion-content class="ion-padding">
48217
+ <div class="member-head">
48218
+ <val-user-avatar
48219
+ [props]="{
48220
+ name: member.name,
48221
+ avatarUrl: member.avatar,
48222
+ email: member.email,
48223
+ size: 'large',
48224
+ }"
48225
+ />
48226
+ <val-display [props]="{ content: displayName(), size: 'small', color: 'dark' }" />
48227
+ <val-title
48228
+ [props]="{
48229
+ content: subtitle(),
48230
+ size: 'large',
48231
+ color: '',
48232
+ bold: false,
48233
+ }"
48234
+ />
48235
+ </div>
48236
+
48237
+ <div class="role-section">
48238
+ @if (canManage) {
48239
+ <val-title
48240
+ [props]="{
48241
+ content: t('roleLabel'),
48242
+ size: 'small',
48243
+ color: 'dark',
48244
+ bold: false,
48245
+ }"
48246
+ />
48247
+ <val-select-input data-testid="member-role-select" [props]="roleSelectProps()" />
48248
+ <val-text
48249
+ [props]="{
48250
+ content: t('roleHint'),
48251
+ size: 'small',
48252
+ color: 'medium',
48253
+ bold: false,
48254
+ }"
48255
+ />
48256
+ } @else {
48257
+ <span class="role-readonly-label">{{ t('roleReadonly') }}</span>
48258
+ <span class="member-role-badge">{{ currentRoleLabel() }}</span>
48259
+ }
48260
+ </div>
48261
+
48262
+ @if (permissionsForCurrentRole().length > 0) {
48263
+ <div class="perms-section">
48264
+ <span class="perms-label">{{ t('permissionsTitle') }}</span>
48265
+ <div class="perms-list">
48266
+ @for (perm of permissionsForCurrentRole(); track perm) {
48267
+ <span class="perm-chip">{{ permissionLabel(perm) }}</span>
48268
+ }
48269
+ </div>
48270
+ </div>
48271
+ }
48272
+
48273
+ @if (canManage) {
48274
+ <div class="remove-section">
48275
+ <val-button [props]="removeButtonProps()" (click)="onRemove()" />
48276
+ </div>
48277
+ }
48278
+ </ion-content>
48279
+ `, isInline: true, styles: ["ion-content{--padding-bottom: 24px}.member-head{display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;margin-bottom:20px}val-display{display:block}val-title{display:block}.role-section{display:flex;flex-direction:column;gap:8px;margin-bottom:24px}.role-readonly-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.member-role-badge{font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:4px 12px;border-radius:20px;width:fit-content;background:color-mix(in srgb,var(--ion-color-primary) 12%,transparent);color:var(--ion-color-primary)}.perms-section{display:flex;flex-direction:column;gap:10px;margin-bottom:24px}.perms-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.perms-list{display:flex;flex-wrap:wrap;gap:6px}.perm-chip{font-size:.75rem;font-weight:500;padding:3px 10px;border-radius:20px;border:1px solid var(--val-border-color, rgba(0, 0, 0, .12));color:var(--ion-color-dark);background:transparent}.remove-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08));padding-top:20px}\n"], dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: SearchSelectorComponent, selector: "val-select-input", inputs: ["preset", "props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: UserAvatarComponent, selector: "val-user-avatar", inputs: ["props"], outputs: ["onClick"] }] }); }
48280
+ }
48281
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberDetailModalComponent, decorators: [{
48282
+ type: Component,
48283
+ args: [{ selector: 'val-member-detail-modal', standalone: true, imports: [
48284
+ IonHeader,
48285
+ IonToolbar,
48286
+ IonButtons,
48287
+ IonButton,
48288
+ IonContent,
48289
+ ReactiveFormsModule,
48290
+ ButtonComponent,
48291
+ DisplayComponent,
48292
+ SearchSelectorComponent,
48293
+ TextComponent,
48294
+ TitleComponent,
48295
+ UserAvatarComponent,
48296
+ ], template: `
48297
+ <ion-header>
48298
+ <ion-toolbar>
48299
+ <ion-buttons slot="end">
48300
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
48301
+ <strong>{{ t('close') }}</strong>
48302
+ </ion-button>
48303
+ </ion-buttons>
48304
+ </ion-toolbar>
48305
+ </ion-header>
48306
+
48307
+ <ion-content class="ion-padding">
48308
+ <div class="member-head">
48309
+ <val-user-avatar
48310
+ [props]="{
48311
+ name: member.name,
48312
+ avatarUrl: member.avatar,
48313
+ email: member.email,
48314
+ size: 'large',
48315
+ }"
48316
+ />
48317
+ <val-display [props]="{ content: displayName(), size: 'small', color: 'dark' }" />
48318
+ <val-title
48319
+ [props]="{
48320
+ content: subtitle(),
48321
+ size: 'large',
48322
+ color: '',
48323
+ bold: false,
48324
+ }"
48325
+ />
48326
+ </div>
48327
+
48328
+ <div class="role-section">
48329
+ @if (canManage) {
48330
+ <val-title
48331
+ [props]="{
48332
+ content: t('roleLabel'),
48333
+ size: 'small',
48334
+ color: 'dark',
48335
+ bold: false,
48336
+ }"
48337
+ />
48338
+ <val-select-input data-testid="member-role-select" [props]="roleSelectProps()" />
48339
+ <val-text
48340
+ [props]="{
48341
+ content: t('roleHint'),
48342
+ size: 'small',
48343
+ color: 'medium',
48344
+ bold: false,
48345
+ }"
48346
+ />
48347
+ } @else {
48348
+ <span class="role-readonly-label">{{ t('roleReadonly') }}</span>
48349
+ <span class="member-role-badge">{{ currentRoleLabel() }}</span>
48350
+ }
48351
+ </div>
48352
+
48353
+ @if (permissionsForCurrentRole().length > 0) {
48354
+ <div class="perms-section">
48355
+ <span class="perms-label">{{ t('permissionsTitle') }}</span>
48356
+ <div class="perms-list">
48357
+ @for (perm of permissionsForCurrentRole(); track perm) {
48358
+ <span class="perm-chip">{{ permissionLabel(perm) }}</span>
48359
+ }
48360
+ </div>
48361
+ </div>
48362
+ }
48363
+
48364
+ @if (canManage) {
48365
+ <div class="remove-section">
48366
+ <val-button [props]="removeButtonProps()" (click)="onRemove()" />
48367
+ </div>
48368
+ }
48369
+ </ion-content>
48370
+ `, styles: ["ion-content{--padding-bottom: 24px}.member-head{display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;margin-bottom:20px}val-display{display:block}val-title{display:block}.role-section{display:flex;flex-direction:column;gap:8px;margin-bottom:24px}.role-readonly-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.member-role-badge{font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:4px 12px;border-radius:20px;width:fit-content;background:color-mix(in srgb,var(--ion-color-primary) 12%,transparent);color:var(--ion-color-primary)}.perms-section{display:flex;flex-direction:column;gap:10px;margin-bottom:24px}.perms-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.perms-list{display:flex;flex-wrap:wrap;gap:6px}.perm-chip{font-size:.75rem;font-weight:500;padding:3px 10px;border-radius:20px;border:1px solid var(--val-border-color, rgba(0, 0, 0, .12));color:var(--ion-color-dark);background:transparent}.remove-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08));padding-top:20px}\n"] }]
48371
+ }], ctorParameters: () => [], propDecorators: { _modalRef: [{
48372
+ type: Input
48373
+ }], member: [{
48374
+ type: Input
48375
+ }], orgId: [{
48376
+ type: Input
48377
+ }], canManage: [{
48378
+ type: Input
48379
+ }], isSelf: [{
48380
+ type: Input
48381
+ }], availableRoles: [{
48382
+ type: Input
48383
+ }], onChanged: [{
48384
+ type: Input
48385
+ }], i18nNamespace: [{
48386
+ type: Input
48387
+ }] } });
48388
+
48389
+ /**
48390
+ * Defaults i18n (es/en) embebidos en `val-edit-org-modal`. Auto-registrados en
48391
+ * el constructor del componente si el consumer no proveyó el namespace
48392
+ * (`EditOrgModal` por default). SOLO incluye las keys que el modal usa.
48393
+ */
48394
+ const EDIT_ORG_MODAL_I18N = {
48395
+ es: {
48396
+ title: 'Editar organización',
48397
+ subtitle: 'Actualiza el nombre y descripción.',
48398
+ nameLabel: 'Nombre',
48399
+ namePlaceholder: 'Nombre de la organización',
48400
+ nameRequired: 'El nombre es obligatorio.',
48401
+ nameMin: 'Mínimo 2 caracteres.',
48402
+ descriptionLabel: 'Descripción (opcional)',
48403
+ descriptionPlaceholder: 'Describe tu organización',
48404
+ saveCta: 'Guardar cambios',
48405
+ close: 'Cancelar',
48406
+ saveSuccess: 'Organización actualizada.',
48407
+ saveError: 'No se pudo guardar. Intenta de nuevo.',
48408
+ fillRequired: 'Completa todos los campos requeridos.',
48409
+ },
48410
+ en: {
48411
+ title: 'Edit organization',
48412
+ subtitle: 'Update the name and description.',
48413
+ nameLabel: 'Name',
48414
+ namePlaceholder: 'Organization name',
48415
+ nameRequired: 'Name is required.',
48416
+ nameMin: 'Minimum 2 characters.',
48417
+ descriptionLabel: 'Description (optional)',
48418
+ descriptionPlaceholder: 'Describe your organization',
48419
+ saveCta: 'Save changes',
48420
+ close: 'Cancel',
48421
+ saveSuccess: 'Organization updated.',
48422
+ saveError: 'Could not save. Please try again.',
48423
+ fillRequired: 'Please fill in all required fields.',
48424
+ },
48425
+ };
48426
+
48427
+ const DEFAULT_NAMESPACE$1 = 'EditOrgModal';
48428
+ /**
48429
+ * `val-edit-org-modal` — modal de edición de organización (nombre + descripción).
48430
+ * Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por la vista de
48431
+ * gestión de organización (y futuras vistas org-aware).
48432
+ *
48433
+ * Edita la org vía `OrgService.updateOrg`. El formulario se construye una vez y se
48434
+ * mantiene reactivo a idioma + estado de trabajo vía effects `allowSignalWrites`.
48435
+ * Si el consumer no pasa `org`, cae al `activeOrgId` del `AuthService`.
48436
+ *
48437
+ * Abierto vía `ModalService` — recibe `_modalRef` por `componentProps` + callback
48438
+ * `onSuccess(updated)`. Header canónico (Regla #5): sin `ion-title`, botón Cerrar
48439
+ * texto en `slot=end`, título/subtítulo en el body con `val-display`/`val-title`.
48440
+ *
48441
+ * Auto-registra sus defaults i18n (es/en) si el consumer no proveyó el namespace
48442
+ * `EditOrgModal`.
48443
+ */
48444
+ class EditOrgModalComponent {
48445
+ constructor() {
48446
+ this.i18n = inject(I18nService);
48447
+ this.orgService = inject(OrgService);
48448
+ this.toast = inject(ToastService);
48449
+ this.errors = inject(ValtechErrorService);
48450
+ this.auth = inject(AuthService);
48451
+ /** Organización a editar (pasada por `componentProps`). */
48452
+ this.org = null;
48453
+ /** Namespace i18n con que la vista resuelve sus textos. */
48454
+ this.i18nNamespace = DEFAULT_NAMESPACE$1;
48455
+ this.saving = signal(false);
48456
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$1)) {
48457
+ this.i18n.registerContent(DEFAULT_NAMESPACE$1, EDIT_ORG_MODAL_I18N);
48458
+ }
48459
+ this.formMeta = signal(this.buildFormMeta());
48460
+ effect(() => {
48461
+ this.i18n.lang();
48462
+ this.formMeta.set(this.buildFormMeta());
48463
+ }, { allowSignalWrites: true });
48464
+ effect(() => {
48465
+ const w = this.saving();
48466
+ this.formMeta.update(m => ({
48467
+ ...m,
48468
+ state: w ? ComponentStates.WORKING : ComponentStates.ENABLED,
48469
+ actions: {
48470
+ ...m.actions,
48471
+ state: w ? ComponentStates.WORKING : ComponentStates.ENABLED,
48472
+ },
48473
+ }));
48474
+ }, { allowSignalWrites: true });
48475
+ }
48476
+ ngOnInit() {
48477
+ this.formMeta.set(this.buildFormMeta());
48478
+ }
48479
+ get activeOrgId() {
48480
+ const u = this.auth.user();
48481
+ return u?.activeOrgId ?? u?.activeOrg ?? '';
48482
+ }
48483
+ buildFormMeta() {
48484
+ const nameField = {
48485
+ token: 'edit-org-name',
48486
+ name: 'name',
48487
+ label: this.t('nameLabel'),
48488
+ hint: '',
48489
+ placeholder: this.t('namePlaceholder'),
48490
+ type: InputType.TEXT,
48491
+ order: 1,
48492
+ validators: [Validators.required, Validators.minLength(2), Validators.maxLength(100)],
48493
+ errors: {
48494
+ required: this.t('nameRequired'),
48495
+ minlength: this.t('nameMin'),
48496
+ },
48497
+ value: this.org?.name ?? '',
48498
+ state: ComponentStates.ENABLED,
48499
+ };
48500
+ const descField = {
48501
+ token: 'edit-org-desc',
48502
+ name: 'description',
48503
+ label: this.t('descriptionLabel'),
48504
+ hint: '',
48505
+ placeholder: this.t('descriptionPlaceholder'),
48506
+ type: InputType.TEXTAREA,
48507
+ order: 2,
48508
+ validators: [],
48509
+ errors: {},
48510
+ value: this.org?.description ?? '',
48511
+ state: ComponentStates.ENABLED,
48512
+ };
48513
+ return {
48514
+ name: '',
48515
+ state: ComponentStates.ENABLED,
48516
+ sections: [{ name: '', order: 1, fields: [nameField, descField] }],
48517
+ actions: {
48518
+ text: this.t('saveCta'),
48519
+ color: 'dark',
48520
+ type: 'submit',
48521
+ fill: 'solid',
48522
+ size: 'default',
48523
+ shape: 'round',
48524
+ expand: 'block',
48525
+ state: ComponentStates.ENABLED,
48526
+ token: 'edit-org-submit',
48527
+ },
48528
+ };
48529
+ }
48530
+ onFormSubmit(event) {
48531
+ const name = event.fields['name'];
48532
+ if (!name?.trim()) {
48533
+ this.toast.show({
48534
+ message: this.t('fillRequired'),
48535
+ duration: 3000,
48536
+ color: 'dark',
48537
+ position: 'top',
48538
+ });
48539
+ return;
48540
+ }
48541
+ const description = event.fields['description'] ?? '';
48542
+ void this.save(name, description);
48543
+ }
48544
+ async save(name, description) {
48545
+ const orgId = this.org?.id ?? this.activeOrgId;
48546
+ if (!orgId || this.saving())
48547
+ return;
48548
+ this.saving.set(true);
48549
+ try {
48550
+ const updated = await firstValueFrom(this.orgService.updateOrg(orgId, { name, description }));
48551
+ this.toast.show({
48552
+ message: this.t('saveSuccess'),
48553
+ color: 'dark',
48554
+ duration: 3500,
48555
+ });
48556
+ this.onSuccess?.(updated);
48557
+ this._modalRef?.dismiss(updated, 'confirm');
48558
+ }
48559
+ catch (err) {
48560
+ this.errors.handle(err, {
48561
+ context: 'editOrgModal.save',
48562
+ fallbackKey: 'saveError',
48563
+ i18nNamespace: this.i18nNamespace,
48564
+ });
48565
+ }
48566
+ finally {
48567
+ this.saving.set(false);
48568
+ }
48569
+ }
48570
+ dismiss() {
48571
+ this._modalRef?.dismiss(null, 'cancel');
48572
+ }
48573
+ t(key) {
48574
+ return this.i18n.t(key, this.i18nNamespace);
48575
+ }
48576
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EditOrgModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
48577
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: EditOrgModalComponent, isStandalone: true, selector: "val-edit-org-modal", inputs: { _modalRef: "_modalRef", org: "org", onSuccess: "onSuccess", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
48578
+ <ion-header>
48579
+ <ion-toolbar>
48580
+ <ion-buttons slot="end">
48581
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
48582
+ <strong>{{ t('close') }}</strong>
48583
+ </ion-button>
48584
+ </ion-buttons>
48585
+ </ion-toolbar>
48586
+ </ion-header>
48587
+ <ion-content class="ion-padding">
48588
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
48589
+ <val-title
48590
+ [props]="{
48591
+ content: t('subtitle'),
48592
+ size: 'large',
48593
+ color: '',
48594
+ bold: false,
48595
+ }"
48596
+ />
48597
+ <val-form [props]="formMeta()" (onSubmit)="onFormSubmit($event)" />
48598
+ </ion-content>
48599
+ `, isInline: true, dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
48600
+ }
48601
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EditOrgModalComponent, decorators: [{
48602
+ type: Component,
48603
+ args: [{
48604
+ selector: 'val-edit-org-modal',
48605
+ standalone: true,
48606
+ imports: [IonHeader, IonToolbar, IonButtons, IonButton, IonContent, FormComponent, DisplayComponent, TitleComponent],
48607
+ template: `
48608
+ <ion-header>
48609
+ <ion-toolbar>
48610
+ <ion-buttons slot="end">
48611
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
48612
+ <strong>{{ t('close') }}</strong>
48613
+ </ion-button>
48614
+ </ion-buttons>
48615
+ </ion-toolbar>
48616
+ </ion-header>
48617
+ <ion-content class="ion-padding">
48618
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
48619
+ <val-title
48620
+ [props]="{
48621
+ content: t('subtitle'),
48622
+ size: 'large',
48623
+ color: '',
48624
+ bold: false,
48625
+ }"
48626
+ />
48627
+ <val-form [props]="formMeta()" (onSubmit)="onFormSubmit($event)" />
48628
+ </ion-content>
48629
+ `,
48630
+ }]
48631
+ }], ctorParameters: () => [], propDecorators: { _modalRef: [{
48632
+ type: Input
48633
+ }], org: [{
48634
+ type: Input
48635
+ }], onSuccess: [{
48636
+ type: Input
48637
+ }], i18nNamespace: [{
48638
+ type: Input
48639
+ }] } });
48640
+
48641
+ /**
48642
+ * Defaults i18n (es/en) embebidos en `val-transfer-ownership-modal`.
48643
+ * Auto-registrados en el constructor del componente si el consumer no proveyó el
48644
+ * namespace (`Settings.TransferModal` por default). SOLO incluye las keys que el
48645
+ * modal usa.
48646
+ */
48647
+ const TRANSFER_OWNERSHIP_MODAL_I18N = {
48648
+ es: {
48649
+ title: 'Transferir propiedad',
48650
+ hint: 'Selecciona el nuevo propietario de la organización.',
48651
+ searchPlaceholder: 'Buscar miembro...',
48652
+ empty: 'No hay otros miembros disponibles.',
48653
+ noResults: 'Sin resultados para',
48654
+ confirmCta: 'Confirmar transferencia',
48655
+ close: 'Cancelar',
48656
+ },
48657
+ en: {
48658
+ title: 'Transfer ownership',
48659
+ hint: 'Select the new owner of the organization.',
48660
+ searchPlaceholder: 'Search member...',
48661
+ empty: 'No other members available.',
48662
+ noResults: 'No results for',
48663
+ confirmCta: 'Confirm transfer',
48664
+ close: 'Cancel',
48665
+ },
48666
+ };
48667
+
48668
+ const DEFAULT_NAMESPACE = 'Settings.TransferModal';
48669
+ /**
48670
+ * `val-transfer-ownership-modal` — selector de miembro para transferir la
48671
+ * propiedad de la organización. Promovido desde `showcase` bajo el proceso de
48672
+ * ADR-021.
48673
+ *
48674
+ * Recibe la lista de candidatos por `members` (todos excepto el owner actual),
48675
+ * permite filtrarlos con `val-searchbar` y, al confirmar, ejecuta el callback
48676
+ * `onConfirm(newOwnerId)`. La ejecución real de la transferencia (vía
48677
+ * `OrgService.transferOwnership`) queda en el consumer.
48678
+ *
48679
+ * Abierto vía `ModalService` — recibe `_modalRef` por `componentProps`. Header
48680
+ * canónico (Regla #5): sin `ion-title`, botón Cerrar texto en `slot=end`,
48681
+ * título/subtítulo en el body con `val-display`/`val-title`.
48682
+ *
48683
+ * Auto-registra sus defaults i18n (es/en) si el consumer no proveyó el namespace
48684
+ * `Settings.TransferModal`.
48685
+ */
48686
+ class TransferOwnershipModalComponent {
48687
+ constructor() {
48688
+ this.i18n = inject(I18nService);
48689
+ /** Lista de miembros transferibles (todos excepto el owner actual). */
48690
+ this.members = [];
48691
+ /** Namespace i18n con que la vista resuelve sus textos. */
48692
+ this.i18nNamespace = DEFAULT_NAMESPACE;
48693
+ this.query = signal('');
48694
+ this.selectedId = signal('');
48695
+ this.filtered = computed(() => {
48696
+ const q = this.query().trim().toLowerCase();
48697
+ if (!q)
48698
+ return this.members;
48699
+ return this.members.filter(m => m.name?.toLowerCase().includes(q) ||
48700
+ m.email?.toLowerCase().includes(q) ||
48701
+ m.userId.toLowerCase().includes(q));
48702
+ });
48703
+ this.noResultsLabel = computed(() => `${this.t('noResults')} "${this.query()}"`);
48704
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE)) {
48705
+ this.i18n.registerContent(DEFAULT_NAMESPACE, TRANSFER_OWNERSHIP_MODAL_I18N);
48706
+ }
48707
+ }
48708
+ ngOnInit() {
48709
+ if (!this.i18n.hasNamespace(this.i18nNamespace)) {
48710
+ this.i18n.registerContent(this.i18nNamespace, TRANSFER_OWNERSHIP_MODAL_I18N);
48711
+ }
48712
+ }
48713
+ confirm() {
48714
+ const id = this.selectedId();
48715
+ if (!id)
48716
+ return;
48717
+ this.onConfirm?.(id);
48718
+ this.dismiss();
48719
+ }
48720
+ dismiss() {
48721
+ this._modalRef?.dismiss(null, 'cancel');
48722
+ }
48723
+ t(key) {
48724
+ return this.i18n.t(key, this.i18nNamespace);
48725
+ }
48726
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TransferOwnershipModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
48727
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: TransferOwnershipModalComponent, isStandalone: true, selector: "val-transfer-ownership-modal", inputs: { members: "members", onConfirm: "onConfirm", _modalRef: "_modalRef", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
48728
+ <ion-header>
48729
+ <ion-toolbar>
48730
+ <ion-buttons slot="end">
48731
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
48732
+ <strong>{{ t('close') }}</strong>
48733
+ </ion-button>
48734
+ </ion-buttons>
48735
+ </ion-toolbar>
48736
+ </ion-header>
48737
+
48738
+ <ion-content class="ion-padding">
48739
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
48740
+ <val-title
48741
+ [props]="{
48742
+ content: t('hint'),
48743
+ size: 'large',
48744
+ color: '',
48745
+ bold: false,
48746
+ }"
48747
+ />
48748
+
48749
+ <val-searchbar
48750
+ [props]="{ placeholder: t('searchPlaceholder'), debounce: 150 }"
48751
+ (filterEvent)="query.set($event)"
48752
+ />
48753
+
48754
+ @if (members.length === 0) {
48755
+ <val-text
48756
+ [props]="{
48757
+ content: t('empty'),
48758
+ size: 'medium',
48759
+ color: 'medium',
48760
+ bold: false,
48761
+ }"
48762
+ />
48763
+ } @else if (filtered().length === 0) {
48764
+ <val-text
48765
+ [props]="{
48766
+ content: noResultsLabel(),
48767
+ size: 'medium',
48768
+ color: 'medium',
48769
+ bold: false,
48770
+ }"
48771
+ />
48772
+ } @else {
48773
+ <div class="transfer-member-list">
48774
+ @for (m of filtered(); track m.userId) {
48775
+ <button
48776
+ class="transfer-member-row"
48777
+ [class.transfer-member-row--selected]="selectedId() === m.userId"
48778
+ (click)="selectedId.set(m.userId)"
48779
+ type="button"
48780
+ >
48781
+ <span class="transfer-member-name">{{ m.name || m.email || m.userId }}</span>
48782
+ @if (selectedId() === m.userId) {
48783
+ <span class="transfer-member-check">&#10003;</span>
48784
+ }
48785
+ </button>
48786
+ }
48787
+ </div>
48788
+ }
48789
+
48790
+ <div class="transfer-confirm-row">
48791
+ <ion-button expand="block" color="dark" [disabled]="!selectedId()" (click)="confirm()">
48792
+ {{ t('confirmCta') }}
48793
+ </ion-button>
48794
+ </div>
48795
+ </ion-content>
48796
+ `, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:16px}val-searchbar{display:block;margin-bottom:12px}.transfer-member-list{display:flex;flex-direction:column;gap:4px}.transfer-member-row{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-radius:10px;border:1.5px solid var(--ion-color-light);background:transparent;cursor:pointer;width:100%;text-align:left;font-size:15px;color:var(--ion-color-dark);transition:border-color .15s}.transfer-member-row--selected{border-color:var(--ion-color-dark);background:var(--ion-color-light)}.transfer-member-check{font-size:17px;color:var(--ion-color-dark)}.transfer-confirm-row{margin-top:24px}\n"], dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: SearchbarComponent, selector: "val-searchbar", inputs: ["preset", "props"], outputs: ["filterEvent", "focusEvent", "blurEvent"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
48797
+ }
48798
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TransferOwnershipModalComponent, decorators: [{
48799
+ type: Component,
48800
+ args: [{ selector: 'val-transfer-ownership-modal', standalone: true, imports: [
48801
+ IonHeader,
48802
+ IonToolbar,
48803
+ IonButtons,
48804
+ IonButton,
48805
+ IonContent,
48806
+ SearchbarComponent,
48807
+ DisplayComponent,
48808
+ TextComponent,
48809
+ TitleComponent,
48810
+ ], template: `
48811
+ <ion-header>
48812
+ <ion-toolbar>
48813
+ <ion-buttons slot="end">
48814
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
48815
+ <strong>{{ t('close') }}</strong>
48816
+ </ion-button>
48817
+ </ion-buttons>
48818
+ </ion-toolbar>
48819
+ </ion-header>
48820
+
48821
+ <ion-content class="ion-padding">
48822
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
48823
+ <val-title
48824
+ [props]="{
48825
+ content: t('hint'),
48826
+ size: 'large',
48827
+ color: '',
48828
+ bold: false,
48829
+ }"
48830
+ />
48831
+
48832
+ <val-searchbar
48833
+ [props]="{ placeholder: t('searchPlaceholder'), debounce: 150 }"
48834
+ (filterEvent)="query.set($event)"
48835
+ />
48836
+
48837
+ @if (members.length === 0) {
48838
+ <val-text
48839
+ [props]="{
48840
+ content: t('empty'),
48841
+ size: 'medium',
48842
+ color: 'medium',
48843
+ bold: false,
48844
+ }"
48845
+ />
48846
+ } @else if (filtered().length === 0) {
48847
+ <val-text
48848
+ [props]="{
48849
+ content: noResultsLabel(),
48850
+ size: 'medium',
48851
+ color: 'medium',
48852
+ bold: false,
48853
+ }"
48854
+ />
48855
+ } @else {
48856
+ <div class="transfer-member-list">
48857
+ @for (m of filtered(); track m.userId) {
48858
+ <button
48859
+ class="transfer-member-row"
48860
+ [class.transfer-member-row--selected]="selectedId() === m.userId"
48861
+ (click)="selectedId.set(m.userId)"
48862
+ type="button"
48863
+ >
48864
+ <span class="transfer-member-name">{{ m.name || m.email || m.userId }}</span>
48865
+ @if (selectedId() === m.userId) {
48866
+ <span class="transfer-member-check">&#10003;</span>
48867
+ }
48868
+ </button>
48869
+ }
48870
+ </div>
48871
+ }
48872
+
48873
+ <div class="transfer-confirm-row">
48874
+ <ion-button expand="block" color="dark" [disabled]="!selectedId()" (click)="confirm()">
48875
+ {{ t('confirmCta') }}
48876
+ </ion-button>
48877
+ </div>
48878
+ </ion-content>
48879
+ `, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:16px}val-searchbar{display:block;margin-bottom:12px}.transfer-member-list{display:flex;flex-direction:column;gap:4px}.transfer-member-row{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-radius:10px;border:1.5px solid var(--ion-color-light);background:transparent;cursor:pointer;width:100%;text-align:left;font-size:15px;color:var(--ion-color-dark);transition:border-color .15s}.transfer-member-row--selected{border-color:var(--ion-color-dark);background:var(--ion-color-light)}.transfer-member-check{font-size:17px;color:var(--ion-color-dark)}.transfer-confirm-row{margin-top:24px}\n"] }]
48880
+ }], ctorParameters: () => [], propDecorators: { members: [{
48881
+ type: Input
48882
+ }], onConfirm: [{
48883
+ type: Input
48884
+ }], _modalRef: [{
48885
+ type: Input
48886
+ }], i18nNamespace: [{
48887
+ type: Input
48888
+ }] } });
48889
+
47379
48890
  /** Built-in label sets for the three platform locales. */
47380
48891
  const CALLOUT_LABELS = {
47381
48892
  es: {
@@ -57700,5 +59211,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
57700
59211
  * Generated bundle index. Do not edit.
57701
59212
  */
57702
59213
 
57703
- export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
59214
+ export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
57704
59215
  //# sourceMappingURL=valtech-components.mjs.map