valtech-components 2.0.1006 → 2.0.1007

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/esm2022/lib/components/organisms/api-keys-modal/api-keys-modal.component.mjs +385 -0
  2. package/esm2022/lib/components/organisms/api-keys-modal/api-keys-modal.i18n.mjs +63 -0
  3. package/esm2022/lib/components/organisms/member-import-modal/member-import-modal.component.mjs +313 -0
  4. package/esm2022/lib/components/organisms/member-import-modal/member-import-modal.i18n.mjs +63 -0
  5. package/esm2022/lib/components/organisms/organization-view/organization-view.component.mjs +141 -3
  6. package/esm2022/lib/components/organisms/organization-view/organization-view.i18n.mjs +21 -1
  7. package/esm2022/lib/services/apikeys/api-keys.service.mjs +46 -0
  8. package/esm2022/lib/services/apikeys/types.mjs +7 -0
  9. package/esm2022/lib/services/auth/auth.service.mjs +14 -1
  10. package/esm2022/lib/services/auth/types.mjs +1 -1
  11. package/esm2022/lib/services/org/org.service.mjs +8 -1
  12. package/esm2022/lib/services/org/types.mjs +1 -1
  13. package/esm2022/lib/version.mjs +2 -2
  14. package/esm2022/public-api.mjs +4 -1
  15. package/fesm2022/valtech-components.mjs +1062 -44
  16. package/fesm2022/valtech-components.mjs.map +1 -1
  17. package/lib/components/organisms/api-keys-modal/api-keys-modal.component.d.ts +44 -0
  18. package/lib/components/organisms/api-keys-modal/api-keys-modal.i18n.d.ts +6 -0
  19. package/lib/components/organisms/member-import-modal/member-import-modal.component.d.ts +47 -0
  20. package/lib/components/organisms/member-import-modal/member-import-modal.i18n.d.ts +6 -0
  21. package/lib/components/organisms/organization-view/organization-view.component.d.ts +6 -0
  22. package/lib/services/apikeys/api-keys.service.d.ts +25 -0
  23. package/lib/services/apikeys/types.d.ts +46 -0
  24. package/lib/services/auth/auth.service.d.ts +7 -1
  25. package/lib/services/auth/types.d.ts +23 -0
  26. package/lib/services/org/org.service.d.ts +6 -1
  27. package/lib/services/org/types.d.ts +30 -0
  28. package/lib/version.d.ts +1 -1
  29. package/package.json +1 -1
  30. package/public-api.d.ts +5 -1
@@ -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.1006';
57
+ const VERSION = '2.0.1007';
58
58
 
59
59
  // Control de estado de refresco (singleton a nivel de módulo)
60
60
  let isRefreshing = false;
@@ -8658,6 +8658,19 @@ class AuthService {
8658
8658
  .post(`${this.baseUrl}/reset-password`, request)
8659
8659
  .pipe(catchError(error => this.handleAuthError(error)));
8660
8660
  }
8661
+ /**
8662
+ * Activa una cuenta pre-aprovisionada por una organización (ADR-023): define
8663
+ * la contraseña con el token del enlace de email. En éxito el backend devuelve
8664
+ * tokens de auto-signin → el usuario queda logueado (mismo flujo que verify-email).
8665
+ */
8666
+ activateAccount(request) {
8667
+ this.stateService.clearError();
8668
+ return this.http.post(`${this.baseUrl}/activate`, request).pipe(tap(response => {
8669
+ if (response.activated && response.accessToken) {
8670
+ this.handleSuccessfulAuth(response);
8671
+ }
8672
+ }), catchError(error => this.handleAuthError(error)));
8673
+ }
8661
8674
  /**
8662
8675
  * Cambia la contraseña del usuario autenticado.
8663
8676
  * Requiere la contraseña actual para verificación.
@@ -41737,7 +41750,7 @@ const PROFILE_VIEW_I18N = {
41737
41750
  const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
41738
41751
  const HANDLE_MIN = 3;
41739
41752
  const HANDLE_MAX = 20;
41740
- const DEFAULT_NAMESPACE$g = 'Settings.Profile';
41753
+ const DEFAULT_NAMESPACE$i = 'Settings.Profile';
41741
41754
  /**
41742
41755
  * `val-profile-view` — vista Perfil full-feature autocontenida (organism).
41743
41756
  *
@@ -41787,7 +41800,7 @@ class ProfileViewComponent {
41787
41800
  showPhone: merged.showPhone ?? true,
41788
41801
  showShareCta: merged.showShareCta ?? true,
41789
41802
  profileBaseUrl: merged.profileBaseUrl ?? '',
41790
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$g,
41803
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$i,
41791
41804
  onSaved: merged.onSaved,
41792
41805
  onAvatarUploaded: merged.onAvatarUploaded,
41793
41806
  };
@@ -42703,7 +42716,7 @@ const PREFERENCES_VIEW_I18N = {
42703
42716
  },
42704
42717
  };
42705
42718
 
42706
- const DEFAULT_NAMESPACE$f = 'Settings.General';
42719
+ const DEFAULT_NAMESPACE$h = 'Settings.General';
42707
42720
  const DEFAULT_LANGUAGES = ['es', 'en'];
42708
42721
  /**
42709
42722
  * `val-preferences-view` — vista de preferencias de UI cross-app (tema +
@@ -42740,7 +42753,7 @@ class PreferencesViewComponent {
42740
42753
  showLanguage: merged.showLanguage ?? true,
42741
42754
  showFontSize: merged.showFontSize ?? true,
42742
42755
  supportedLanguages: merged.supportedLanguages ?? DEFAULT_LANGUAGES,
42743
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$f,
42756
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$h,
42744
42757
  onThemeChanged: merged.onThemeChanged,
42745
42758
  onLanguageChanged: merged.onLanguageChanged,
42746
42759
  onFontSizeChanged: merged.onFontSizeChanged,
@@ -44496,7 +44509,7 @@ const SECURITY_VIEW_I18N = {
44496
44509
  };
44497
44510
 
44498
44511
  addIcons({ laptopOutline, phonePortraitOutline });
44499
- const DEFAULT_NAMESPACE$e = 'Settings.Security';
44512
+ const DEFAULT_NAMESPACE$g = 'Settings.Security';
44500
44513
  /**
44501
44514
  * `val-security-view` — vista Seguridad full-feature autocontenida (organism).
44502
44515
  *
@@ -44545,7 +44558,7 @@ class SecurityViewComponent {
44545
44558
  showChangeEmail: merged.showChangeEmail ?? true,
44546
44559
  showMfa: merged.showMfa ?? true,
44547
44560
  showPolicies: merged.showPolicies ?? true,
44548
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$e,
44561
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$g,
44549
44562
  handleDeepLinks: merged.handleDeepLinks ?? true,
44550
44563
  homeRoute: merged.homeRoute ?? '/app/home',
44551
44564
  onMfaCompleted: merged.onMfaCompleted,
@@ -45347,6 +45360,13 @@ class OrgService {
45347
45360
  inviteUser(orgId, req) {
45348
45361
  return this.http.post(`${this.baseUrl}/${orgId}/invite`, req);
45349
45362
  }
45363
+ /**
45364
+ * Crea/asigna miembros en masa (ADR-023). Resultado por-fila (partial success):
45365
+ * `created` (pre-aprovisionado), `assigned` (ya existía), `skipped`, `error`.
45366
+ */
45367
+ importMembers(orgId, req) {
45368
+ return this.http.post(`${this.baseUrl}/${orgId}/members/import`, req);
45369
+ }
45350
45370
  leaveOrg(orgId) {
45351
45371
  return this.http.post(`${this.baseUrl}/${orgId}/leave`, {});
45352
45372
  }
@@ -45451,7 +45471,7 @@ const CREATE_ORG_MODAL_I18N = {
45451
45471
  },
45452
45472
  };
45453
45473
 
45454
- const DEFAULT_NAMESPACE$d = 'CreateOrgModal';
45474
+ const DEFAULT_NAMESPACE$f = 'CreateOrgModal';
45455
45475
  /**
45456
45476
  * `val-create-org-modal` — modal de creación de organización (organism
45457
45477
  * compartido). Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por
@@ -45477,10 +45497,10 @@ class CreateOrgModalComponent {
45477
45497
  this.toast = inject(ToastService);
45478
45498
  this.errors = inject(ValtechErrorService);
45479
45499
  /** Namespace i18n con que la vista resuelve sus textos. */
45480
- this.i18nNamespace = DEFAULT_NAMESPACE$d;
45500
+ this.i18nNamespace = DEFAULT_NAMESPACE$f;
45481
45501
  this._busy = signal(false);
45482
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$d)) {
45483
- this.i18n.registerContent(DEFAULT_NAMESPACE$d, CREATE_ORG_MODAL_I18N);
45502
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$f)) {
45503
+ this.i18n.registerContent(DEFAULT_NAMESPACE$f, CREATE_ORG_MODAL_I18N);
45484
45504
  }
45485
45505
  this.formMeta = signal(this.buildFormMeta());
45486
45506
  effect(() => {
@@ -45716,7 +45736,7 @@ const DELETE_ACCOUNT_MODAL_I18N = {
45716
45736
  },
45717
45737
  };
45718
45738
 
45719
- const DEFAULT_NAMESPACE$c = 'Settings.DeleteAccount';
45739
+ const DEFAULT_NAMESPACE$e = 'Settings.DeleteAccount';
45720
45740
  /**
45721
45741
  * `val-delete-account-modal` — modal de eliminación de cuenta (organism,
45722
45742
  * cuenta-específico). Promovido desde `showcase` bajo el proceso de ADR-021.
@@ -45742,7 +45762,7 @@ class DeleteAccountModalComponent {
45742
45762
  this.i18n = inject(I18nService);
45743
45763
  this.toast = inject(ToastService);
45744
45764
  /** Namespace i18n con que la vista resuelve sus textos. */
45745
- this.i18nNamespace = DEFAULT_NAMESPACE$c;
45765
+ this.i18nNamespace = DEFAULT_NAMESPACE$e;
45746
45766
  this.hasPassword = signal(null);
45747
45767
  this.codeSent = signal(false);
45748
45768
  this.formState = signal(ComponentStates.ENABLED);
@@ -45809,8 +45829,8 @@ class DeleteAccountModalComponent {
45809
45829
  ],
45810
45830
  actions: this.submitBtn('delete-code-submit', 'confirmCta'),
45811
45831
  }));
45812
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$c)) {
45813
- this.i18n.registerContent(DEFAULT_NAMESPACE$c, DELETE_ACCOUNT_MODAL_I18N);
45832
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$e)) {
45833
+ this.i18n.registerContent(DEFAULT_NAMESPACE$e, DELETE_ACCOUNT_MODAL_I18N);
45814
45834
  }
