valtech-components 2.0.994 → 2.0.996

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 (26) 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 +76 -1
  6. package/esm2022/lib/components/organisms/organization-view/organization-view.i18n.mjs +5 -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/org/org.service.mjs +8 -1
  10. package/esm2022/lib/services/org/types.mjs +1 -1
  11. package/esm2022/lib/version.mjs +2 -2
  12. package/esm2022/public-api.mjs +4 -1
  13. package/fesm2022/valtech-components.mjs +969 -42
  14. package/fesm2022/valtech-components.mjs.map +1 -1
  15. package/lib/components/organisms/api-keys-modal/api-keys-modal.component.d.ts +44 -0
  16. package/lib/components/organisms/api-keys-modal/api-keys-modal.i18n.d.ts +6 -0
  17. package/lib/components/organisms/member-import-modal/member-import-modal.component.d.ts +47 -0
  18. package/lib/components/organisms/member-import-modal/member-import-modal.i18n.d.ts +6 -0
  19. package/lib/components/organisms/organization-view/organization-view.component.d.ts +5 -0
  20. package/lib/services/apikeys/api-keys.service.d.ts +25 -0
  21. package/lib/services/apikeys/types.d.ts +46 -0
  22. package/lib/services/org/org.service.d.ts +6 -1
  23. package/lib/services/org/types.d.ts +30 -0
  24. package/lib/version.d.ts +1 -1
  25. package/package.json +1 -1
  26. 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.994';
57
+ const VERSION = '2.0.996';
58
58
 
59
59
  // Control de estado de refresco (singleton a nivel de módulo)
60
60
  let isRefreshing = false;
@@ -41750,7 +41750,7 @@ const PROFILE_VIEW_I18N = {
41750
41750
  const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
41751
41751
  const HANDLE_MIN = 3;
41752
41752
  const HANDLE_MAX = 20;
41753
- const DEFAULT_NAMESPACE$d = 'Settings.Profile';
41753
+ const DEFAULT_NAMESPACE$f = 'Settings.Profile';
41754
41754
  /**
41755
41755
  * `val-profile-view` — vista Perfil full-feature autocontenida (organism).
41756
41756
  *
@@ -41800,7 +41800,7 @@ class ProfileViewComponent {
41800
41800
  showPhone: merged.showPhone ?? true,
41801
41801
  showShareCta: merged.showShareCta ?? true,
41802
41802
  profileBaseUrl: merged.profileBaseUrl ?? '',
41803
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$d,
41803
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$f,
41804
41804
  onSaved: merged.onSaved,
41805
41805
  onAvatarUploaded: merged.onAvatarUploaded,
41806
41806
  };
@@ -42716,7 +42716,7 @@ const PREFERENCES_VIEW_I18N = {
42716
42716
  },
42717
42717
  };
42718
42718
 
42719
- const DEFAULT_NAMESPACE$c = 'Settings.General';
42719
+ const DEFAULT_NAMESPACE$e = 'Settings.General';
42720
42720
  const DEFAULT_LANGUAGES = ['es', 'en'];
42721
42721
  /**
42722
42722
  * `val-preferences-view` — vista de preferencias de UI cross-app (tema +
@@ -42753,7 +42753,7 @@ class PreferencesViewComponent {
42753
42753
  showLanguage: merged.showLanguage ?? true,
42754
42754
  showFontSize: merged.showFontSize ?? true,
42755
42755
  supportedLanguages: merged.supportedLanguages ?? DEFAULT_LANGUAGES,
42756
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$c,
42756
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$e,
42757
42757
  onThemeChanged: merged.onThemeChanged,
42758
42758
  onLanguageChanged: merged.onLanguageChanged,
42759
42759
  onFontSizeChanged: merged.onFontSizeChanged,
@@ -44565,7 +44565,7 @@ const SECURITY_VIEW_I18N = {
44565
44565
  };
44566
44566
 
44567
44567
  addIcons({ laptopOutline, phonePortraitOutline });
44568
- const DEFAULT_NAMESPACE$b = 'Settings.Security';
44568
+ const DEFAULT_NAMESPACE$d = 'Settings.Security';
44569
44569
  /**
44570
44570
  * `val-security-view` — vista Seguridad full-feature autocontenida (organism).
44571
44571
  *
@@ -44614,7 +44614,7 @@ class SecurityViewComponent {
44614
44614
  showChangeEmail: merged.showChangeEmail ?? true,
44615
44615
  showMfa: merged.showMfa ?? true,
44616
44616
  showPolicies: merged.showPolicies ?? true,
44617
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$b,
44617
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$d,
44618
44618
  handleDeepLinks: merged.handleDeepLinks ?? true,
44619
44619
  homeRoute: merged.homeRoute ?? '/app/home',
44620
44620
  onMfaCompleted: merged.onMfaCompleted,
@@ -45416,6 +45416,13 @@ class OrgService {
45416
45416
  inviteUser(orgId, req) {
45417
45417
  return this.http.post(`${this.baseUrl}/${orgId}/invite`, req);
45418
45418
  }
45419
+ /**
45420
+ * Crea/asigna miembros en masa (ADR-023). Resultado por-fila (partial success):
45421
+ * `created` (pre-aprovisionado), `assigned` (ya existía), `skipped`, `error`.
45422
+ */
45423
+ importMembers(orgId, req) {
45424
+ return this.http.post(`${this.baseUrl}/${orgId}/members/import`, req);
45425
+ }
45419
45426
  leaveOrg(orgId) {
45420
45427
  return this.http.post(`${this.baseUrl}/${orgId}/leave`, {});
45421
45428
  }
@@ -45520,7 +45527,7 @@ const CREATE_ORG_MODAL_I18N = {
45520
45527
  },
45521
45528
  };
45522
45529
 
45523
- const DEFAULT_NAMESPACE$a = 'CreateOrgModal';
45530
+ const DEFAULT_NAMESPACE$c = 'CreateOrgModal';
45524
45531
  /**
45525
45532
  * `val-create-org-modal` — modal de creación de organización (organism
45526
45533
  * compartido). Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por
@@ -45546,10 +45553,10 @@ class CreateOrgModalComponent {
45546
45553
  this.toast = inject(ToastService);
45547
45554
  this.errors = inject(ValtechErrorService);
45548
45555
  /** Namespace i18n con que la vista resuelve sus textos. */
45549
- this.i18nNamespace = DEFAULT_NAMESPACE$a;
45556
+ this.i18nNamespace = DEFAULT_NAMESPACE$c;
45550
45557
  this._busy = signal(false);
45551
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$a)) {
45552
- this.i18n.registerContent(DEFAULT_NAMESPACE$a, CREATE_ORG_MODAL_I18N);
45558
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$c)) {
45559
+ this.i18n.registerContent(DEFAULT_NAMESPACE$c, CREATE_ORG_MODAL_I18N);
45553
45560
  }
45554
45561
  this.formMeta = signal(this.buildFormMeta());
45555
45562
  effect(() => {
@@ -45785,7 +45792,7 @@ const DELETE_ACCOUNT_MODAL_I18N = {
45785
45792
  },
45786
45793
  };
45787
45794
 
45788
- const DEFAULT_NAMESPACE$9 = 'Settings.DeleteAccount';
45795
+ const DEFAULT_NAMESPACE$b = 'Settings.DeleteAccount';
45789
45796
  /**
45790
45797
  * `val-delete-account-modal` — modal de eliminación de cuenta (organism,
45791
45798
  * cuenta-específico). Promovido desde `showcase` bajo el proceso de ADR-021.
@@ -45811,7 +45818,7 @@ class DeleteAccountModalComponent {
45811
45818
  this.i18n = inject(I18nService);
45812
45819
  this.toast = inject(ToastService);
45813
45820
  /** Namespace i18n con que la vista resuelve sus textos. */
45814
- this.i18nNamespace = DEFAULT_NAMESPACE$9;
45821
+ this.i18nNamespace = DEFAULT_NAMESPACE$b;
45815
45822
  this.hasPassword = signal(null);
45816
45823
  this.codeSent = signal(false);
45817
45824
  this.formState = signal(ComponentStates.ENABLED);
@@ -45878,8 +45885,8 @@ class DeleteAccountModalComponent {
45878
45885
  ],
45879
45886
  actions: this.submitBtn('delete-code-submit', 'confirmCta'),
45880
45887
  }));