45815
45835
  }
45816
45836
  ngOnInit() {
@@ -46011,7 +46031,7 @@ const ORG_INFO_SHEET_I18N = {
46011
46031
  },
46012
46032
  };
46013
46033
 
46014
- const DEFAULT_NAMESPACE$b = 'OrgInfoSheet';
46034
+ const DEFAULT_NAMESPACE$d = 'OrgInfoSheet';
46015
46035
  /**
46016
46036
  * `val-org-info-sheet` — sheet informativo "¿Qué son las organizaciones?".
46017
46037
  * Extraído del `OrgInfoSheetComponent` inline de `AccountPage` bajo el proceso de
@@ -46056,15 +46076,15 @@ class OrgInfoSheetComponent {
46056
46076
  text: this.closeProps.text || this.resolvedCloseLabel(),
46057
46077
  }));
46058
46078
  addIcons({ peopleOutline, swapHorizontalOutline, mailOutline });
46059
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$b)) {
46060
- this.i18n.registerContent(DEFAULT_NAMESPACE$b, ORG_INFO_SHEET_I18N);
46079
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$d)) {
46080
+ this.i18n.registerContent(DEFAULT_NAMESPACE$d, ORG_INFO_SHEET_I18N);
46061
46081
  }
46062
46082
  }
46063
46083
  dismiss() {
46064
46084
  this._modalRef?.dismiss(null, 'cancel');
46065
46085
  }
46066
46086
  tt(key) {
46067
- return this.i18n.t(key, DEFAULT_NAMESPACE$b);
46087
+ return this.i18n.t(key, DEFAULT_NAMESPACE$d);
46068
46088
  }
46069
46089
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgInfoSheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
46070
46090
  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: `
@@ -46231,7 +46251,7 @@ const SWITCH_ORG_MODAL_I18N = {
46231
46251
  },
46232
46252
  };
46233
46253
 
46234
- const DEFAULT_NAMESPACE$a = 'Settings.SwitchOrg';
46254
+ const DEFAULT_NAMESPACE$c = 'Settings.SwitchOrg';
46235
46255
  addIcons({ businessOutline, checkmarkCircleOutline });
46236
46256
  /**
46237
46257
  * `val-switch-org-modal` — modal de cambio de organización activa (organism
@@ -46257,7 +46277,7 @@ class SwitchOrgModalComponent {
46257
46277
  this.orgSwitch = inject(OrgSwitchService);
46258
46278
  this.errors = inject(ValtechErrorService);
46259
46279
  /** Namespace i18n con que la vista resuelve sus textos. */
46260
- this.i18nNamespace = DEFAULT_NAMESPACE$a;
46280
+ this.i18nNamespace = DEFAULT_NAMESPACE$c;
46261
46281
  this.orgs = signal([]);
46262
46282
  this.loading = signal(false);
46263
46283
  this.query = signal('');
@@ -46273,8 +46293,8 @@ class SwitchOrgModalComponent {
46273
46293
  return list;
46274
46294
  return list.filter(o => o.name.toLowerCase().includes(q));
46275
46295
  });
46276
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$a)) {
46277
- this.i18n.registerContent(DEFAULT_NAMESPACE$a, SWITCH_ORG_MODAL_I18N);
46296
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$c)) {
46297
+ this.i18n.registerContent(DEFAULT_NAMESPACE$c, SWITCH_ORG_MODAL_I18N);
46278
46298
  }
46279
46299
  this.loadOrgs();
46280
46300
  }
@@ -46592,7 +46612,7 @@ const ACCOUNT_VIEW_I18N = {
46592
46612
  };
46593
46613
 
46594
46614
  addIcons({ peopleOutline, businessOutline, chevronForwardOutline, swapHorizontalOutline, mailOutline });
46595
- const DEFAULT_NAMESPACE$9 = 'Settings.Account';
46615
+ const DEFAULT_NAMESPACE$b = 'Settings.Account';
46596
46616
  const DEFAULT_MANAGE_ORG_ROUTE = '/app/settings/organization';
46597
46617
  /**
46598
46618
  * `val-account-view` — vista Cuenta full-feature autocontenida (organism).
@@ -46633,7 +46653,7 @@ class AccountViewComponent {
46633
46653
  showNewOrgCta: merged.showNewOrgCta ?? true,
46634
46654
  showLogout: merged.showLogout ?? true,
46635
46655
  showDeleteAccount: merged.showDeleteAccount ?? true,
46636
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$9,
46656
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$b,
46637
46657
  manageOrgRoute: merged.manageOrgRoute ?? DEFAULT_MANAGE_ORG_ROUTE,
46638
46658
  onOrgSwitched: merged.onOrgSwitched,
46639
46659
  onOrgCreated: merged.onOrgCreated,
@@ -47358,7 +47378,7 @@ const EDIT_ORG_MODAL_I18N = {
47358
47378
  },
47359
47379
  };
47360
47380
 
47361
- const DEFAULT_NAMESPACE$8 = 'EditOrgModal';
47381
+ const DEFAULT_NAMESPACE$a = 'EditOrgModal';
47362
47382
  /**
47363
47383
  * `val-edit-org-modal` — modal de edición de organización (nombre + descripción).
47364
47384
  * Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por la vista de
@@ -47385,10 +47405,10 @@ class EditOrgModalComponent {
47385
47405
  /** Organización a editar (pasada por `componentProps`). */
47386
47406
  this.org = null;
47387
47407
  /** Namespace i18n con que la vista resuelve sus textos. */
47388
- this.i18nNamespace = DEFAULT_NAMESPACE$8;
47408
+ this.i18nNamespace = DEFAULT_NAMESPACE$a;
47389
47409
  this.saving = signal(false);
47390
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$8)) {
47391
- this.i18n.registerContent(DEFAULT_NAMESPACE$8, EDIT_ORG_MODAL_I18N);
47410
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$a)) {
47411
+ this.i18n.registerContent(DEFAULT_NAMESPACE$a, EDIT_ORG_MODAL_I18N);
47392
47412
  }
47393
47413
  this.formMeta = signal(this.buildFormMeta());
47394
47414
  effect(() => {
@@ -47626,7 +47646,7 @@ const INVITE_MEMBER_MODAL_I18N = {
47626
47646
  },
47627
47647
  };
47628
47648
 
47629
- const DEFAULT_NAMESPACE$7 = 'InviteModal';
47649
+ const DEFAULT_NAMESPACE$9 = 'InviteModal';
47630
47650
  addIcons({ personOutline, mailOutline, closeOutline, checkmarkCircleOutline });
47631
47651
  /**
47632
47652
  * `val-invite-member-modal` — invitación de miembros a una organización.
@@ -47657,7 +47677,7 @@ class InviteMemberModalComponent {
47657
47677
  /** Ids de miembros ya existentes (excluidos de los resultados). */
47658
47678
  this.existingMemberIds = [];
47659
47679
  /** Namespace i18n con que la vista resuelve sus textos. */
47660
- this.i18nNamespace = DEFAULT_NAMESPACE$7;
47680
+ this.i18nNamespace = DEFAULT_NAMESPACE$9;
47661
47681
  this.query = signal('');
47662
47682
  this.searching = signal(false);
47663
47683
  this.searchResults = signal([]);
@@ -47704,8 +47724,8 @@ class InviteMemberModalComponent {
47704
47724
  state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
47705
47725
  }));
47706
47726
  this.searchSubject = new Subject();
47707
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
47708
- this.i18n.registerContent(DEFAULT_NAMESPACE$7, INVITE_MEMBER_MODAL_I18N);
47727
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$9)) {
47728
+ this.i18n.registerContent(DEFAULT_NAMESPACE$9, INVITE_MEMBER_MODAL_I18N);
47709
47729
  }
47710
47730
  this.loadRoles();
47711
47731
  this.roleSub = this.roleControl.valueChanges.subscribe(v => {
@@ -48194,7 +48214,7 @@ const MEMBER_DETAIL_MODAL_I18N = {
48194
48214
  },
48195
48215
  };
48196
48216
 
48197
- const DEFAULT_NAMESPACE$6 = 'Settings.MemberDetail';
48217
+ const DEFAULT_NAMESPACE$8 = 'Settings.MemberDetail';
48198
48218
  /**
48199
48219
  * `val-member-detail-modal` — detalle de miembro de organización: perfil, rol y
48200
48220
  * permisos. Promovido desde `showcase` bajo el proceso de ADR-021.
@@ -48231,7 +48251,7 @@ class MemberDetailModalComponent {
48231
48251
  { id: 'admin', name: 'admin' },
48232
48252
  ];
48233
48253
  /** Namespace i18n con que la vista resuelve sus textos. */
48234
- this.i18nNamespace = DEFAULT_NAMESPACE$6;
48254
+ this.i18nNamespace = DEFAULT_NAMESPACE$8;
48235
48255
  this.working = signal(false);
48236
48256
  this.currentRole = signal('');
48237
48257
  this.roleControl = new FormControl('');
@@ -48271,8 +48291,8 @@ class MemberDetailModalComponent {
48271
48291
  type: 'button',
48272
48292
  state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
48273
48293
  }));
48274
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$6)) {
48275
- this.i18n.registerContent(DEFAULT_NAMESPACE$6, MEMBER_DETAIL_MODAL_I18N);
48294
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$8)) {
48295
+ this.i18n.registerContent(DEFAULT_NAMESPACE$8, MEMBER_DETAIL_MODAL_I18N);
48276
48296
  }
48277
48297
  }
48278
48298
  ngOnInit() {
@@ -48609,7 +48629,7 @@ const TRANSFER_OWNERSHIP_MODAL_I18N = {
48609
48629
  },
48610
48630
  };
48611
48631
 
48612
- const DEFAULT_NAMESPACE$5 = 'Settings.TransferModal';
48632
+ const DEFAULT_NAMESPACE$7 = 'Settings.TransferModal';
48613
48633
  /**
48614
48634
  * `val-transfer-ownership-modal` — selector de miembro para transferir la
48615
48635
  * propiedad de la organización. Promovido desde `showcase` bajo el proceso de
@@ -48633,7 +48653,7 @@ class TransferOwnershipModalComponent {
48633
48653
  /** Lista de miembros transferibles (todos excepto el owner actual). */
48634
48654
  this.members = [];
48635
48655
  /** Namespace i18n con que la vista resuelve sus textos. */
48636
- this.i18nNamespace = DEFAULT_NAMESPACE$5;
48656
+ this.i18nNamespace = DEFAULT_NAMESPACE$7;
48637
48657
  this.query = signal('');
48638
48658
  this.selectedId = signal('');
48639
48659
  this.filtered = computed(() => {
@@ -48645,8 +48665,8 @@ class TransferOwnershipModalComponent {
48645
48665
  m.userId.toLowerCase().includes(q));
48646
48666
  });
48647
48667
  this.noResultsLabel = computed(() => `${this.t('noResults')} "${this.query()}"`);
48648
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
48649
- this.i18n.registerContent(DEFAULT_NAMESPACE$5, TRANSFER_OWNERSHIP_MODAL_I18N);
48668
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
48669
+ this.i18n.registerContent(DEFAULT_NAMESPACE$7, TRANSFER_OWNERSHIP_MODAL_I18N);
48650
48670
  }
48651
48671
  }
48652
48672
  ngOnInit() {
@@ -48831,6 +48851,849 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
48831
48851
  type: Input
48832
48852
  }] } });
48833
48853
 
48854
+ /**
48855
+ * Defaults i18n (es/en) embebidos en `val-member-import-modal` (ADR-023).
48856
+ * Auto-registrados si el consumer no proveyó el namespace `Settings.ImportModal`.
48857
+ */
48858
+ const MEMBER_IMPORT_MODAL_I18N = {
48859
+ es: {
48860
+ title: 'Importar miembros',
48861
+ hint: 'Pega un CSV con columnas: email, nombre, rol (una fila por persona).',
48862
+ csvPlaceholder: 'email,nombre,rol\nana@x.com,Ana,editor\nbeto@x.com,Beto,viewer',
48863
+ rolesAvailable: 'Roles disponibles:',
48864
+ onConflictLabel: 'Si el email ya existe',
48865
+ conflictAssign: 'Asignar el rol',
48866
+ conflictSkip: 'Omitir',
48867
+ conflictError: 'Marcar error',
48868
+ sendActivation: 'Enviar email de activación a las cuentas nuevas',
48869
+ submit: 'Importar',
48870
+ submitting: 'Importando...',
48871
+ parseError: 'No se pudo interpretar el CSV. Revisa el formato.',
48872
+ noRows: 'Pega al menos una fila válida (email y rol).',
48873
+ summary: 'Resumen',
48874
+ created: 'creados',
48875
+ assigned: 'asignados',
48876
+ skipped: 'omitidos',
48877
+ failed: 'con error',
48878
+ colEmail: 'Email',
48879
+ colStatus: 'Estado',
48880
+ colDetail: 'Detalle',
48881
+ activationSent: 'activación enviada',
48882
+ activationPending: 'activación pendiente',
48883
+ again: 'Importar otro lote',
48884
+ close: 'Cerrar',
48885
+ genericError: 'No se pudo completar la importación. Intenta de nuevo.',
48886
+ },
48887
+ en: {
48888
+ title: 'Import members',
48889
+ hint: 'Paste a CSV with columns: email, name, role (one row per person).',
48890
+ csvPlaceholder: 'email,name,role\nana@x.com,Ana,editor\nbeto@x.com,Beto,viewer',
48891
+ rolesAvailable: 'Available roles:',
48892
+ onConflictLabel: 'If the email already exists',
48893
+ conflictAssign: 'Assign the role',
48894
+ conflictSkip: 'Skip',
48895
+ conflictError: 'Mark as error',
48896
+ sendActivation: 'Send activation email to new accounts',
48897
+ submit: 'Import',
48898
+ submitting: 'Importing...',
48899
+ parseError: "Couldn't parse the CSV. Check the format.",
48900
+ noRows: 'Paste at least one valid row (email and role).',
48901
+ summary: 'Summary',
48902
+ created: 'created',
48903
+ assigned: 'assigned',
48904
+ skipped: 'skipped',
48905
+ failed: 'failed',
48906
+ colEmail: 'Email',
48907
+ colStatus: 'Status',
48908
+ colDetail: 'Detail',
48909
+ activationSent: 'activation sent',
48910
+ activationPending: 'activation pending',
48911
+ again: 'Import another batch',
48912
+ close: 'Close',
48913
+ genericError: "Couldn't complete the import. Try again.",
48914
+ },
48915
+ };
48916
+
48917
+ const DEFAULT_NAMESPACE$6 = 'Settings.ImportModal';
48918
+ /**
48919
+ * `val-member-import-modal` — panel de carga masiva de miembros (ADR-023 fase 6).
48920
+ *
48921
+ * El admin pega un CSV (`email,nombre,rol`); el modal lo parsea a filas y llama
48922
+ * `OrgService.importMembers`. Muestra el resultado por-fila (partial success):
48923
+ * `created` / `assigned` / `skipped` / `error`. Header canónico (Regla #5).
48924
+ *
48925
+ * Auto-registra defaults i18n (es/en) en `Settings.ImportModal`.
48926
+ */
48927
+ class MemberImportModalComponent {
48928
+ constructor() {
48929
+ this.i18n = inject(I18nService);
48930
+ this.orgService = inject(OrgService);
48931
+ /** Org destino del import. */
48932
+ this.orgId = '';
48933
+ /** Nombres de roles disponibles (para el hint). Opcional. */
48934
+ this.availableRoles = [];
48935
+ /** Namespace i18n. */
48936
+ this.i18nNamespace = DEFAULT_NAMESPACE$6;
48937
+ this.csv = '';
48938
+ this.onConflict = 'assignRole';
48939
+ this.sendActivation = true;
48940
+ this.submitting = signal(false);
48941
+ this.errorMsg = signal('');
48942
+ this.results = signal(null);
48943
+ this.rolesHint = computed(() => this.availableRoles.length ? `${this.t('rolesAvailable')} ${this.availableRoles.join(', ')}` : '');
48944
+ this.summaryLabel = computed(() => {
48945
+ const s = this.results()?.summary;
48946
+ if (!s)
48947
+ return this.t('summary');
48948
+ return `${this.t('summary')}: ${s.created} ${this.t('created')} · ${s.assigned} ${this.t('assigned')} · ${s.skipped} ${this.t('skipped')} · ${s.failed} ${this.t('failed')}`;
48949
+ });
48950
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$6)) {
48951
+ this.i18n.registerContent(DEFAULT_NAMESPACE$6, MEMBER_IMPORT_MODAL_I18N);
48952
+ }
48953
+ }
48954
+ ngOnInit() {
48955
+ if (!this.i18n.hasNamespace(this.i18nNamespace)) {
48956
+ this.i18n.registerContent(this.i18nNamespace, MEMBER_IMPORT_MODAL_I18N);
48957
+ }
48958
+ }
48959
+ /** Parsea el CSV pegado a filas {email,name,roleName}. Tolera header. */
48960
+ parseCsv() {
48961
+ const rows = [];
48962
+ for (const raw of this.csv.split(/\r?\n/)) {
48963
+ const line = raw.trim();
48964
+ if (!line)
48965
+ continue;
48966
+ const cols = line.split(',').map(c => c.trim());
48967
+ const email = (cols[0] ?? '').toLowerCase();
48968
+ // Saltar header (primera col "email") o líneas sin @.
48969
+ if (!email || !email.includes('@'))
48970
+ continue;
48971
+ rows.push({ email, name: cols[1] ?? '', roleName: cols[2] ?? '' });
48972
+ }
48973
+ return rows;
48974
+ }
48975
+ submit() {
48976
+ this.errorMsg.set('');
48977
+ const users = this.parseCsv();
48978
+ if (users.length === 0) {
48979
+ this.errorMsg.set(this.t('noRows'));
48980
+ return;
48981
+ }
48982
+ if (users.some(u => !u.roleName)) {
48983
+ this.errorMsg.set(this.t('parseError'));
48984
+ return;
48985
+ }
48986
+ this.submitting.set(true);
48987
+ this.orgService
48988
+ .importMembers(this.orgId, {
48989
+ users,
48990
+ onConflict: this.onConflict,
48991
+ sendActivation: this.sendActivation,
48992
+ })
48993
+ .subscribe({
48994
+ next: res => {
48995
+ this.submitting.set(false);
48996
+ this.results.set(res);
48997
+ // Refrescar lista del consumer si hubo altas/asignaciones efectivas.
48998
+ if (res.summary.created + res.summary.assigned > 0) {
48999
+ this.onSuccess?.();
49000
+ }
49001
+ },
49002
+ error: err => {
49003
+ this.submitting.set(false);
49004
+ this.errorMsg.set(err?.error?.message ?? this.t('genericError'));
49005
+ },
49006
+ });
49007
+ }
49008
+ statusLabel(r) {
49009
+ const base = this.t(r.status === 'error' ? 'failed' : r.status);
49010
+ if (r.status === 'created' && r.activation) {
49011
+ return `${base} · ${this.t(r.activation === 'sent' ? 'activationSent' : 'activationPending')}`;
49012
+ }
49013
+ return base;
49014
+ }
49015
+ reset() {
49016
+ this.csv = '';
49017
+ this.errorMsg.set('');
49018
+ this.results.set(null);
49019
+ }
49020
+ dismiss() {
49021
+ this._modalRef?.dismiss(null, 'cancel');
49022
+ }
49023
+ t(key) {
49024
+ return this.i18n.t(key, this.i18nNamespace);
49025
+ }
49026
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberImportModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
49027
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MemberImportModalComponent, isStandalone: true, selector: "val-member-import-modal", inputs: { orgId: "orgId", availableRoles: "availableRoles", onSuccess: "onSuccess", _modalRef: "_modalRef", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
49028
+ <ion-header>
49029
+ <ion-toolbar>
49030
+ <ion-buttons slot="end">
49031
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
49032
+ <strong>{{ t('close') }}</strong>
49033
+ </ion-button>
49034
+ </ion-buttons>
49035
+ </ion-toolbar>
49036
+ </ion-header>
49037
+
49038
+ <ion-content class="ion-padding">
49039
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
49040
+
49041
+ @if (!results()) {
49042
+ <val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
49043
+
49044
+ @if (rolesHint()) {
49045
+ <val-text [props]="{ content: rolesHint(), size: 'small', color: 'medium', bold: false }" />
49046
+ }
49047
+
49048
+ <ion-textarea
49049
+ class="import-csv"
49050
+ [autoGrow]="true"
49051
+ [rows]="6"
49052
+ [placeholder]="t('csvPlaceholder')"
49053
+ [(ngModel)]="csv"
49054
+ />
49055
+
49056
+ <ion-select
49057
+ class="import-conflict"
49058
+ label="{{ t('onConflictLabel') }}"
49059
+ labelPlacement="stacked"
49060
+ interface="popover"
49061
+ [(ngModel)]="onConflict"
49062
+ >
49063
+ <ion-select-option value="assignRole">{{ t('conflictAssign') }}</ion-select-option>
49064
+ <ion-select-option value="skip">{{ t('conflictSkip') }}</ion-select-option>
49065
+ <ion-select-option value="error">{{ t('conflictError') }}</ion-select-option>
49066
+ </ion-select>
49067
+
49068
+ <ion-checkbox class="import-activation" labelPlacement="end" [(ngModel)]="sendActivation">
49069
+ {{ t('sendActivation') }}
49070
+ </ion-checkbox>
49071
+
49072
+ @if (errorMsg()) {
49073
+ <p class="import-error">{{ errorMsg() }}</p>
49074
+ }
49075
+
49076
+ <div class="import-actions">
49077
+ <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49078
+ @if (submitting()) {
49079
+ <ion-spinner name="dots" />
49080
+ &nbsp;{{ t('submitting') }}
49081
+ } @else {
49082
+ {{ t('submit') }}
49083
+ }
49084
+ </ion-button>
49085
+ </div>
49086
+ } @else {
49087
+ <!-- Resultados -->
49088
+ <val-title [props]="{ content: summaryLabel(), size: 'large', color: '', bold: true }" />
49089
+ <div class="import-results">
49090
+ @for (r of results()!.results; track r.email) {
49091
+ <div class="import-row import-row--{{ r.status }}">
49092
+ <span class="import-row__email">{{ r.email }}</span>
49093
+ <span class="import-row__status">{{ statusLabel(r) }}</span>
49094
+ @if (r.reason) {
49095
+ <span class="import-row__reason">{{ r.reason }}</span>
49096
+ }
49097
+ </div>
49098
+ }
49099
+ </div>
49100
+ <div class="import-actions">
49101
+ <ion-button expand="block" fill="outline" color="dark" (click)="reset()">
49102
+ {{ t('again') }}
49103
+ </ion-button>
49104
+ </div>
49105
+ }
49106
+ </ion-content>
49107
+ `, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.import-csv{--background: var(--ion-color-light);border-radius:10px;margin-bottom:16px;font-family:monospace;font-size:13px}.import-conflict{margin-bottom:12px}.import-activation{display:block;margin-bottom:16px;font-size:14px}.import-error{margin:0 0 12px;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.import-actions{margin-top:8px}.import-results{display:flex;flex-direction:column;gap:6px;margin:12px 0}.import-row{display:grid;grid-template-columns:1fr auto;grid-row-gap:2px;align-items:center;padding:10px 12px;border-radius:8px;border-left:3px solid var(--ion-color-medium);background:var(--ion-color-light);font-size:14px}.import-row--created{border-left-color:var(--ion-color-success, #2dd36f)}.import-row--assigned{border-left-color:var(--ion-color-primary, #7026df)}.import-row--skipped{border-left-color:var(--ion-color-medium, #92949c)}.import-row--error{border-left-color:var(--ion-color-danger, #c0392b)}.import-row__email{font-weight:600;color:var(--ion-color-dark)}.import-row__status{text-align:right;color:var(--ion-color-medium-shade, #6b6e76)}.import-row__reason{grid-column:1 / -1;font-size:12px;color:var(--ion-color-danger, #c0392b)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
49108
+ }
49109
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberImportModalComponent, decorators: [{
49110
+ type: Component,
49111
+ args: [{ selector: 'val-member-import-modal', standalone: true, imports: [
49112
+ FormsModule,
49113
+ IonHeader,
49114
+ IonToolbar,
49115
+ IonButtons,
49116
+ IonButton,
49117
+ IonContent,
49118
+ IonTextarea,
49119
+ IonSelect,
49120
+ IonSelectOption,
49121
+ IonCheckbox,
49122
+ IonSpinner,
49123
+ DisplayComponent,
49124
+ TextComponent,
49125
+ TitleComponent,
49126
+ ], template: `
49127
+ <ion-header>
49128
+ <ion-toolbar>
49129
+ <ion-buttons slot="end">
49130
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
49131
+ <strong>{{ t('close') }}</strong>
49132
+ </ion-button>
49133
+ </ion-buttons>
49134
+ </ion-toolbar>
49135
+ </ion-header>
49136
+
49137
+ <ion-content class="ion-padding">
49138
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
49139
+
49140
+ @if (!results()) {
49141
+ <val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
49142
+
49143
+ @if (rolesHint()) {
49144
+ <val-text [props]="{ content: rolesHint(), size: 'small', color: 'medium', bold: false }" />
49145
+ }
49146
+
49147
+ <ion-textarea
49148
+ class="import-csv"
49149
+ [autoGrow]="true"
49150
+ [rows]="6"
49151
+ [placeholder]="t('csvPlaceholder')"
49152
+ [(ngModel)]="csv"
49153
+ />
49154
+
49155
+ <ion-select
49156
+ class="import-conflict"
49157
+ label="{{ t('onConflictLabel') }}"
49158
+ labelPlacement="stacked"
49159
+ interface="popover"
49160
+ [(ngModel)]="onConflict"
49161
+ >
49162
+ <ion-select-option value="assignRole">{{ t('conflictAssign') }}</ion-select-option>
49163
+ <ion-select-option value="skip">{{ t('conflictSkip') }}</ion-select-option>
49164
+ <ion-select-option value="error">{{ t('conflictError') }}</ion-select-option>
49165
+ </ion-select>
49166
+
49167
+ <ion-checkbox class="import-activation" labelPlacement="end" [(ngModel)]="sendActivation">
49168
+ {{ t('sendActivation') }}
49169
+ </ion-checkbox>
49170
+
49171
+ @if (errorMsg()) {
49172
+ <p class="import-error">{{ errorMsg() }}</p>
49173
+ }
49174
+
49175
+ <div class="import-actions">
49176
+ <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49177
+ @if (submitting()) {
49178
+ <ion-spinner name="dots" />
49179
+ &nbsp;{{ t('submitting') }}
49180
+ } @else {
49181
+ {{ t('submit') }}
49182
+ }
49183
+ </ion-button>
49184
+ </div>
49185
+ } @else {
49186
+ <!-- Resultados -->
49187
+ <val-title [props]="{ content: summaryLabel(), size: 'large', color: '', bold: true }" />
49188
+ <div class="import-results">
49189
+ @for (r of results()!.results; track r.email) {
49190
+ <div class="import-row import-row--{{ r.status }}">
49191
+ <span class="import-row__email">{{ r.email }}</span>
49192
+ <span class="import-row__status">{{ statusLabel(r) }}</span>
49193
+ @if (r.reason) {
49194
+ <span class="import-row__reason">{{ r.reason }}</span>
49195
+ }
49196
+ </div>
49197
+ }
49198
+ </div>
49199
+ <div class="import-actions">
49200
+ <ion-button expand="block" fill="outline" color="dark" (click)="reset()">
49201
+ {{ t('again') }}
49202
+ </ion-button>
49203
+ </div>
49204
+ }
49205
+ </ion-content>
49206
+ `, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.import-csv{--background: var(--ion-color-light);border-radius:10px;margin-bottom:16px;font-family:monospace;font-size:13px}.import-conflict{margin-bottom:12px}.import-activation{display:block;margin-bottom:16px;font-size:14px}.import-error{margin:0 0 12px;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.import-actions{margin-top:8px}.import-results{display:flex;flex-direction:column;gap:6px;margin:12px 0}.import-row{display:grid;grid-template-columns:1fr auto;grid-row-gap:2px;align-items:center;padding:10px 12px;border-radius:8px;border-left:3px solid var(--ion-color-medium);background:var(--ion-color-light);font-size:14px}.import-row--created{border-left-color:var(--ion-color-success, #2dd36f)}.import-row--assigned{border-left-color:var(--ion-color-primary, #7026df)}.import-row--skipped{border-left-color:var(--ion-color-medium, #92949c)}.import-row--error{border-left-color:var(--ion-color-danger, #c0392b)}.import-row__email{font-weight:600;color:var(--ion-color-dark)}.import-row__status{text-align:right;color:var(--ion-color-medium-shade, #6b6e76)}.import-row__reason{grid-column:1 / -1;font-size:12px;color:var(--ion-color-danger, #c0392b)}\n"] }]
49207
+ }], ctorParameters: () => [], propDecorators: { orgId: [{
49208
+ type: Input
49209
+ }], availableRoles: [{
49210
+ type: Input
49211
+ }], onSuccess: [{
49212
+ type: Input
49213
+ }], _modalRef: [{
49214
+ type: Input
49215
+ }], i18nNamespace: [{
49216
+ type: Input
49217
+ }] } });
49218
+
49219
+ /**
49220
+ * Cliente HTTP de Client API Keys org-scopeadas (ADR-023). La org activa se
49221
+ * resuelve en backend desde el JWT, así que el path no la incluye.
49222
+ */
49223
+ class ApiKeyService {
49224
+ constructor(config, http) {
49225
+ this.config = config;
49226
+ this.http = http;
49227
+ }
49228
+ get baseUrl() {
49229
+ return `${this.config.apiUrl}/org/api-keys`;
49230
+ }
49231
+ /** Lista las API keys de la org activa. */
49232
+ list() {
49233
+ return this.http.get(`${this.baseUrl}/`).pipe(map$1(r => r.keys ?? []));
49234
+ }
49235
+ /** Crea una API key. La respuesta incluye el secreto (se muestra UNA vez). */
49236
+ create(req) {
49237
+ return this.http.post(`${this.baseUrl}/`, req);
49238
+ }
49239
+ /** Revoca (elimina) una API key por id. */
49240
+ revoke(keyId) {
49241
+ return this.http.delete(`${this.baseUrl}/${keyId}`);
49242
+ }
49243
+ /** Catálogo de permisos asignables a una key. */
49244
+ availablePermissions() {
49245
+ return this.http
49246
+ .get(`${this.baseUrl}/permissions`)
49247
+ .pipe(map$1(r => r.permissions ?? []));
49248
+ }
49249
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeyService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$3.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
49250
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeyService, providedIn: 'root' }); }
49251
+ }
49252
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeyService, decorators: [{
49253
+ type: Injectable,
49254
+ args: [{ providedIn: 'root' }]
49255
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
49256
+ type: Inject,
49257
+ args: [VALTECH_AUTH_CONFIG]
49258
+ }] }, { type: i1$3.HttpClient }] });
49259
+
49260
+ /**
49261
+ * Defaults i18n (es/en) embebidos en `val-api-keys-modal` (ADR-023).
49262
+ * Auto-registrados si el consumer no proveyó el namespace `Settings.ApiKeysModal`.
49263
+ */
49264
+ const API_KEYS_MODAL_I18N = {
49265
+ es: {
49266
+ title: 'Client API Keys',
49267
+ hint: 'Llaves M2M org-scopeadas para integraciones (p. ej. importar miembros).',
49268
+ empty: 'Aún no hay API keys en esta organización.',
49269
+ newKey: 'Nueva API key',
49270
+ nameLabel: 'Nombre',
49271
+ namePlaceholder: 'Ej: Integración RRHH',
49272
+ permissionsLabel: 'Permisos',
49273
+ expiresLabel: 'Expira en (días, opcional)',
49274
+ create: 'Crear key',
49275
+ creating: 'Creando...',
49276
+ cancel: 'Cancelar',
49277
+ revoke: 'Revocar',
49278
+ revokeConfirm: '¿Revocar esta API key? Las integraciones que la usen dejarán de funcionar.',
49279
+ secretTitle: 'Copia tu API key ahora',
49280
+ secretWarning: 'Este secreto se muestra una sola vez. Guárdalo en un lugar seguro.',
49281
+ copy: 'Copiar',
49282
+ copied: 'Copiada',
49283
+ done: 'Listo',
49284
+ statusActive: 'activa',
49285
+ lastUsed: 'Último uso',
49286
+ never: 'nunca',
49287
+ permsCount: 'permisos',
49288
+ noName: 'El nombre es obligatorio.',
49289
+ noPerms: 'Selecciona al menos un permiso.',
49290
+ close: 'Cerrar',
49291
+ genericError: 'No se pudo completar la operación. Intenta de nuevo.',
49292
+ },
49293
+ en: {
49294
+ title: 'Client API Keys',
49295
+ hint: 'Org-scoped M2M keys for integrations (e.g. member import).',
49296
+ empty: 'No API keys in this organization yet.',
49297
+ newKey: 'New API key',
49298
+ nameLabel: 'Name',
49299
+ namePlaceholder: 'E.g: HR integration',
49300
+ permissionsLabel: 'Permissions',
49301
+ expiresLabel: 'Expires in (days, optional)',
49302
+ create: 'Create key',
49303
+ creating: 'Creating...',
49304
+ cancel: 'Cancel',
49305
+ revoke: 'Revoke',
49306
+ revokeConfirm: 'Revoke this API key? Integrations using it will stop working.',
49307
+ secretTitle: 'Copy your API key now',
49308
+ secretWarning: 'This secret is shown only once. Store it somewhere safe.',
49309
+ copy: 'Copy',
49310
+ copied: 'Copied',
49311
+ done: 'Done',
49312
+ statusActive: 'active',
49313
+ lastUsed: 'Last used',
49314
+ never: 'never',
49315
+ permsCount: 'permissions',
49316
+ noName: 'Name is required.',
49317
+ noPerms: 'Select at least one permission.',
49318
+ close: 'Close',
49319
+ genericError: "Couldn't complete the operation. Try again.",
49320
+ },
49321
+ };
49322
+
49323
+ const DEFAULT_NAMESPACE$5 = 'Settings.ApiKeysModal';
49324
+ /**
49325
+ * `val-api-keys-modal` — gestión de Client API Keys org-scopeadas (ADR-023 fase 6).
49326
+ *
49327
+ * Lista las keys de la org, permite crear (mostrando el secreto UNA vez con copia)
49328
+ * y revocar. Pensado para habilitar integraciones M2M del import de miembros, pero
49329
+ * sirve a cualquier permiso del catálogo. Header canónico (Regla #5).
49330
+ */
49331
+ class ApiKeysModalComponent {
49332
+ constructor() {
49333
+ this.i18n = inject(I18nService);
49334
+ this.apiKeys = inject(ApiKeyService);
49335
+ /** Namespace i18n. */
49336
+ this.i18nNamespace = DEFAULT_NAMESPACE$5;
49337
+ this.keys = signal([]);
49338
+ this.availablePermissions = signal([]);
49339
+ this.loading = signal(true);
49340
+ this.showCreate = signal(false);
49341
+ this.submitting = signal(false);
49342
+ this.errorMsg = signal('');
49343
+ this.secret = signal(null);
49344
+ this.copied = signal(false);
49345
+ this.name = '';
49346
+ this.expiresInDays = null;
49347
+ this.selectedPerms = signal(new Set());
49348
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
49349
+ this.i18n.registerContent(DEFAULT_NAMESPACE$5, API_KEYS_MODAL_I18N);
49350
+ }
49351
+ }
49352
+ ngOnInit() {
49353
+ if (!this.i18n.hasNamespace(this.i18nNamespace)) {
49354
+ this.i18n.registerContent(this.i18nNamespace, API_KEYS_MODAL_I18N);
49355
+ }
49356
+ this.refresh();
49357
+ this.apiKeys.availablePermissions().subscribe({
49358
+ next: perms => this.availablePermissions.set(perms),
49359
+ error: () => { },
49360
+ });
49361
+ }
49362
+ refresh() {
49363
+ this.loading.set(true);
49364
+ this.apiKeys.list().subscribe({
49365
+ next: keys => {
49366
+ this.keys.set(keys);
49367
+ this.loading.set(false);
49368
+ },
49369
+ error: () => this.loading.set(false),
49370
+ });
49371
+ }
49372
+ openCreate() {
49373
+ this.errorMsg.set('');
49374
+ this.name = '';
49375
+ this.expiresInDays = null;
49376
+ this.selectedPerms.set(new Set());
49377
+ this.showCreate.set(true);
49378
+ }
49379
+ togglePerm(perm, ev) {
49380
+ const checked = ev.detail?.checked ?? false;
49381
+ const next = new Set(this.selectedPerms());
49382
+ if (checked) {
49383
+ next.add(perm);
49384
+ }
49385
+ else {
49386
+ next.delete(perm);
49387
+ }
49388
+ this.selectedPerms.set(next);
49389
+ }
49390
+ submit() {
49391
+ this.errorMsg.set('');
49392
+ if (!this.name.trim()) {
49393
+ this.errorMsg.set(this.t('noName'));
49394
+ return;
49395
+ }
49396
+ const permissions = Array.from(this.selectedPerms());
49397
+ if (permissions.length === 0) {
49398
+ this.errorMsg.set(this.t('noPerms'));
49399
+ return;
49400
+ }
49401
+ this.submitting.set(true);
49402
+ this.apiKeys
49403
+ .create({
49404
+ name: this.name.trim(),
49405
+ permissions,
49406
+ expiresInDays: this.expiresInDays && this.expiresInDays > 0 ? this.expiresInDays : undefined,
49407
+ })
49408
+ .subscribe({
49409
+ next: res => {
49410
+ this.submitting.set(false);
49411
+ this.showCreate.set(false);
49412
+ this.secret.set(res.key);
49413
+ },
49414
+ error: err => {
49415
+ this.submitting.set(false);
49416
+ this.errorMsg.set(err?.error?.message ?? this.t('genericError'));
49417
+ },
49418
+ });
49419
+ }
49420
+ revoke(key) {
49421
+ if (!confirm(this.t('revokeConfirm')))
49422
+ return;
49423
+ this.apiKeys.revoke(key.keyId).subscribe({
49424
+ next: () => this.refresh(),
49425
+ error: err => this.errorMsg.set(err?.error?.message ?? this.t('genericError')),
49426
+ });
49427
+ }
49428
+ async copy(secret) {
49429
+ try {
49430
+ await navigator.clipboard.writeText(secret);
49431
+ this.copied.set(true);
49432
+ }
49433
+ catch {
49434
+ // clipboard no disponible — el user puede seleccionar el texto manualmente
49435
+ }
49436
+ }
49437
+ finishSecret() {
49438
+ this.secret.set(null);
49439
+ this.copied.set(false);
49440
+ this.refresh();
49441
+ }
49442
+ dismiss() {
49443
+ this._modalRef?.dismiss(null, 'cancel');
49444
+ }
49445
+ t(key) {
49446
+ return this.i18n.t(key, this.i18nNamespace);
49447
+ }
49448
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeysModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
49449
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ApiKeysModalComponent, isStandalone: true, selector: "val-api-keys-modal", inputs: { _modalRef: "_modalRef", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
49450
+ <ion-header>
49451
+ <ion-toolbar>
49452
+ <ion-buttons slot="end">
49453
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
49454
+ <strong>{{ t('close') }}</strong>
49455
+ </ion-button>
49456
+ </ion-buttons>
49457
+ </ion-toolbar>
49458
+ </ion-header>
49459
+
49460
+ <ion-content class="ion-padding">
49461
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
49462
+ <val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
49463
+
49464
+ @if (secret(); as s) {
49465
+ <!-- Secreto recién creado: mostrar UNA vez -->
49466
+ <div class="apikey-secret">
49467
+ <val-title [props]="{ content: t('secretTitle'), size: 'large', color: '', bold: true }" />
49468
+ <p class="apikey-secret__warn">{{ t('secretWarning') }}</p>
49469
+ <div class="apikey-secret__box">
49470
+ <code>{{ s.secret }}</code>
49471
+ </div>
49472
+ <div class="apikey-actions">
49473
+ <ion-button expand="block" color="dark" (click)="copy(s.secret)">
49474
+ {{ copied() ? t('copied') : t('copy') }}
49475
+ </ion-button>
49476
+ <ion-button expand="block" fill="outline" color="dark" (click)="finishSecret()">
49477
+ {{ t('done') }}
49478
+ </ion-button>
49479
+ </div>
49480
+ </div>
49481
+ } @else {
49482
+ @if (loading()) {
49483
+ <ion-spinner name="dots" />
49484
+ } @else {
49485
+ <!-- Lista -->
49486
+ @if (keys().length === 0) {
49487
+ <val-text [props]="{ content: t('empty'), size: 'medium', color: 'medium', bold: false }" />
49488
+ } @else {
49489
+ <div class="apikey-list">
49490
+ @for (k of keys(); track k.keyId) {
49491
+ <div class="apikey-row">
49492
+ <div class="apikey-row__main">
49493
+ <span class="apikey-row__name">{{ k.name }}</span>
49494
+ <span class="apikey-row__meta">
49495
+ {{ k.permissions.length }} {{ t('permsCount') }} · {{ t('lastUsed') }}:
49496
+ {{ k.lastUsedAt || t('never') }}
49497
+ </span>
49498
+ </div>
49499
+ <ion-button fill="clear" color="danger" size="small" (click)="revoke(k)">
49500
+ {{ t('revoke') }}
49501
+ </ion-button>
49502
+ </div>
49503
+ }
49504
+ </div>
49505
+ }
49506
+
49507
+ @if (errorMsg()) {
49508
+ <p class="apikey-error">{{ errorMsg() }}</p>
49509
+ }
49510
+
49511
+ <!-- Crear -->
49512
+ @if (showCreate()) {
49513
+ <div class="apikey-create">
49514
+ <ion-input
49515
+ label="{{ t('nameLabel') }}"
49516
+ labelPlacement="stacked"
49517
+ [placeholder]="t('namePlaceholder')"
49518
+ [(ngModel)]="name"
49519
+ />
49520
+ <p class="apikey-create__label">{{ t('permissionsLabel') }}</p>
49521
+ <div class="apikey-perms">
49522
+ @for (p of availablePermissions(); track p) {
49523
+ <ion-checkbox
49524
+ labelPlacement="end"
49525
+ [checked]="selectedPerms().has(p)"
49526
+ (ionChange)="togglePerm(p, $event)"
49527
+ >
49528
+ {{ p }}
49529
+ </ion-checkbox>
49530
+ }
49531
+ </div>
49532
+ <ion-input
49533
+ label="{{ t('expiresLabel') }}"
49534
+ labelPlacement="stacked"
49535
+ type="number"
49536
+ [(ngModel)]="expiresInDays"
49537
+ />
49538
+ <div class="apikey-actions">
49539
+ <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49540
+ @if (submitting()) {
49541
+ <ion-spinner name="dots" />&nbsp;{{ t('creating') }}
49542
+ } @else {
49543
+ {{ t('create') }}
49544
+ }
49545
+ </ion-button>
49546
+ <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49547
+ {{ t('cancel') }}
49548
+ </ion-button>
49549
+ </div>
49550
+ </div>
49551
+ } @else {
49552
+ <div class="apikey-actions">
49553
+ <ion-button expand="block" color="primary" (click)="openCreate()">
49554
+ {{ t('newKey') }}
49555
+ </ion-button>
49556
+ </div>
49557
+ }
49558
+ }
49559
+ }
49560
+ </ion-content>
49561
+ `, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.apikey-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.apikey-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:10px;border:1.5px solid var(--ion-color-light)}.apikey-row__main{display:flex;flex-direction:column}.apikey-row__name{font-weight:600;color:var(--ion-color-dark)}.apikey-row__meta{font-size:12px;color:var(--ion-color-medium-shade, #6b6e76)}.apikey-create{margin-top:8px;padding:16px;border-radius:12px;background:var(--ion-color-light)}.apikey-create__label{margin:12px 0 4px;font-size:13px;font-weight:600;color:var(--ion-color-dark)}.apikey-perms{display:flex;flex-direction:column;gap:6px;margin-bottom:12px;font-size:13px}.apikey-actions{margin-top:16px;display:flex;flex-direction:column;gap:8px}.apikey-error{margin:8px 0;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.apikey-secret__warn{font-size:.875rem;color:var(--ion-color-warning-shade, #b88600);margin:0 0 12px}.apikey-secret__box{padding:12px;border-radius:8px;background:var(--ion-color-dark);color:#fff;word-break:break-all;font-family:monospace;font-size:13px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
49562
+ }
49563
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeysModalComponent, decorators: [{
49564
+ type: Component,
49565
+ args: [{ selector: 'val-api-keys-modal', standalone: true, imports: [
49566
+ FormsModule,
49567
+ IonHeader,
49568
+ IonToolbar,
49569
+ IonButtons,
49570
+ IonButton,
49571
+ IonContent,
49572
+ IonInput,
49573
+ IonCheckbox,
49574
+ IonSpinner,
49575
+ DisplayComponent,
49576
+ TextComponent,
49577
+ TitleComponent,
49578
+ ], template: `
49579
+ <ion-header>
49580
+ <ion-toolbar>
49581
+ <ion-buttons slot="end">
49582
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
49583
+ <strong>{{ t('close') }}</strong>
49584
+ </ion-button>
49585
+ </ion-buttons>
49586
+ </ion-toolbar>
49587
+ </ion-header>
49588
+
49589
+ <ion-content class="ion-padding">
49590
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
49591
+ <val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
49592
+
49593
+ @if (secret(); as s) {
49594
+ <!-- Secreto recién creado: mostrar UNA vez -->
49595
+ <div class="apikey-secret">
49596
+ <val-title [props]="{ content: t('secretTitle'), size: 'large', color: '', bold: true }" />
49597
+ <p class="apikey-secret__warn">{{ t('secretWarning') }}</p>
49598
+ <div class="apikey-secret__box">
49599
+ <code>{{ s.secret }}</code>
49600
+ </div>
49601
+ <div class="apikey-actions">
49602
+ <ion-button expand="block" color="dark" (click)="copy(s.secret)">
49603
+ {{ copied() ? t('copied') : t('copy') }}
49604
+ </ion-button>
49605
+ <ion-button expand="block" fill="outline" color="dark" (click)="finishSecret()">
49606
+ {{ t('done') }}
49607
+ </ion-button>
49608
+ </div>
49609
+ </div>
49610
+ } @else {
49611
+ @if (loading()) {
49612
+ <ion-spinner name="dots" />
49613
+ } @else {
49614
+ <!-- Lista -->
49615
+ @if (keys().length === 0) {
49616
+ <val-text [props]="{ content: t('empty'), size: 'medium', color: 'medium', bold: false }" />
49617
+ } @else {
49618
+ <div class="apikey-list">
49619
+ @for (k of keys(); track k.keyId) {
49620
+ <div class="apikey-row">
49621
+ <div class="apikey-row__main">
49622
+ <span class="apikey-row__name">{{ k.name }}</span>
49623
+ <span class="apikey-row__meta">
49624
+ {{ k.permissions.length }} {{ t('permsCount') }} · {{ t('lastUsed') }}:
49625
+ {{ k.lastUsedAt || t('never') }}
49626
+ </span>
49627
+ </div>
49628
+ <ion-button fill="clear" color="danger" size="small" (click)="revoke(k)">
49629
+ {{ t('revoke') }}
49630
+ </ion-button>
49631
+ </div>
49632
+ }
49633
+ </div>
49634
+ }
49635
+
49636
+ @if (errorMsg()) {
49637
+ <p class="apikey-error">{{ errorMsg() }}</p>
49638
+ }
49639
+
49640
+ <!-- Crear -->
49641
+ @if (showCreate()) {
49642
+ <div class="apikey-create">
49643
+ <ion-input
49644
+ label="{{ t('nameLabel') }}"
49645
+ labelPlacement="stacked"
49646
+ [placeholder]="t('namePlaceholder')"
49647
+ [(ngModel)]="name"
49648
+ />
49649
+ <p class="apikey-create__label">{{ t('permissionsLabel') }}</p>
49650
+ <div class="apikey-perms">
49651
+ @for (p of availablePermissions(); track p) {
49652
+ <ion-checkbox
49653
+ labelPlacement="end"
49654
+ [checked]="selectedPerms().has(p)"
49655
+ (ionChange)="togglePerm(p, $event)"
49656
+ >
49657
+ {{ p }}
49658
+ </ion-checkbox>
49659
+ }
49660
+ </div>
49661
+ <ion-input
49662
+ label="{{ t('expiresLabel') }}"
49663
+ labelPlacement="stacked"
49664
+ type="number"
49665
+ [(ngModel)]="expiresInDays"
49666
+ />
49667
+ <div class="apikey-actions">
49668
+ <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49669
+ @if (submitting()) {
49670
+ <ion-spinner name="dots" />&nbsp;{{ t('creating') }}
49671
+ } @else {
49672
+ {{ t('create') }}
49673
+ }
49674
+ </ion-button>
49675
+ <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49676
+ {{ t('cancel') }}
49677
+ </ion-button>
49678
+ </div>
49679
+ </div>
49680
+ } @else {
49681
+ <div class="apikey-actions">
49682
+ <ion-button expand="block" color="primary" (click)="openCreate()">
49683
+ {{ t('newKey') }}
49684
+ </ion-button>
49685
+ </div>
49686
+ }
49687
+ }
49688
+ }
49689
+ </ion-content>
49690
+ `, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.apikey-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.apikey-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:10px;border:1.5px solid var(--ion-color-light)}.apikey-row__main{display:flex;flex-direction:column}.apikey-row__name{font-weight:600;color:var(--ion-color-dark)}.apikey-row__meta{font-size:12px;color:var(--ion-color-medium-shade, #6b6e76)}.apikey-create{margin-top:8px;padding:16px;border-radius:12px;background:var(--ion-color-light)}.apikey-create__label{margin:12px 0 4px;font-size:13px;font-weight:600;color:var(--ion-color-dark)}.apikey-perms{display:flex;flex-direction:column;gap:6px;margin-bottom:12px;font-size:13px}.apikey-actions{margin-top:16px;display:flex;flex-direction:column;gap:8px}.apikey-error{margin:8px 0;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.apikey-secret__warn{font-size:.875rem;color:var(--ion-color-warning-shade, #b88600);margin:0 0 12px}.apikey-secret__box{padding:12px;border-radius:8px;background:var(--ion-color-dark);color:#fff;word-break:break-all;font-family:monospace;font-size:13px}\n"] }]
49691
+ }], ctorParameters: () => [], propDecorators: { _modalRef: [{
49692
+ type: Input
49693
+ }], i18nNamespace: [{
49694
+ type: Input
49695
+ }] } });
49696
+
48834
49697
  /**
48835
49698
  * Defaults i18n (es/en) embebidos en `val-organization-view`. Auto-registrados en
48836
49699
  * el constructor del componente si el consumer no proveyó el namespace
@@ -48886,6 +49749,16 @@ const ORGANIZATION_VIEW_I18N = {
48886
49749
  inviteCtaTitle: '¿Deseas agregar a alguien?',
48887
49750
  inviteCtaHint: 'Invita a usuarios a unirse a tu organización.',
48888
49751
  inviteOpen: 'Invitar',
49752
+ importOpen: 'Importar',
49753
+ apiKeysOpen: 'Gestionar',
49754
+ apiKeysSectionTitle: 'Integraciones (API Keys)',
49755
+ apiKeysSectionHint: 'También puedes operar tu organización vía API: crea una Client API Key org-scopeada para integraciones máquina-a-máquina (por ejemplo, dar de alta miembros en masa desde tu propio sistema).',
49756
+ apiKeysMoreInfo: 'Más información',
49757
+ orgsMoreInfo: 'Más información',
49758
+ orgsInfoTitle: '¿Qué son las organizaciones?',
49759
+ orgsInfoSubtitle: 'Todo lo que necesitas saber',
49760
+ orgsInfoClose: 'Cerrar',
49761
+ orgsInfoContent: 'Las organizaciones te permiten separar tus proyectos y datos por empresa, equipo o contexto. Puedes pertenecer a varias organizaciones y cambiar entre ellas en cualquier momento.\n\nCada organización tiene sus propios miembros, roles y permisos. La organización activa determina qué datos ves y qué acciones puedes realizar.\n\nPuedes crear una organización nueva o unirte a una existente mediante invitación.',
48889
49762
  viewMemberCta: 'Ver miembro',
48890
49763
  roleViewer: 'Visor',
48891
49764
  roleEditor: 'Editor',
@@ -48955,6 +49828,16 @@ const ORGANIZATION_VIEW_I18N = {
48955
49828
  inviteCtaTitle: 'Want to add someone?',
48956
49829
  inviteCtaHint: 'Invite users to join your organization.',
48957
49830
  inviteOpen: 'Invite',
49831
+ importOpen: 'Import',
49832
+ apiKeysOpen: 'Manage',
49833
+ apiKeysSectionTitle: 'Integrations (API Keys)',
49834
+ apiKeysSectionHint: 'You can also operate your organization via API: create an org-scoped Client API Key for machine-to-machine integrations (for example, bulk-provisioning members from your own system).',
49835
+ apiKeysMoreInfo: 'More information',
49836
+ orgsMoreInfo: 'More information',
49837
+ orgsInfoTitle: 'What are organizations?',
49838
+ orgsInfoSubtitle: 'Everything you need to know',
49839
+ orgsInfoClose: 'Close',
49840
+ orgsInfoContent: 'Organizations let you separate your projects and data by company, team, or context. You can belong to multiple organizations and switch between them at any time.\n\nEach organization has its own members, roles, and permissions. The active organization determines what data you see and what actions you can take.\n\nYou can create a new organization or join an existing one via invitation.',
48958
49841
  viewMemberCta: 'View member',
48959
49842
  roleViewer: 'Viewer',
48960
49843
  roleEditor: 'Editor',
@@ -49068,6 +49951,22 @@ class OrganizationViewComponent {
49068
49951
  this.members = signal([]);
49069
49952
  this.membersLoading = signal(false);
49070
49953
  this.membersLoadError = signal(null);
49954
+ this.membersErrorState = computed(() => {
49955
+ this.i18n.lang();
49956
+ const err = this.membersLoadError();
49957
+ if (!err)
49958
+ return null;
49959
+ return createErrorStateProps(err, {
49960
+ title: { offline: this.tt('offlineTitle'), error: this.tt('errorTitle') },
49961
+ description: {
49962
+ offline: this.tt('offlineHint'),
49963
+ error: interpretError(err).message,
49964
+ },
49965
+ retryLabel: this.tt('retry'),
49966
+ onRetry: () => this.loadMembers(),
49967
+ retrying: this.membersLoading(),
49968
+ });
49969
+ });
49071
49970
  this.membersNextToken = signal(null);
49072
49971
  this.loadingMoreMembers = signal(false);
49073
49972
  this.showAllMembers = signal(false);
@@ -49154,6 +50053,22 @@ class OrganizationViewComponent {
49154
50053
  shape: 'round',
49155
50054
  type: 'button',
49156
50055
  }));
50056
+ this.openImportButtonProps = computed(() => ({
50057
+ text: this.tt('importOpen'),
50058
+ color: 'dark',
50059
+ fill: 'outline',
50060
+ size: 'default',
50061
+ shape: 'round',
50062
+ type: 'button',
50063
+ }));
50064
+ this.openApiKeysButtonProps = computed(() => ({
50065
+ text: this.tt('apiKeysOpen'),
50066
+ color: 'dark',
50067
+ fill: 'clear',
50068
+ size: 'default',
50069
+ shape: 'round',
50070
+ type: 'button',
50071
+ }));
49157
50072
  /** Guarda para detectar el primer disparo del effect (evita doble carga). */
49158
50073
  this.lastLoadedOrgId = null;
49159
50074
  const ns = this.ns;
@@ -49254,6 +50169,59 @@ class OrganizationViewComponent {
49254
50169
  backdropDismiss: true,
49255
50170
  });
49256
50171
  }