45881
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$9)) {
45882
- this.i18n.registerContent(DEFAULT_NAMESPACE$9, DELETE_ACCOUNT_MODAL_I18N);
45888
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$b)) {
45889
+ this.i18n.registerContent(DEFAULT_NAMESPACE$b, DELETE_ACCOUNT_MODAL_I18N);
45883
45890
  }
45884
45891
  }
45885
45892
  ngOnInit() {
@@ -46080,7 +46087,7 @@ const ORG_INFO_SHEET_I18N = {
46080
46087
  },
46081
46088
  };
46082
46089
 
46083
- const DEFAULT_NAMESPACE$8 = 'OrgInfoSheet';
46090
+ const DEFAULT_NAMESPACE$a = 'OrgInfoSheet';
46084
46091
  /**
46085
46092
  * `val-org-info-sheet` — sheet informativo "¿Qué son las organizaciones?".
46086
46093
  * Extraído del `OrgInfoSheetComponent` inline de `AccountPage` bajo el proceso de
@@ -46125,15 +46132,15 @@ class OrgInfoSheetComponent {
46125
46132
  text: this.closeProps.text || this.resolvedCloseLabel(),
46126
46133
  }));
46127
46134
  addIcons({ peopleOutline, swapHorizontalOutline, mailOutline });
46128
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$8)) {
46129
- this.i18n.registerContent(DEFAULT_NAMESPACE$8, ORG_INFO_SHEET_I18N);
46135
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$a)) {
46136
+ this.i18n.registerContent(DEFAULT_NAMESPACE$a, ORG_INFO_SHEET_I18N);
46130
46137
  }
46131
46138
  }
46132
46139
  dismiss() {
46133
46140
  this._modalRef?.dismiss(null, 'cancel');
46134
46141
  }
46135
46142
  tt(key) {
46136
- return this.i18n.t(key, DEFAULT_NAMESPACE$8);
46143
+ return this.i18n.t(key, DEFAULT_NAMESPACE$a);
46137
46144
  }
46138
46145
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgInfoSheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
46139
46146
  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: `
@@ -46300,7 +46307,7 @@ const SWITCH_ORG_MODAL_I18N = {
46300
46307
  },
46301
46308
  };
46302
46309
 
46303
- const DEFAULT_NAMESPACE$7 = 'Settings.SwitchOrg';
46310
+ const DEFAULT_NAMESPACE$9 = 'Settings.SwitchOrg';
46304
46311
  addIcons({ businessOutline, checkmarkCircleOutline });
46305
46312
  /**
46306
46313
  * `val-switch-org-modal` — modal de cambio de organización activa (organism
@@ -46326,7 +46333,7 @@ class SwitchOrgModalComponent {
46326
46333
  this.orgSwitch = inject(OrgSwitchService);
46327
46334
  this.errors = inject(ValtechErrorService);
46328
46335
  /** Namespace i18n con que la vista resuelve sus textos. */
46329
- this.i18nNamespace = DEFAULT_NAMESPACE$7;
46336
+ this.i18nNamespace = DEFAULT_NAMESPACE$9;
46330
46337
  this.orgs = signal([]);
46331
46338
  this.loading = signal(false);
46332
46339
  this.query = signal('');
@@ -46342,8 +46349,8 @@ class SwitchOrgModalComponent {
46342
46349
  return list;
46343
46350
  return list.filter(o => o.name.toLowerCase().includes(q));
46344
46351
  });
46345
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
46346
- this.i18n.registerContent(DEFAULT_NAMESPACE$7, SWITCH_ORG_MODAL_I18N);
46352
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$9)) {
46353
+ this.i18n.registerContent(DEFAULT_NAMESPACE$9, SWITCH_ORG_MODAL_I18N);
46347
46354
  }
46348
46355
  this.loadOrgs();
46349
46356
  }
@@ -46661,7 +46668,7 @@ const ACCOUNT_VIEW_I18N = {
46661
46668
  };
46662
46669
 
46663
46670
  addIcons({ peopleOutline, businessOutline, chevronForwardOutline, swapHorizontalOutline, mailOutline });
46664
- const DEFAULT_NAMESPACE$6 = 'Settings.Account';
46671
+ const DEFAULT_NAMESPACE$8 = 'Settings.Account';
46665
46672
  const DEFAULT_MANAGE_ORG_ROUTE = '/app/settings/organization';
46666
46673
  /**
46667
46674
  * `val-account-view` — vista Cuenta full-feature autocontenida (organism).
@@ -46702,7 +46709,7 @@ class AccountViewComponent {
46702
46709
  showNewOrgCta: merged.showNewOrgCta ?? true,
46703
46710
  showLogout: merged.showLogout ?? true,
46704
46711
  showDeleteAccount: merged.showDeleteAccount ?? true,
46705
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$6,
46712
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$8,
46706
46713
  manageOrgRoute: merged.manageOrgRoute ?? DEFAULT_MANAGE_ORG_ROUTE,
46707
46714
  onOrgSwitched: merged.onOrgSwitched,
46708
46715
  onOrgCreated: merged.onOrgCreated,
@@ -47427,7 +47434,7 @@ const EDIT_ORG_MODAL_I18N = {
47427
47434
  },
47428
47435
  };
47429
47436
 
47430
- const DEFAULT_NAMESPACE$5 = 'EditOrgModal';
47437
+ const DEFAULT_NAMESPACE$7 = 'EditOrgModal';
47431
47438
  /**
47432
47439
  * `val-edit-org-modal` — modal de edición de organización (nombre + descripción).
47433
47440
  * Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por la vista de
@@ -47454,10 +47461,10 @@ class EditOrgModalComponent {
47454
47461
  /** Organización a editar (pasada por `componentProps`). */
47455
47462
  this.org = null;
47456
47463
  /** Namespace i18n con que la vista resuelve sus textos. */
47457
- this.i18nNamespace = DEFAULT_NAMESPACE$5;
47464
+ this.i18nNamespace = DEFAULT_NAMESPACE$7;
47458
47465
  this.saving = signal(false);
47459
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
47460
- this.i18n.registerContent(DEFAULT_NAMESPACE$5, EDIT_ORG_MODAL_I18N);
47466
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
47467
+ this.i18n.registerContent(DEFAULT_NAMESPACE$7, EDIT_ORG_MODAL_I18N);
47461
47468
  }
47462
47469
  this.formMeta = signal(this.buildFormMeta());
47463
47470
  effect(() => {
@@ -47695,7 +47702,7 @@ const INVITE_MEMBER_MODAL_I18N = {
47695
47702
  },
47696
47703
  };
47697
47704
 
47698
- const DEFAULT_NAMESPACE$4 = 'InviteModal';
47705
+ const DEFAULT_NAMESPACE$6 = 'InviteModal';
47699
47706
  addIcons({ personOutline, mailOutline, closeOutline, checkmarkCircleOutline });
47700
47707
  /**
47701
47708
  * `val-invite-member-modal` — invitación de miembros a una organización.
@@ -47726,7 +47733,7 @@ class InviteMemberModalComponent {
47726
47733
  /** Ids de miembros ya existentes (excluidos de los resultados). */
47727
47734
  this.existingMemberIds = [];
47728
47735
  /** Namespace i18n con que la vista resuelve sus textos. */
47729
- this.i18nNamespace = DEFAULT_NAMESPACE$4;
47736
+ this.i18nNamespace = DEFAULT_NAMESPACE$6;
47730
47737
  this.query = signal('');
47731
47738
  this.searching = signal(false);
47732
47739
  this.searchResults = signal([]);
@@ -47773,8 +47780,8 @@ class InviteMemberModalComponent {
47773
47780
  state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
47774
47781
  }));
47775
47782
  this.searchSubject = new Subject();
47776
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$4)) {
47777
- this.i18n.registerContent(DEFAULT_NAMESPACE$4, INVITE_MEMBER_MODAL_I18N);
47783
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$6)) {
47784
+ this.i18n.registerContent(DEFAULT_NAMESPACE$6, INVITE_MEMBER_MODAL_I18N);
47778
47785
  }
47779
47786
  this.loadRoles();
47780
47787
  this.roleSub = this.roleControl.valueChanges.subscribe(v => {
@@ -48263,7 +48270,7 @@ const MEMBER_DETAIL_MODAL_I18N = {
48263
48270
  },
48264
48271
  };
48265
48272
 
48266
- const DEFAULT_NAMESPACE$3 = 'Settings.MemberDetail';
48273
+ const DEFAULT_NAMESPACE$5 = 'Settings.MemberDetail';
48267
48274
  /**
48268
48275
  * `val-member-detail-modal` — detalle de miembro de organización: perfil, rol y
48269
48276
  * permisos. Promovido desde `showcase` bajo el proceso de ADR-021.
@@ -48300,7 +48307,7 @@ class MemberDetailModalComponent {
48300
48307
  { id: 'admin', name: 'admin' },
48301
48308
  ];
48302
48309
  /** Namespace i18n con que la vista resuelve sus textos. */
48303
- this.i18nNamespace = DEFAULT_NAMESPACE$3;
48310
+ this.i18nNamespace = DEFAULT_NAMESPACE$5;
48304
48311
  this.working = signal(false);
48305
48312
  this.currentRole = signal('');
48306
48313
  this.roleControl = new FormControl('');
@@ -48340,8 +48347,8 @@ class MemberDetailModalComponent {
48340
48347
  type: 'button',
48341
48348
  state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
48342
48349
  }));
48343
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$3)) {
48344
- this.i18n.registerContent(DEFAULT_NAMESPACE$3, MEMBER_DETAIL_MODAL_I18N);
48350
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
48351
+ this.i18n.registerContent(DEFAULT_NAMESPACE$5, MEMBER_DETAIL_MODAL_I18N);
48345
48352
  }
48346
48353
  }
48347
48354
  ngOnInit() {
@@ -48678,7 +48685,7 @@ const TRANSFER_OWNERSHIP_MODAL_I18N = {
48678
48685
  },
48679
48686
  };
48680
48687
 
48681
- const DEFAULT_NAMESPACE$2 = 'Settings.TransferModal';
48688
+ const DEFAULT_NAMESPACE$4 = 'Settings.TransferModal';
48682
48689
  /**
48683
48690
  * `val-transfer-ownership-modal` — selector de miembro para transferir la
48684
48691
  * propiedad de la organización. Promovido desde `showcase` bajo el proceso de
@@ -48702,7 +48709,7 @@ class TransferOwnershipModalComponent {
48702
48709
  /** Lista de miembros transferibles (todos excepto el owner actual). */
48703
48710
  this.members = [];
48704
48711
  /** Namespace i18n con que la vista resuelve sus textos. */
48705
- this.i18nNamespace = DEFAULT_NAMESPACE$2;
48712
+ this.i18nNamespace = DEFAULT_NAMESPACE$4;
48706
48713
  this.query = signal('');
48707
48714
  this.selectedId = signal('');
48708
48715
  this.filtered = computed(() => {
@@ -48714,8 +48721,8 @@ class TransferOwnershipModalComponent {
48714
48721
  m.userId.toLowerCase().includes(q));
48715
48722
  });
48716
48723
  this.noResultsLabel = computed(() => `${this.t('noResults')} "${this.query()}"`);
48717
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$2)) {
48718
- this.i18n.registerContent(DEFAULT_NAMESPACE$2, TRANSFER_OWNERSHIP_MODAL_I18N);
48724
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$4)) {
48725
+ this.i18n.registerContent(DEFAULT_NAMESPACE$4, TRANSFER_OWNERSHIP_MODAL_I18N);
48719
48726
  }
48720
48727
  }
48721
48728
  ngOnInit() {
@@ -48900,6 +48907,849 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
48900
48907
  type: Input
48901
48908
  }] } });
48902
48909
 