50172
+ onOpenImport() {
50173
+ void this.modalService.open({
50174
+ component: MemberImportModalComponent,
50175
+ componentProps: {
50176
+ orgId: this.activeOrgId(),
50177
+ availableRoles: this.availableRoles().map(r => r.name),
50178
+ onSuccess: () => {
50179
+ this.loadMembers();
50180
+ this.resolvedConfig().onMemberInvited?.();
50181
+ },
50182
+ },
50183
+ breakpoints: {
50184
+ breakpoints: [0, 0.92],
50185
+ initialBreakpoint: 0.92,
50186
+ showHandle: true,
50187
+ },
50188
+ backdropDismiss: true,
50189
+ });
50190
+ }
50191
+ onOpenApiKeys() {
50192
+ void this.modalService.open({
50193
+ component: ApiKeysModalComponent,
50194
+ breakpoints: {
50195
+ breakpoints: [0, 0.92],
50196
+ initialBreakpoint: 0.92,
50197
+ showHandle: true,
50198
+ },
50199
+ backdropDismiss: true,
50200
+ });
50201
+ }
50202
+ // "Más información" sobre organizaciones — mismo sheet explicativo que muestra
50203
+ // account-view sin org, pero disponible siempre (todo user tiene org por defecto).
50204
+ onOrgInfo() {
50205
+ void this.modalService.open({
50206
+ component: OrgInfoSheetComponent,
50207
+ componentProps: {
50208
+ title: this.tt('orgsInfoTitle'),
50209
+ subtitle: this.tt('orgsInfoSubtitle'),
50210
+ body: this.tt('orgsInfoContent'),
50211
+ closeLabel: this.tt('orgsInfoClose'),
50212
+ closeProps: {
50213
+ text: this.tt('orgsInfoClose'),
50214
+ fill: 'outline',
50215
+ color: 'dark',
50216
+ size: 'default',
50217
+ expand: 'block',
50218
+ type: 'button',
50219
+ },
50220
+ },
50221
+ breakpoints: { breakpoints: [0, 0.85], initialBreakpoint: 0.85, showHandle: true },
50222
+ backdropDismiss: true,
50223
+ });
50224
+ }
49257
50225
  onViewPermissions() {
49258
50226
  this.nav.navigateByUrl(this.resolvedConfig().viewPermissionsRoute);
49259
50227
  }
@@ -49335,6 +50303,7 @@ class OrganizationViewComponent {
49335
50303
  if (!orgId)
49336
50304
  return Promise.resolve();
49337
50305
  this.membersLoading.set(true);
50306
+ this.membersLoadError.set(null);
49338
50307
  this.membersNextToken.set(null);
49339
50308
  this.showAllMembers.set(false);
49340
50309
  return new Promise(resolve => {
@@ -49498,6 +50467,12 @@ class OrganizationViewComponent {
49498
50467
  }
49499
50468
  </div>
49500
50469
 
50470
+ <!-- "Más información" sobre organizaciones — disponible siempre (todo
50471
+ user tiene org por defecto y no ve el empty-state de account). -->
50472
+ <button class="org-more-info-link" type="button" (click)="onOrgInfo()">
50473
+ {{ tt('orgsMoreInfo') }}
50474
+ </button>
50475
+
49501
50476
  <div class="section-body">
49502
50477
  @if (loading()) {
49503
50478
  <val-skeleton-layout [props]="{ preset: 'detail', rows: 4 }" />
@@ -49545,6 +50520,8 @@ class OrganizationViewComponent {
49545
50520
  <div class="section-body">
49546
50521
  @if (membersLoading()) {
49547
50522
  <val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
50523
+ } @else if (membersErrorState()) {
50524
+ <val-empty-state [props]="membersErrorState()!" />
49548
50525
  } @else if (members().length === 0) {
49549
50526
  <val-text
49550
50527
  [props]="{
@@ -49637,10 +50614,25 @@ class OrganizationViewComponent {
49637
50614
  />
49638
50615
  </div>
49639
50616
  <val-button [props]="openInviteButtonProps()" (click)="onOpenInvite()" />
50617
+ <val-button [props]="openImportButtonProps()" (click)="onOpenImport()" />
49640
50618
  </div>
49641
50619
  </section>
49642
50620
  }
49643
50621
 
50622
+ <!-- API Keys: sección propia. Integraciones M2M (import de miembros, etc.). -->
50623
+ <section *valHasPermission="'api-keys:read'" class="settings-section" data-testid="org-apikeys-section">
50624
+ <div class="invite-cta">
50625
+ <div class="invite-cta__text">
50626
+ <val-title [props]="{ size: 'medium', color: 'dark', bold: true, content: tt('apiKeysSectionTitle') }" />
50627
+ <val-text [props]="{ size: 'medium', color: 'dark', bold: false, content: tt('apiKeysSectionHint') }" />
50628
+ <button class="org-more-info-link" type="button" (click)="onOpenApiKeys()">
50629
+ {{ tt('apiKeysMoreInfo') }}
50630
+ </button>
50631
+ </div>
50632
+ <val-button [props]="openApiKeysButtonProps()" (click)="onOpenApiKeys()" />
50633
+ </div>
50634
+ </section>
50635
+
49644
50636
  @if (resolvedConfig().showTransferOwnership && isOwner()) {
49645
50637
  <!-- Transfer ownership: solo owner (gating RBAC interno) -->
49646
50638
  <section class="settings-section" data-testid="org-transfer-section">
@@ -49731,7 +50723,7 @@ class OrganizationViewComponent {
49731
50723
  </section>
49732
50724
  }
49733
50725
  </div>
49734
- `, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.section-header-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.section-body{display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.row-actions--gap{display:flex;gap:8px}.invite-cta{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}.invite-cta__text{display:flex;flex-direction:column;gap:4px;flex:1}.org-info-card{border-radius:14px;background:var(--ion-color-light, #f4f5f8);overflow:hidden}:host-context(body.dark) .org-info-card,:host-context(html.ion-palette-dark) .org-info-card,:host-context([data-theme=\"dark\"]) .org-info-card{background:#ffffff0d}.org-info-field{padding:12px 16px;display:flex;flex-direction:column;gap:4px;border-bottom:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-field--full{width:100%}.org-info-field:last-child{border-bottom:none}.org-info-row{display:flex}.org-info-row .org-info-field{flex:1}.org-info-row .org-info-field:first-child{border-right:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.org-info-value{font-size:.95rem;font-weight:500;color:var(--ion-color-dark)}.org-info-value--muted{color:var(--ion-color-medium);font-weight:400}.plan-badge{display:inline-block;font-size:.78rem;font-weight:700;padding:3px 10px;border-radius:20px;width:fit-content}.plan-badge--free{background:var(--ion-color-light-shade, #d7d8da);color:var(--ion-color-dark)}.plan-badge--pro{background:var(--ion-color-primary);color:#fff}.plan-badge--enterprise{background:#f5c542;color:#222}.members-list{display:flex;flex-direction:column;gap:8px}.members-show-more{background:none;border:none;color:var(--ion-color-primary);font-size:14px;font-weight:500;cursor:pointer;padding:8px 0}.rbac-debug{opacity:.7}.rbac-debug__body{display:flex;flex-direction:column;gap:6px;font-family:monospace;font-size:.78rem}.rbac-debug__row{display:flex;gap:8px;align-items:flex-start}.rbac-debug__label{color:var(--ion-color-medium);min-width:72px;flex-shrink:0}.rbac-debug__value{color:var(--ion-color-dark);word-break:break-all}.rbac-debug__value--perms{color:var(--ion-color-medium)}\n"], dependencies: [{ kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "directive", type: HasPermissionDirective, selector: "[valHasPermission]", inputs: ["valHasPermission"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: MemberCardComponent, selector: "val-member-card", inputs: ["props"], outputs: ["onAction"] }] }); }
50726
+ `, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.org-more-info-link{background:none;border:none;padding:4px 0;margin-top:4px;font-size:13px;font-weight:600;color:var(--ion-color-primary, #7026df);cursor:pointer;text-align:left}.page-header{margin-bottom:16px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.section-header-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.section-body{display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.row-actions--gap{display:flex;gap:8px}.invite-cta{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}.invite-cta__text{display:flex;flex-direction:column;gap:4px;flex:1}.org-info-card{border-radius:14px;background:var(--ion-color-light, #f4f5f8);overflow:hidden}:host-context(body.dark) .org-info-card,:host-context(html.ion-palette-dark) .org-info-card,:host-context([data-theme=\"dark\"]) .org-info-card{background:#ffffff0d}.org-info-field{padding:12px 16px;display:flex;flex-direction:column;gap:4px;border-bottom:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-field--full{width:100%}.org-info-field:last-child{border-bottom:none}.org-info-row{display:flex}.org-info-row .org-info-field{flex:1}.org-info-row .org-info-field:first-child{border-right:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.org-info-value{font-size:.95rem;font-weight:500;color:var(--ion-color-dark)}.org-info-value--muted{color:var(--ion-color-medium);font-weight:400}.plan-badge{display:inline-block;font-size:.78rem;font-weight:700;padding:3px 10px;border-radius:20px;width:fit-content}.plan-badge--free{background:var(--ion-color-light-shade, #d7d8da);color:var(--ion-color-dark)}.plan-badge--pro{background:var(--ion-color-primary);color:#fff}.plan-badge--enterprise{background:#f5c542;color:#222}.members-list{display:flex;flex-direction:column;gap:8px}.members-show-more{background:none;border:none;color:var(--ion-color-primary);font-size:14px;font-weight:500;cursor:pointer;padding:8px 0}.rbac-debug{opacity:.7}.rbac-debug__body{display:flex;flex-direction:column;gap:6px;font-family:monospace;font-size:.78rem}.rbac-debug__row{display:flex;gap:8px;align-items:flex-start}.rbac-debug__label{color:var(--ion-color-medium);min-width:72px;flex-shrink:0}.rbac-debug__value{color:var(--ion-color-dark);word-break:break-all}.rbac-debug__value--perms{color:var(--ion-color-medium)}\n"], dependencies: [{ kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "directive", type: HasPermissionDirective, selector: "[valHasPermission]", inputs: ["valHasPermission"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: MemberCardComponent, selector: "val-member-card", inputs: ["props"], outputs: ["onAction"] }] }); }
49735
50727
  }
49736
50728
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrganizationViewComponent, decorators: [{
49737
50729
  type: Component,
@@ -49745,6 +50737,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49745
50737
  ButtonComponent,
49746
50738
  MemberCardComponent,
49747
50739
  TransferOwnershipModalComponent,
50740
+ MemberImportModalComponent,
50741
+ ApiKeysModalComponent,
50742
+ OrgInfoSheetComponent,
49748
50743
  ], template: `
49749
50744
  <div class="page">
49750
50745
  <header class="page-header">
@@ -49776,6 +50771,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49776
50771
  }
49777
50772
  </div>
49778
50773
 
50774
+ <!-- "Más información" sobre organizaciones — disponible siempre (todo
50775
+ user tiene org por defecto y no ve el empty-state de account). -->
50776
+ <button class="org-more-info-link" type="button" (click)="onOrgInfo()">
50777
+ {{ tt('orgsMoreInfo') }}
50778
+ </button>
50779
+
49779
50780
  <div class="section-body">
49780
50781
  @if (loading()) {
49781
50782
  <val-skeleton-layout [props]="{ preset: 'detail', rows: 4 }" />
@@ -49823,6 +50824,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49823
50824
  <div class="section-body">
49824
50825
  @if (membersLoading()) {
49825
50826
  <val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
50827
+ } @else if (membersErrorState()) {
50828
+ <val-empty-state [props]="membersErrorState()!" />
49826
50829
  } @else if (members().length === 0) {
49827
50830
  <val-text
49828
50831
  [props]="{
@@ -49915,10 +50918,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49915
50918
  />
49916
50919
  </div>
49917
50920
  <val-button [props]="openInviteButtonProps()" (click)="onOpenInvite()" />
50921
+ <val-button [props]="openImportButtonProps()" (click)="onOpenImport()" />
49918
50922
  </div>
49919
50923
  </section>
49920
50924
  }
49921
50925
 
50926
+ <!-- API Keys: sección propia. Integraciones M2M (import de miembros, etc.). -->
50927
+ <section *valHasPermission="'api-keys:read'" class="settings-section" data-testid="org-apikeys-section">
50928
+ <div class="invite-cta">
50929
+ <div class="invite-cta__text">
50930
+ <val-title [props]="{ size: 'medium', color: 'dark', bold: true, content: tt('apiKeysSectionTitle') }" />
50931
+ <val-text [props]="{ size: 'medium', color: 'dark', bold: false, content: tt('apiKeysSectionHint') }" />
50932
+ <button class="org-more-info-link" type="button" (click)="onOpenApiKeys()">
50933
+ {{ tt('apiKeysMoreInfo') }}
50934
+ </button>
50935
+ </div>
50936
+ <val-button [props]="openApiKeysButtonProps()" (click)="onOpenApiKeys()" />
50937
+ </div>
50938
+ </section>
50939
+
49922
50940
  @if (resolvedConfig().showTransferOwnership && isOwner()) {
49923
50941
  <!-- Transfer ownership: solo owner (gating RBAC interno) -->
49924
50942
  <section class="settings-section" data-testid="org-transfer-section">
@@ -50009,7 +51027,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
50009
51027
  </section>
50010
51028
  }
50011
51029
  </div>
50012
- `, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.section-header-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.section-body{display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.row-actions--gap{display:flex;gap:8px}.invite-cta{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}.invite-cta__text{display:flex;flex-direction:column;gap:4px;flex:1}.org-info-card{border-radius:14px;background:var(--ion-color-light, #f4f5f8);overflow:hidden}:host-context(body.dark) .org-info-card,:host-context(html.ion-palette-dark) .org-info-card,:host-context([data-theme=\"dark\"]) .org-info-card{background:#ffffff0d}.org-info-field{padding:12px 16px;display:flex;flex-direction:column;gap:4px;border-bottom:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-field--full{width:100%}.org-info-field:last-child{border-bottom:none}.org-info-row{display:flex}.org-info-row .org-info-field{flex:1}.org-info-row .org-info-field:first-child{border-right:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.org-info-value{font-size:.95rem;font-weight:500;color:var(--ion-color-dark)}.org-info-value--muted{color:var(--ion-color-medium);font-weight:400}.plan-badge{display:inline-block;font-size:.78rem;font-weight:700;padding:3px 10px;border-radius:20px;width:fit-content}.plan-badge--free{background:var(--ion-color-light-shade, #d7d8da);color:var(--ion-color-dark)}.plan-badge--pro{background:var(--ion-color-primary);color:#fff}.plan-badge--enterprise{background:#f5c542;color:#222}.members-list{display:flex;flex-direction:column;gap:8px}.members-show-more{background:none;border:none;color:var(--ion-color-primary);font-size:14px;font-weight:500;cursor:pointer;padding:8px 0}.rbac-debug{opacity:.7}.rbac-debug__body{display:flex;flex-direction:column;gap:6px;font-family:monospace;font-size:.78rem}.rbac-debug__row{display:flex;gap:8px;align-items:flex-start}.rbac-debug__label{color:var(--ion-color-medium);min-width:72px;flex-shrink:0}.rbac-debug__value{color:var(--ion-color-dark);word-break:break-all}.rbac-debug__value--perms{color:var(--ion-color-medium)}\n"] }]
51030
+ `, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.org-more-info-link{background:none;border:none;padding:4px 0;margin-top:4px;font-size:13px;font-weight:600;color:var(--ion-color-primary, #7026df);cursor:pointer;text-align:left}.page-header{margin-bottom:16px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.section-header-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.section-body{display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.row-actions--gap{display:flex;gap:8px}.invite-cta{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}.invite-cta__text{display:flex;flex-direction:column;gap:4px;flex:1}.org-info-card{border-radius:14px;background:var(--ion-color-light, #f4f5f8);overflow:hidden}:host-context(body.dark) .org-info-card,:host-context(html.ion-palette-dark) .org-info-card,:host-context([data-theme=\"dark\"]) .org-info-card{background:#ffffff0d}.org-info-field{padding:12px 16px;display:flex;flex-direction:column;gap:4px;border-bottom:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-field--full{width:100%}.org-info-field:last-child{border-bottom:none}.org-info-row{display:flex}.org-info-row .org-info-field{flex:1}.org-info-row .org-info-field:first-child{border-right:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.org-info-value{font-size:.95rem;font-weight:500;color:var(--ion-color-dark)}.org-info-value--muted{color:var(--ion-color-medium);font-weight:400}.plan-badge{display:inline-block;font-size:.78rem;font-weight:700;padding:3px 10px;border-radius:20px;width:fit-content}.plan-badge--free{background:var(--ion-color-light-shade, #d7d8da);color:var(--ion-color-dark)}.plan-badge--pro{background:var(--ion-color-primary);color:#fff}.plan-badge--enterprise{background:#f5c542;color:#222}.members-list{display:flex;flex-direction:column;gap:8px}.members-show-more{background:none;border:none;color:var(--ion-color-primary);font-size:14px;font-weight:500;cursor:pointer;padding:8px 0}.rbac-debug{opacity:.7}.rbac-debug__body{display:flex;flex-direction:column;gap:6px;font-family:monospace;font-size:.78rem}.rbac-debug__row{display:flex;gap:8px;align-items:flex-start}.rbac-debug__label{color:var(--ion-color-medium);min-width:72px;flex-shrink:0}.rbac-debug__value{color:var(--ion-color-dark);word-break:break-all}.rbac-debug__value--perms{color:var(--ion-color-medium)}\n"] }]
50013
51031
  }], ctorParameters: () => [], propDecorators: { config: [{
50014
51032
  type: Input
50015
51033
  }] } });
@@ -63222,5 +64240,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
63222
64240
  * Generated bundle index. Do not edit.
63223
64241
  */
63224
64242
 
63225
- 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, AboutViewComponent, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PermissionCatalogService, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
64243
+ 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, AboutViewComponent, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, ApiKeyService, ApiKeysModalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MemberImportModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PermissionCatalogService, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
63226
64244
  //# sourceMappingURL=valtech-components.mjs.map