48910
+ /**
48911
+ * Defaults i18n (es/en) embebidos en `val-member-import-modal` (ADR-023).
48912
+ * Auto-registrados si el consumer no proveyó el namespace `Settings.ImportModal`.
48913
+ */
48914
+ const MEMBER_IMPORT_MODAL_I18N = {
48915
+ es: {
48916
+ title: 'Importar miembros',
48917
+ hint: 'Pega un CSV con columnas: email, nombre, rol (una fila por persona).',
48918
+ csvPlaceholder: 'email,nombre,rol\nana@x.com,Ana,editor\nbeto@x.com,Beto,viewer',
48919
+ rolesAvailable: 'Roles disponibles:',
48920
+ onConflictLabel: 'Si el email ya existe',
48921
+ conflictAssign: 'Asignar el rol',
48922
+ conflictSkip: 'Omitir',
48923
+ conflictError: 'Marcar error',
48924
+ sendActivation: 'Enviar email de activación a las cuentas nuevas',
48925
+ submit: 'Importar',
48926
+ submitting: 'Importando...',
48927
+ parseError: 'No se pudo interpretar el CSV. Revisa el formato.',
48928
+ noRows: 'Pega al menos una fila válida (email y rol).',
48929
+ summary: 'Resumen',
48930
+ created: 'creados',
48931
+ assigned: 'asignados',
48932
+ skipped: 'omitidos',
48933
+ failed: 'con error',
48934
+ colEmail: 'Email',
48935
+ colStatus: 'Estado',
48936
+ colDetail: 'Detalle',
48937
+ activationSent: 'activación enviada',
48938
+ activationPending: 'activación pendiente',
48939
+ again: 'Importar otro lote',
48940
+ close: 'Cerrar',
48941
+ genericError: 'No se pudo completar la importación. Intenta de nuevo.',
48942
+ },
48943
+ en: {
48944
+ title: 'Import members',
48945
+ hint: 'Paste a CSV with columns: email, name, role (one row per person).',
48946
+ csvPlaceholder: 'email,name,role\nana@x.com,Ana,editor\nbeto@x.com,Beto,viewer',
48947
+ rolesAvailable: 'Available roles:',
48948
+ onConflictLabel: 'If the email already exists',
48949
+ conflictAssign: 'Assign the role',
48950
+ conflictSkip: 'Skip',
48951
+ conflictError: 'Mark as error',
48952
+ sendActivation: 'Send activation email to new accounts',
48953
+ submit: 'Import',
48954
+ submitting: 'Importing...',
48955
+ parseError: "Couldn't parse the CSV. Check the format.",
48956
+ noRows: 'Paste at least one valid row (email and role).',
48957
+ summary: 'Summary',
48958
+ created: 'created',
48959
+ assigned: 'assigned',
48960
+ skipped: 'skipped',
48961
+ failed: 'failed',
48962
+ colEmail: 'Email',
48963
+ colStatus: 'Status',
48964
+ colDetail: 'Detail',
48965
+ activationSent: 'activation sent',
48966
+ activationPending: 'activation pending',
48967
+ again: 'Import another batch',
48968
+ close: 'Close',
48969
+ genericError: "Couldn't complete the import. Try again.",
48970
+ },
48971
+ };
48972
+
48973
+ const DEFAULT_NAMESPACE$3 = 'Settings.ImportModal';
48974
+ /**
48975
+ * `val-member-import-modal` — panel de carga masiva de miembros (ADR-023 fase 6).
48976
+ *
48977
+ * El admin pega un CSV (`email,nombre,rol`); el modal lo parsea a filas y llama
48978
+ * `OrgService.importMembers`. Muestra el resultado por-fila (partial success):
48979
+ * `created` / `assigned` / `skipped` / `error`. Header canónico (Regla #5).
48980
+ *
48981
+ * Auto-registra defaults i18n (es/en) en `Settings.ImportModal`.
48982
+ */
48983
+ class MemberImportModalComponent {
48984
+ constructor() {
48985
+ this.i18n = inject(I18nService);
48986
+ this.orgService = inject(OrgService);
48987
+ /** Org destino del import. */
48988
+ this.orgId = '';
48989
+ /** Nombres de roles disponibles (para el hint). Opcional. */
48990
+ this.availableRoles = [];
48991
+ /** Namespace i18n. */
48992
+ this.i18nNamespace = DEFAULT_NAMESPACE$3;
48993
+ this.csv = '';
48994
+ this.onConflict = 'assignRole';
48995
+ this.sendActivation = true;
48996
+ this.submitting = signal(false);
48997
+ this.errorMsg = signal('');
48998
+ this.results = signal(null);
48999
+ this.rolesHint = computed(() => this.availableRoles.length ? `${this.t('rolesAvailable')} ${this.availableRoles.join(', ')}` : '');
49000
+ this.summaryLabel = computed(() => {
49001
+ const s = this.results()?.summary;
49002
+ if (!s)
49003
+ return this.t('summary');
49004
+ return `${this.t('summary')}: ${s.created} ${this.t('created')} · ${s.assigned} ${this.t('assigned')} · ${s.skipped} ${this.t('skipped')} · ${s.failed} ${this.t('failed')}`;
49005
+ });
49006
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$3)) {
49007
+ this.i18n.registerContent(DEFAULT_NAMESPACE$3, MEMBER_IMPORT_MODAL_I18N);
49008
+ }
49009
+ }
49010
+ ngOnInit() {
49011
+ if (!this.i18n.hasNamespace(this.i18nNamespace)) {
49012
+ this.i18n.registerContent(this.i18nNamespace, MEMBER_IMPORT_MODAL_I18N);
49013
+ }
49014
+ }
49015
+ /** Parsea el CSV pegado a filas {email,name,roleName}. Tolera header. */
49016
+ parseCsv() {
49017
+ const rows = [];
49018
+ for (const raw of this.csv.split(/\r?\n/)) {
49019
+ const line = raw.trim();
49020
+ if (!line)
49021
+ continue;
49022
+ const cols = line.split(',').map(c => c.trim());
49023
+ const email = (cols[0] ?? '').toLowerCase();
49024
+ // Saltar header (primera col "email") o líneas sin @.
49025
+ if (!email || !email.includes('@'))
49026
+ continue;
49027
+ rows.push({ email, name: cols[1] ?? '', roleName: cols[2] ?? '' });
49028
+ }
49029
+ return rows;
49030
+ }
49031
+ submit() {
49032
+ this.errorMsg.set('');
49033
+ const users = this.parseCsv();
49034
+ if (users.length === 0) {
49035
+ this.errorMsg.set(this.t('noRows'));
49036
+ return;
49037
+ }
49038
+ if (users.some(u => !u.roleName)) {
49039
+ this.errorMsg.set(this.t('parseError'));
49040
+ return;
49041
+ }
49042
+ this.submitting.set(true);
49043
+ this.orgService
49044
+ .importMembers(this.orgId, {
49045
+ users,
49046
+ onConflict: this.onConflict,
49047
+ sendActivation: this.sendActivation,
49048
+ })
49049
+ .subscribe({
49050
+ next: res => {
49051
+ this.submitting.set(false);
49052
+ this.results.set(res);
49053
+ // Refrescar lista del consumer si hubo altas/asignaciones efectivas.
49054
+ if (res.summary.created + res.summary.assigned > 0) {
49055
+ this.onSuccess?.();
49056
+ }
49057
+ },
49058
+ error: err => {
49059
+ this.submitting.set(false);
49060
+ this.errorMsg.set(err?.error?.message ?? this.t('genericError'));
49061
+ },
49062
+ });
49063
+ }
49064
+ statusLabel(r) {
49065
+ const base = this.t(r.status === 'error' ? 'failed' : r.status);
49066
+ if (r.status === 'created' && r.activation) {
49067
+ return `${base} · ${this.t(r.activation === 'sent' ? 'activationSent' : 'activationPending')}`;
49068
+ }
49069
+ return base;
49070
+ }
49071
+ reset() {
49072
+ this.csv = '';
49073
+ this.errorMsg.set('');
49074
+ this.results.set(null);
49075
+ }
49076
+ dismiss() {
49077
+ this._modalRef?.dismiss(null, 'cancel');
49078
+ }
49079
+ t(key) {
49080
+ return this.i18n.t(key, this.i18nNamespace);
49081
+ }
49082
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberImportModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
49083
+ 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: `
49084
+ <ion-header>
49085
+ <ion-toolbar>
49086
+ <ion-buttons slot="end">
49087
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
49088
+ <strong>{{ t('close') }}</strong>
49089
+ </ion-button>
49090
+ </ion-buttons>
49091
+ </ion-toolbar>
49092
+ </ion-header>
49093
+
49094
+ <ion-content class="ion-padding">
49095
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
49096
+
49097
+ @if (!results()) {
49098
+ <val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
49099
+
49100
+ @if (rolesHint()) {
49101
+ <val-text [props]="{ content: rolesHint(), size: 'small', color: 'medium', bold: false }" />
49102
+ }
49103
+
49104
+ <ion-textarea
49105
+ class="import-csv"
49106
+ [autoGrow]="true"
49107
+ [rows]="6"
49108
+ [placeholder]="t('csvPlaceholder')"
49109
+ [(ngModel)]="csv"
49110
+ />
49111
+
49112
+ <ion-select
49113
+ class="import-conflict"
49114
+ label="{{ t('onConflictLabel') }}"
49115
+ labelPlacement="stacked"
49116
+ interface="popover"
49117
+ [(ngModel)]="onConflict"
49118
+ >
49119
+ <ion-select-option value="assignRole">{{ t('conflictAssign') }}</ion-select-option>
49120
+ <ion-select-option value="skip">{{ t('conflictSkip') }}</ion-select-option>
49121
+ <ion-select-option value="error">{{ t('conflictError') }}</ion-select-option>
49122
+ </ion-select>
49123
+
49124
+ <ion-checkbox class="import-activation" labelPlacement="end" [(ngModel)]="sendActivation">
49125
+ {{ t('sendActivation') }}
49126
+ </ion-checkbox>
49127
+
49128
+ @if (errorMsg()) {
49129
+ <p class="import-error">{{ errorMsg() }}</p>
49130
+ }
49131
+
49132
+ <div class="import-actions">
49133
+ <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49134
+ @if (submitting()) {
49135
+ <ion-spinner name="dots" />
49136
+ &nbsp;{{ t('submitting') }}
49137
+ } @else {
49138
+ {{ t('submit') }}
49139
+ }
49140
+ </ion-button>
49141
+ </div>
49142
+ } @else {
49143
+ <!-- Resultados -->
49144
+ <val-title [props]="{ content: summaryLabel(), size: 'large', color: '', bold: true }" />
49145
+ <div class="import-results">
49146
+ @for (r of results()!.results; track r.email) {
49147
+ <div class="import-row import-row--{{ r.status }}">
49148
+ <span class="import-row__email">{{ r.email }}</span>
49149
+ <span class="import-row__status">{{ statusLabel(r) }}</span>
49150
+ @if (r.reason) {
49151
+ <span class="import-row__reason">{{ r.reason }}</span>
49152
+ }
49153
+ </div>
49154
+ }
49155
+ </div>
49156
+ <div class="import-actions">
49157
+ <ion-button expand="block" fill="outline" color="dark" (click)="reset()">
49158
+ {{ t('again') }}
49159
+ </ion-button>
49160
+ </div>
49161
+ }
49162
+ </ion-content>
49163
+ `, 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"] }] }); }
49164
+ }
49165
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberImportModalComponent, decorators: [{
49166
+ type: Component,
49167
+ args: [{ selector: 'val-member-import-modal', standalone: true, imports: [
49168
+ FormsModule,
49169
+ IonHeader,
49170
+ IonToolbar,
49171
+ IonButtons,
49172
+ IonButton,
49173
+ IonContent,
49174
+ IonTextarea,
49175
+ IonSelect,
49176
+ IonSelectOption,
49177
+ IonCheckbox,
49178
+ IonSpinner,
49179
+ DisplayComponent,
49180
+ TextComponent,
49181
+ TitleComponent,
49182
+ ], template: `
49183
+ <ion-header>
49184
+ <ion-toolbar>
49185
+ <ion-buttons slot="end">
49186
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
49187
+ <strong>{{ t('close') }}</strong>
49188
+ </ion-button>
49189
+ </ion-buttons>
49190
+ </ion-toolbar>
49191
+ </ion-header>
49192
+
49193
+ <ion-content class="ion-padding">
49194
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
49195
+
49196
+ @if (!results()) {
49197
+ <val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
49198
+
49199
+ @if (rolesHint()) {
49200
+ <val-text [props]="{ content: rolesHint(), size: 'small', color: 'medium', bold: false }" />
49201
+ }
49202
+
49203
+ <ion-textarea
49204
+ class="import-csv"
49205
+ [autoGrow]="true"
49206
+ [rows]="6"
49207
+ [placeholder]="t('csvPlaceholder')"
49208
+ [(ngModel)]="csv"
49209
+ />
49210
+
49211
+ <ion-select
49212
+ class="import-conflict"
49213
+ label="{{ t('onConflictLabel') }}"
49214
+ labelPlacement="stacked"
49215
+ interface="popover"
49216
+ [(ngModel)]="onConflict"
49217
+ >
49218
+ <ion-select-option value="assignRole">{{ t('conflictAssign') }}</ion-select-option>
49219
+ <ion-select-option value="skip">{{ t('conflictSkip') }}</ion-select-option>
49220
+ <ion-select-option value="error">{{ t('conflictError') }}</ion-select-option>
49221
+ </ion-select>
49222
+
49223
+ <ion-checkbox class="import-activation" labelPlacement="end" [(ngModel)]="sendActivation">
49224
+ {{ t('sendActivation') }}
49225
+ </ion-checkbox>
49226
+
49227
+ @if (errorMsg()) {
49228
+ <p class="import-error">{{ errorMsg() }}</p>
49229
+ }
49230
+
49231
+ <div class="import-actions">
49232
+ <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49233
+ @if (submitting()) {
49234
+ <ion-spinner name="dots" />
49235
+ &nbsp;{{ t('submitting') }}
49236
+ } @else {
49237
+ {{ t('submit') }}
49238
+ }
49239
+ </ion-button>
49240
+ </div>
49241
+ } @else {
49242
+ <!-- Resultados -->
49243
+ <val-title [props]="{ content: summaryLabel(), size: 'large', color: '', bold: true }" />
49244
+ <div class="import-results">
49245
+ @for (r of results()!.results; track r.email) {
49246
+ <div class="import-row import-row--{{ r.status }}">
49247
+ <span class="import-row__email">{{ r.email }}</span>
49248
+ <span class="import-row__status">{{ statusLabel(r) }}</span>
49249
+ @if (r.reason) {
49250
+ <span class="import-row__reason">{{ r.reason }}</span>
49251
+ }
49252
+ </div>
49253
+ }
49254
+ </div>
49255
+ <div class="import-actions">
49256
+ <ion-button expand="block" fill="outline" color="dark" (click)="reset()">
49257
+ {{ t('again') }}
49258
+ </ion-button>
49259
+ </div>
49260
+ }
49261
+ </ion-content>
49262
+ `, 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"] }]
49263
+ }], ctorParameters: () => [], propDecorators: { orgId: [{
49264
+ type: Input
49265
+ }], availableRoles: [{
49266
+ type: Input
49267
+ }], onSuccess: [{
49268
+ type: Input
49269
+ }], _modalRef: [{
49270
+ type: Input
49271
+ }], i18nNamespace: [{
49272
+ type: Input
49273
+ }] } });
49274
+
49275
+ /**
49276
+ * Cliente HTTP de Client API Keys org-scopeadas (ADR-023). La org activa se
49277
+ * resuelve en backend desde el JWT, así que el path no la incluye.
49278
+ */
49279
+ class ApiKeyService {
49280
+ constructor(config, http) {
49281
+ this.config = config;
49282
+ this.http = http;
49283
+ }
49284
+ get baseUrl() {
49285
+ return `${this.config.apiUrl}/org/api-keys`;
49286
+ }
49287
+ /** Lista las API keys de la org activa. */
49288
+ list() {
49289
+ return this.http.get(`${this.baseUrl}/`).pipe(map$1(r => r.keys ?? []));
49290
+ }
49291
+ /** Crea una API key. La respuesta incluye el secreto (se muestra UNA vez). */
49292
+ create(req) {
49293
+ return this.http.post(`${this.baseUrl}/`, req);
49294
+ }
49295
+ /** Revoca (elimina) una API key por id. */
49296
+ revoke(keyId) {
49297
+ return this.http.delete(`${this.baseUrl}/${keyId}`);
49298
+ }
49299
+ /** Catálogo de permisos asignables a una key. */
49300
+ availablePermissions() {
49301
+ return this.http
49302
+ .get(`${this.baseUrl}/permissions`)
49303
+ .pipe(map$1(r => r.permissions ?? []));
49304
+ }
49305
+ 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 }); }
49306
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeyService, providedIn: 'root' }); }
49307
+ }
49308
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeyService, decorators: [{
49309
+ type: Injectable,
49310
+ args: [{ providedIn: 'root' }]
49311
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
49312
+ type: Inject,
49313
+ args: [VALTECH_AUTH_CONFIG]
49314
+ }] }, { type: i1$3.HttpClient }] });
49315
+
49316
+ /**
49317
+ * Defaults i18n (es/en) embebidos en `val-api-keys-modal` (ADR-023).
49318
+ * Auto-registrados si el consumer no proveyó el namespace `Settings.ApiKeysModal`.
49319
+ */
49320
+ const API_KEYS_MODAL_I18N = {
49321
+ es: {
49322
+ title: 'Client API Keys',
49323
+ hint: 'Llaves M2M org-scopeadas para integraciones (p. ej. importar miembros).',
49324
+ empty: 'Aún no hay API keys en esta organización.',
49325
+ newKey: 'Nueva API key',
49326
+ nameLabel: 'Nombre',
49327
+ namePlaceholder: 'Ej: Integración RRHH',
49328
+ permissionsLabel: 'Permisos',
49329
+ expiresLabel: 'Expira en (días, opcional)',
49330
+ create: 'Crear key',
49331
+ creating: 'Creando...',
49332
+ cancel: 'Cancelar',
49333
+ revoke: 'Revocar',
49334
+ revokeConfirm: '¿Revocar esta API key? Las integraciones que la usen dejarán de funcionar.',
49335
+ secretTitle: 'Copia tu API key ahora',
49336
+ secretWarning: 'Este secreto se muestra una sola vez. Guárdalo en un lugar seguro.',
49337
+ copy: 'Copiar',
49338
+ copied: 'Copiada',
49339
+ done: 'Listo',
49340
+ statusActive: 'activa',
49341
+ lastUsed: 'Último uso',
49342
+ never: 'nunca',
49343
+ permsCount: 'permisos',
49344
+ noName: 'El nombre es obligatorio.',
49345
+ noPerms: 'Selecciona al menos un permiso.',
49346
+ close: 'Cerrar',
49347
+ genericError: 'No se pudo completar la operación. Intenta de nuevo.',
49348
+ },
49349
+ en: {
49350
+ title: 'Client API Keys',
49351
+ hint: 'Org-scoped M2M keys for integrations (e.g. member import).',
49352
+ empty: 'No API keys in this organization yet.',
49353
+ newKey: 'New API key',
49354
+ nameLabel: 'Name',
49355
+ namePlaceholder: 'E.g: HR integration',
49356
+ permissionsLabel: 'Permissions',
49357
+ expiresLabel: 'Expires in (days, optional)',
49358
+ create: 'Create key',
49359
+ creating: 'Creating...',
49360
+ cancel: 'Cancel',
49361
+ revoke: 'Revoke',
49362
+ revokeConfirm: 'Revoke this API key? Integrations using it will stop working.',
49363
+ secretTitle: 'Copy your API key now',
49364
+ secretWarning: 'This secret is shown only once. Store it somewhere safe.',
49365
+ copy: 'Copy',
49366
+ copied: 'Copied',
49367
+ done: 'Done',
49368
+ statusActive: 'active',
49369
+ lastUsed: 'Last used',
49370
+ never: 'never',
49371
+ permsCount: 'permissions',
49372
+ noName: 'Name is required.',
49373
+ noPerms: 'Select at least one permission.',
49374
+ close: 'Close',
49375
+ genericError: "Couldn't complete the operation. Try again.",
49376
+ },
49377
+ };
49378
+
49379
+ const DEFAULT_NAMESPACE$2 = 'Settings.ApiKeysModal';
49380
+ /**
49381
+ * `val-api-keys-modal` — gestión de Client API Keys org-scopeadas (ADR-023 fase 6).
49382
+ *
49383
+ * Lista las keys de la org, permite crear (mostrando el secreto UNA vez con copia)
49384
+ * y revocar. Pensado para habilitar integraciones M2M del import de miembros, pero
49385
+ * sirve a cualquier permiso del catálogo. Header canónico (Regla #5).
49386
+ */
49387
+ class ApiKeysModalComponent {
49388
+ constructor() {
49389
+ this.i18n = inject(I18nService);
49390
+ this.apiKeys = inject(ApiKeyService);
49391
+ /** Namespace i18n. */
49392
+ this.i18nNamespace = DEFAULT_NAMESPACE$2;
49393
+ this.keys = signal([]);
49394
+ this.availablePermissions = signal([]);
49395
+ this.loading = signal(true);
49396
+ this.showCreate = signal(false);
49397
+ this.submitting = signal(false);
49398
+ this.errorMsg = signal('');
49399
+ this.secret = signal(null);
49400
+ this.copied = signal(false);
49401
+ this.name = '';
49402
+ this.expiresInDays = null;
49403
+ this.selectedPerms = signal(new Set());
49404
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$2)) {
49405
+ this.i18n.registerContent(DEFAULT_NAMESPACE$2, API_KEYS_MODAL_I18N);
49406
+ }
49407
+ }
49408
+ ngOnInit() {
49409
+ if (!this.i18n.hasNamespace(this.i18nNamespace)) {
49410
+ this.i18n.registerContent(this.i18nNamespace, API_KEYS_MODAL_I18N);
49411
+ }
49412
+ this.refresh();
49413
+ this.apiKeys.availablePermissions().subscribe({
49414
+ next: perms => this.availablePermissions.set(perms),
49415
+ error: () => { },
49416
+ });
49417
+ }
49418
+ refresh() {
49419
+ this.loading.set(true);
49420
+ this.apiKeys.list().subscribe({
49421
+ next: keys => {
49422
+ this.keys.set(keys);
49423
+ this.loading.set(false);
49424
+ },
49425
+ error: () => this.loading.set(false),
49426
+ });
49427
+ }
49428
+ openCreate() {
49429
+ this.errorMsg.set('');
49430
+ this.name = '';
49431
+ this.expiresInDays = null;
49432
+ this.selectedPerms.set(new Set());
49433
+ this.showCreate.set(true);
49434
+ }
49435
+ togglePerm(perm, ev) {
49436
+ const checked = ev.detail?.checked ?? false;
49437
+ const next = new Set(this.selectedPerms());
49438
+ if (checked) {
49439
+ next.add(perm);
49440
+ }
49441
+ else {
49442
+ next.delete(perm);
49443
+ }
49444
+ this.selectedPerms.set(next);
49445
+ }
49446
+ submit() {
49447
+ this.errorMsg.set('');
49448
+ if (!this.name.trim()) {
49449
+ this.errorMsg.set(this.t('noName'));
49450
+ return;
49451
+ }
49452
+ const permissions = Array.from(this.selectedPerms());
49453
+ if (permissions.length === 0) {
49454
+ this.errorMsg.set(this.t('noPerms'));
49455
+ return;
49456
+ }
49457
+ this.submitting.set(true);
49458
+ this.apiKeys
49459
+ .create({
49460
+ name: this.name.trim(),
49461
+ permissions,
49462
+ expiresInDays: this.expiresInDays && this.expiresInDays > 0 ? this.expiresInDays : undefined,
49463
+ })
49464
+ .subscribe({
49465
+ next: res => {
49466
+ this.submitting.set(false);
49467
+ this.showCreate.set(false);
49468
+ this.secret.set(res.key);
49469
+ },
49470
+ error: err => {
49471
+ this.submitting.set(false);
49472
+ this.errorMsg.set(err?.error?.message ?? this.t('genericError'));
49473
+ },
49474
+ });
49475
+ }
49476
+ revoke(key) {
49477
+ if (!confirm(this.t('revokeConfirm')))
49478
+ return;
49479
+ this.apiKeys.revoke(key.keyId).subscribe({
49480
+ next: () => this.refresh(),
49481
+ error: err => this.errorMsg.set(err?.error?.message ?? this.t('genericError')),
49482
+ });
49483
+ }
49484
+ async copy(secret) {
49485
+ try {
49486
+ await navigator.clipboard.writeText(secret);
49487
+ this.copied.set(true);
49488
+ }
49489
+ catch {
49490
+ // clipboard no disponible — el user puede seleccionar el texto manualmente
49491
+ }
49492
+ }
49493
+ finishSecret() {
49494
+ this.secret.set(null);
49495
+ this.copied.set(false);
49496
+ this.refresh();
49497
+ }
49498
+ dismiss() {
49499
+ this._modalRef?.dismiss(null, 'cancel');
49500
+ }
49501
+ t(key) {
49502
+ return this.i18n.t(key, this.i18nNamespace);
49503
+ }
49504
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeysModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
49505
+ 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: `
49506
+ <ion-header>
49507
+ <ion-toolbar>
49508
+ <ion-buttons slot="end">
49509
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
49510
+ <strong>{{ t('close') }}</strong>
49511
+ </ion-button>
49512
+ </ion-buttons>
49513
+ </ion-toolbar>
49514
+ </ion-header>
49515
+
49516
+ <ion-content class="ion-padding">
49517
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
49518
+ <val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
49519
+
49520
+ @if (secret(); as s) {
49521
+ <!-- Secreto recién creado: mostrar UNA vez -->
49522
+ <div class="apikey-secret">
49523
+ <val-title [props]="{ content: t('secretTitle'), size: 'large', color: '', bold: true }" />
49524
+ <p class="apikey-secret__warn">{{ t('secretWarning') }}</p>
49525
+ <div class="apikey-secret__box">
49526
+ <code>{{ s.secret }}</code>
49527
+ </div>
49528
+ <div class="apikey-actions">
49529
+ <ion-button expand="block" color="dark" (click)="copy(s.secret)">
49530
+ {{ copied() ? t('copied') : t('copy') }}
49531
+ </ion-button>
49532
+ <ion-button expand="block" fill="outline" color="dark" (click)="finishSecret()">
49533
+ {{ t('done') }}
49534
+ </ion-button>
49535
+ </div>
49536
+ </div>
49537
+ } @else {
49538
+ @if (loading()) {
49539
+ <ion-spinner name="dots" />
49540
+ } @else {
49541
+ <!-- Lista -->
49542
+ @if (keys().length === 0) {
49543
+ <val-text [props]="{ content: t('empty'), size: 'medium', color: 'medium', bold: false }" />
49544
+ } @else {
49545
+ <div class="apikey-list">
49546
+ @for (k of keys(); track k.keyId) {
49547
+ <div class="apikey-row">
49548
+ <div class="apikey-row__main">
49549
+ <span class="apikey-row__name">{{ k.name }}</span>
49550
+ <span class="apikey-row__meta">
49551
+ {{ k.permissions.length }} {{ t('permsCount') }} · {{ t('lastUsed') }}:
49552
+ {{ k.lastUsedAt || t('never') }}
49553
+ </span>
49554
+ </div>
49555
+ <ion-button fill="clear" color="danger" size="small" (click)="revoke(k)">
49556
+ {{ t('revoke') }}
49557
+ </ion-button>
49558
+ </div>
49559
+ }
49560
+ </div>
49561
+ }
49562
+
49563
+ @if (errorMsg()) {
49564
+ <p class="apikey-error">{{ errorMsg() }}</p>
49565
+ }
49566
+
49567
+ <!-- Crear -->
49568
+ @if (showCreate()) {
49569
+ <div class="apikey-create">
49570
+ <ion-input
49571
+ label="{{ t('nameLabel') }}"
49572
+ labelPlacement="stacked"
49573
+ [placeholder]="t('namePlaceholder')"
49574
+ [(ngModel)]="name"
49575
+ />
49576
+ <p class="apikey-create__label">{{ t('permissionsLabel') }}</p>
49577
+ <div class="apikey-perms">
49578
+ @for (p of availablePermissions(); track p) {
49579
+ <ion-checkbox
49580
+ labelPlacement="end"
49581
+ [checked]="selectedPerms().has(p)"
49582
+ (ionChange)="togglePerm(p, $event)"
49583
+ >
49584
+ {{ p }}
49585
+ </ion-checkbox>
49586
+ }
49587
+ </div>
49588
+ <ion-input
49589
+ label="{{ t('expiresLabel') }}"
49590
+ labelPlacement="stacked"
49591
+ type="number"
49592
+ [(ngModel)]="expiresInDays"
49593
+ />
49594
+ <div class="apikey-actions">
49595
+ <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49596
+ @if (submitting()) {
49597
+ <ion-spinner name="dots" />&nbsp;{{ t('creating') }}
49598
+ } @else {
49599
+ {{ t('create') }}
49600
+ }
49601
+ </ion-button>
49602
+ <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49603
+ {{ t('cancel') }}
49604
+ </ion-button>
49605
+ </div>
49606
+ </div>
49607
+ } @else {
49608
+ <div class="apikey-actions">
49609
+ <ion-button expand="block" color="primary" (click)="openCreate()">
49610
+ {{ t('newKey') }}
49611
+ </ion-button>
49612
+ </div>
49613
+ }
49614
+ }
49615
+ }
49616
+ </ion-content>
49617
+ `, 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"] }] }); }
49618
+ }
49619
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeysModalComponent, decorators: [{
49620
+ type: Component,
49621
+ args: [{ selector: 'val-api-keys-modal', standalone: true, imports: [
49622
+ FormsModule,
49623
+ IonHeader,
49624
+ IonToolbar,
49625
+ IonButtons,
49626
+ IonButton,
49627
+ IonContent,
49628
+ IonInput,
49629
+ IonCheckbox,
49630
+ IonSpinner,
49631
+ DisplayComponent,
49632
+ TextComponent,
49633
+ TitleComponent,
49634
+ ], template: `
49635
+ <ion-header>
49636
+ <ion-toolbar>
49637
+ <ion-buttons slot="end">
49638
+ <ion-button fill="clear" color="dark" (click)="dismiss()">
49639
+ <strong>{{ t('close') }}</strong>
49640
+ </ion-button>
49641
+ </ion-buttons>
49642
+ </ion-toolbar>
49643
+ </ion-header>
49644
+
49645
+ <ion-content class="ion-padding">
49646
+ <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
49647
+ <val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
49648
+
49649
+ @if (secret(); as s) {
49650
+ <!-- Secreto recién creado: mostrar UNA vez -->
49651
+ <div class="apikey-secret">
49652
+ <val-title [props]="{ content: t('secretTitle'), size: 'large', color: '', bold: true }" />
49653
+ <p class="apikey-secret__warn">{{ t('secretWarning') }}</p>
49654
+ <div class="apikey-secret__box">
49655
+ <code>{{ s.secret }}</code>
49656
+ </div>
49657
+ <div class="apikey-actions">
49658
+ <ion-button expand="block" color="dark" (click)="copy(s.secret)">
49659
+ {{ copied() ? t('copied') : t('copy') }}
49660
+ </ion-button>
49661
+ <ion-button expand="block" fill="outline" color="dark" (click)="finishSecret()">
49662
+ {{ t('done') }}
49663
+ </ion-button>
49664
+ </div>
49665
+ </div>
49666
+ } @else {
49667
+ @if (loading()) {
49668
+ <ion-spinner name="dots" />
49669
+ } @else {
49670
+ <!-- Lista -->
49671
+ @if (keys().length === 0) {
49672
+ <val-text [props]="{ content: t('empty'), size: 'medium', color: 'medium', bold: false }" />
49673
+ } @else {
49674
+ <div class="apikey-list">
49675
+ @for (k of keys(); track k.keyId) {
49676
+ <div class="apikey-row">
49677
+ <div class="apikey-row__main">
49678
+ <span class="apikey-row__name">{{ k.name }}</span>
49679
+ <span class="apikey-row__meta">
49680
+ {{ k.permissions.length }} {{ t('permsCount') }} · {{ t('lastUsed') }}:
49681
+ {{ k.lastUsedAt || t('never') }}
49682
+ </span>
49683
+ </div>
49684
+ <ion-button fill="clear" color="danger" size="small" (click)="revoke(k)">
49685
+ {{ t('revoke') }}
49686
+ </ion-button>
49687
+ </div>
49688
+ }
49689
+ </div>
49690
+ }
49691
+
49692
+ @if (errorMsg()) {
49693
+ <p class="apikey-error">{{ errorMsg() }}</p>
49694
+ }
49695
+
49696
+ <!-- Crear -->
49697
+ @if (showCreate()) {
49698
+ <div class="apikey-create">
49699
+ <ion-input
49700
+ label="{{ t('nameLabel') }}"
49701
+ labelPlacement="stacked"
49702
+ [placeholder]="t('namePlaceholder')"
49703
+ [(ngModel)]="name"
49704
+ />
49705
+ <p class="apikey-create__label">{{ t('permissionsLabel') }}</p>
49706
+ <div class="apikey-perms">
49707
+ @for (p of availablePermissions(); track p) {
49708
+ <ion-checkbox
49709
+ labelPlacement="end"
49710
+ [checked]="selectedPerms().has(p)"
49711
+ (ionChange)="togglePerm(p, $event)"
49712
+ >
49713
+ {{ p }}
49714
+ </ion-checkbox>
49715
+ }
49716
+ </div>
49717
+ <ion-input
49718
+ label="{{ t('expiresLabel') }}"
49719
+ labelPlacement="stacked"
49720
+ type="number"
49721
+ [(ngModel)]="expiresInDays"
49722
+ />
49723
+ <div class="apikey-actions">
49724
+ <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49725
+ @if (submitting()) {
49726
+ <ion-spinner name="dots" />&nbsp;{{ t('creating') }}
49727
+ } @else {
49728
+ {{ t('create') }}
49729
+ }
49730
+ </ion-button>
49731
+ <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49732
+ {{ t('cancel') }}
49733
+ </ion-button>
49734
+ </div>
49735
+ </div>
49736
+ } @else {
49737
+ <div class="apikey-actions">
49738
+ <ion-button expand="block" color="primary" (click)="openCreate()">
49739
+ {{ t('newKey') }}
49740
+ </ion-button>
49741
+ </div>
49742
+ }
49743
+ }
49744
+ }
49745
+ </ion-content>
49746
+ `, 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"] }]
49747
+ }], ctorParameters: () => [], propDecorators: { _modalRef: [{
49748
+ type: Input
49749
+ }], i18nNamespace: [{
49750
+ type: Input
49751
+ }] } });
49752
+
48903
49753
  /**
48904
49754
  * Defaults i18n (es/en) embebidos en `val-organization-view`. Auto-registrados en
48905
49755
  * el constructor del componente si el consumer no proveyó el namespace
@@ -48955,6 +49805,8 @@ const ORGANIZATION_VIEW_I18N = {
48955
49805
  inviteCtaTitle: '¿Deseas agregar a alguien?',
48956
49806
  inviteCtaHint: 'Invita a usuarios a unirse a tu organización.',
48957
49807
  inviteOpen: 'Invitar',
49808
+ importOpen: 'Importar',
49809
+ apiKeysOpen: 'API Keys',
48958
49810
  viewMemberCta: 'Ver miembro',
48959
49811
  roleViewer: 'Visor',
48960
49812
  roleEditor: 'Editor',
@@ -49024,6 +49876,8 @@ const ORGANIZATION_VIEW_I18N = {
49024
49876
  inviteCtaTitle: 'Want to add someone?',
49025
49877
  inviteCtaHint: 'Invite users to join your organization.',
49026
49878
  inviteOpen: 'Invite',
49879
+ importOpen: 'Import',
49880
+ apiKeysOpen: 'API Keys',
49027
49881
  viewMemberCta: 'View member',
49028
49882
  roleViewer: 'Viewer',
49029
49883
  roleEditor: 'Editor',
@@ -49137,6 +49991,22 @@ class OrganizationViewComponent {
49137
49991
  this.members = signal([]);
49138
49992
  this.membersLoading = signal(false);
49139
49993
  this.membersLoadError = signal(null);
49994
+ this.membersErrorState = computed(() => {
49995
+ this.i18n.lang();
49996
+ const err = this.membersLoadError();
49997
+ if (!err)
49998
+ return null;
49999
+ return createErrorStateProps(err, {
50000
+ title: { offline: this.tt('offlineTitle'), error: this.tt('errorTitle') },
50001
+ description: {
50002
+ offline: this.tt('offlineHint'),
50003
+ error: interpretError(err).message,
50004
+ },
50005
+ retryLabel: this.tt('retry'),
50006
+ onRetry: () => this.loadMembers(),
50007
+ retrying: this.membersLoading(),
50008
+ });
50009
+ });
49140
50010
  this.membersNextToken = signal(null);
49141
50011
  this.loadingMoreMembers = signal(false);
49142
50012
  this.showAllMembers = signal(false);
@@ -49223,6 +50093,22 @@ class OrganizationViewComponent {
49223
50093
  shape: 'round',
49224
50094
  type: 'button',
49225
50095
  }));
50096
+ this.openImportButtonProps = computed(() => ({
50097
+ text: this.tt('importOpen'),
50098
+ color: 'dark',
50099
+ fill: 'outline',
50100
+ size: 'default',
50101
+ shape: 'round',
50102
+ type: 'button',
50103
+ }));
50104
+ this.openApiKeysButtonProps = computed(() => ({
50105
+ text: this.tt('apiKeysOpen'),
50106
+ color: 'dark',
50107
+ fill: 'clear',
50108
+ size: 'default',
50109
+ shape: 'round',
50110
+ type: 'button',
50111
+ }));
49226
50112
  /** Guarda para detectar el primer disparo del effect (evita doble carga). */
49227
50113
  this.lastLoadedOrgId = null;
49228
50114
  const ns = this.ns;
@@ -49323,6 +50209,36 @@ class OrganizationViewComponent {
49323
50209
  backdropDismiss: true,
49324
50210
  });
49325
50211
  }
50212
+ onOpenImport() {
50213
+ void this.modalService.open({
50214
+ component: MemberImportModalComponent,
50215
+ componentProps: {
50216
+ orgId: this.activeOrgId(),
50217
+ availableRoles: this.availableRoles().map(r => r.name),
50218
+ onSuccess: () => {
50219
+ this.loadMembers();
50220
+ this.resolvedConfig().onMemberInvited?.();
50221
+ },
50222
+ },
50223
+ breakpoints: {
50224
+ breakpoints: [0, 0.92],
50225
+ initialBreakpoint: 0.92,
50226
+ showHandle: true,
50227
+ },
50228
+ backdropDismiss: true,
50229
+ });
50230
+ }
50231
+ onOpenApiKeys() {
50232
+ void this.modalService.open({
50233
+ component: ApiKeysModalComponent,
50234
+ breakpoints: {
50235
+ breakpoints: [0, 0.92],
50236
+ initialBreakpoint: 0.92,
50237
+ showHandle: true,
50238
+ },
50239
+ backdropDismiss: true,
50240
+ });
50241
+ }
49326
50242
  onViewPermissions() {
49327
50243
  this.nav.navigateByUrl(this.resolvedConfig().viewPermissionsRoute);
49328
50244
  }
@@ -49404,6 +50320,7 @@ class OrganizationViewComponent {
49404
50320
  if (!orgId)
49405
50321
  return Promise.resolve();
49406
50322
  this.membersLoading.set(true);
50323
+ this.membersLoadError.set(null);
49407
50324
  this.membersNextToken.set(null);
49408
50325
  this.showAllMembers.set(false);
49409
50326
  return new Promise(resolve => {
@@ -49614,6 +50531,8 @@ class OrganizationViewComponent {
49614
50531
  <div class="section-body">
49615
50532
  @if (membersLoading()) {
49616
50533
  <val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
50534
+ } @else if (membersErrorState()) {
50535
+ <val-empty-state [props]="membersErrorState()!" />
49617
50536
  } @else if (members().length === 0) {
49618
50537
  <val-text
49619
50538
  [props]="{
@@ -49706,6 +50625,8 @@ class OrganizationViewComponent {
49706
50625
  />
49707
50626
  </div>
49708
50627
  <val-button [props]="openInviteButtonProps()" (click)="onOpenInvite()" />
50628
+ <val-button [props]="openImportButtonProps()" (click)="onOpenImport()" />
50629
+ <val-button [props]="openApiKeysButtonProps()" (click)="onOpenApiKeys()" />
49709
50630
  </div>
49710
50631
  </section>
49711
50632
  }
@@ -49814,6 +50735,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49814
50735
  ButtonComponent,
49815
50736
  MemberCardComponent,
49816
50737
  TransferOwnershipModalComponent,
50738
+ MemberImportModalComponent,
50739
+ ApiKeysModalComponent,
49817
50740
  ], template: `
49818
50741
  <div class="page">
49819
50742
  <header class="page-header">
@@ -49892,6 +50815,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49892
50815
  <div class="section-body">
49893
50816
  @if (membersLoading()) {
49894
50817
  <val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
50818
+ } @else if (membersErrorState()) {
50819
+ <val-empty-state [props]="membersErrorState()!" />
49895
50820
  } @else if (members().length === 0) {
49896
50821
  <val-text
49897
50822
  [props]="{
@@ -49984,6 +50909,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49984
50909
  />
49985
50910
  </div>
49986
50911
  <val-button [props]="openInviteButtonProps()" (click)="onOpenInvite()" />
50912
+ <val-button [props]="openImportButtonProps()" (click)="onOpenImport()" />
50913
+ <val-button [props]="openApiKeysButtonProps()" (click)="onOpenApiKeys()" />
49987
50914
  </div>
49988
50915
  </section>
49989
50916
  }
@@ -61254,5 +62181,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
61254
62181
  * Generated bundle index. Do not edit.
61255
62182
  */
61256
62183
 
61257
- export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, 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, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
62184
+ export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, 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, 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, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
61258
62185
  //# sourceMappingURL=valtech-components.mjs.map