valtech-components 4.0.33 → 4.0.35

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 (37) hide show
  1. package/esm2022/lib/components/organisms/edit-org-modal/edit-org-modal.component.mjs +41 -4
  2. package/esm2022/lib/components/organisms/edit-org-modal/edit-org-modal.i18n.mjs +5 -1
  3. package/esm2022/lib/components/organisms/organization-view/organization-view.component.mjs +8 -5
  4. package/esm2022/lib/components/organisms/switch-org-modal/switch-org-modal.component.mjs +18 -9
  5. package/esm2022/lib/services/auth/auth.service.mjs +28 -20
  6. package/esm2022/lib/services/auth/oauth-callback.component.mjs +13 -1
  7. package/esm2022/lib/services/auth/oauth.service.mjs +83 -8
  8. package/esm2022/lib/services/auth/storage.service.mjs +7 -11
  9. package/esm2022/lib/services/auth/types.mjs +1 -1
  10. package/esm2022/lib/services/link-processor.service.mjs +27 -5
  11. package/esm2022/lib/services/org/types.mjs +1 -1
  12. package/esm2022/lib/services/requests/request-visibility.mjs +41 -0
  13. package/esm2022/lib/services/requests/types.mjs +1 -1
  14. package/esm2022/lib/version.mjs +2 -2
  15. package/esm2022/public-api.mjs +2 -1
  16. package/fesm2022/valtech-components.mjs +263 -56
  17. package/fesm2022/valtech-components.mjs.map +1 -1
  18. package/lib/components/atoms/rights-footer/rights-footer.component.d.ts +2 -2
  19. package/lib/components/molecules/username-input/username-input.component.d.ts +1 -1
  20. package/lib/components/organisms/article/article.component.d.ts +3 -3
  21. package/lib/components/organisms/edit-org-modal/edit-org-modal.component.d.ts +2 -0
  22. package/lib/components/organisms/landing-steps/landing-steps.component.d.ts +1 -1
  23. package/lib/components/organisms/notification-preferences-view/notification-preferences-view.component.d.ts +1 -1
  24. package/lib/components/organisms/organization-view/organization-view.component.d.ts +2 -0
  25. package/lib/components/organisms/switch-org-modal/switch-org-modal.component.d.ts +5 -0
  26. package/lib/services/auth/oauth-callback.component.d.ts +6 -0
  27. package/lib/services/auth/oauth.service.d.ts +28 -0
  28. package/lib/services/auth/storage.service.d.ts +1 -1
  29. package/lib/services/auth/types.d.ts +6 -0
  30. package/lib/services/link-processor.service.d.ts +12 -0
  31. package/lib/services/org/types.d.ts +2 -0
  32. package/lib/services/requests/request-visibility.d.ts +35 -0
  33. package/lib/services/requests/types.d.ts +7 -2
  34. package/lib/version.d.ts +1 -1
  35. package/package.json +1 -1
  36. package/public-api.d.ts +1 -0
  37. package/src/lib/services/firebase/firebase-messaging-sw.js +145 -0
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, InjectionToken, makeEnvironmentProviders, APP_INITIALIZER, ErrorHandler, Injectable, DestroyRef, Inject, signal, computed, PLATFORM_ID, isDevMode, Optional, runInInjectionContext, effect, Pipe, TemplateRef, ViewContainerRef, input, Directive, EventEmitter, Component, Input, Output, ChangeDetectionStrategy, HostBinding, HostListener, NgZone, ViewChild, ChangeDetectorRef, output, ContentChild, model, ViewEncapsulation, ElementRef, viewChild, untracked, Injector, isSignal } from '@angular/core';
2
+ import { inject, InjectionToken, makeEnvironmentProviders, APP_INITIALIZER, ErrorHandler, Injectable, DestroyRef, Inject, signal, computed, PLATFORM_ID, isDevMode, Optional, runInInjectionContext, effect, Pipe, TemplateRef, ViewContainerRef, input, Directive, EventEmitter, Component, Input, Output, ChangeDetectionStrategy, HostBinding, HostListener, SecurityContext, NgZone, ViewChild, ChangeDetectorRef, output, ContentChild, model, ViewEncapsulation, ElementRef, viewChild, untracked, Injector, isSignal } from '@angular/core';
3
3
  import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, forkJoin, race, timer, shareReplay } from 'rxjs';
4
4
  import { catchError, switchMap, finalize, filter, take, map as map$1, tap, distinctUntilChanged as distinctUntilChanged$1, retry, timeout, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
5
5
  import * as i1$3 from '@angular/common/http';
@@ -56,7 +56,7 @@ import { BrowserMultiFormatReader } from '@zxing/browser';
56
56
  * Current version of valtech-components.
57
57
  * This is automatically updated during the publish process.
58
58
  */
59
- const VERSION = '4.0.33';
59
+ const VERSION = '4.0.35';
60
60
 
61
61
  // Control de estado de refresco (singleton a nivel de módulo)
62
62
  let isRefreshing = false;
@@ -2349,7 +2349,8 @@ class AuthStorageService {
2349
2349
  return;
2350
2350
  try {
2351
2351
  localStorage.setItem(this.keys.ACCESS_TOKEN, state.accessToken);
2352
- localStorage.setItem(this.keys.REFRESH_TOKEN, state.refreshToken);
2352
+ // H-05: el refresh token NO se persiste en localStorage — vive en una
2353
+ // cookie HttpOnly que el backend emite. Así un XSS no puede exfiltrarlo.
2353
2354
  localStorage.setItem(this.keys.ROLES, JSON.stringify(state.roles));
2354
2355
  localStorage.setItem(this.keys.PERMISSIONS, JSON.stringify(state.permissions));
2355
2356
  localStorage.setItem(this.keys.IS_SUPER_ADMIN, String(state.isSuperAdmin));
@@ -2407,15 +2408,10 @@ class AuthStorageService {
2407
2408
  /**
2408
2409
  * Guarda el refresh token (token rotation).
2409
2410
  */
2410
- saveRefreshToken(token) {
2411
- if (!this.isBrowser)
2412
- return;
2413
- try {
2414
- localStorage.setItem(this.keys.REFRESH_TOKEN, token);
2415
- }
2416
- catch (e) {
2417
- console.warn('[ValtechAuth] Error guardando refresh token:', e);
2418
- }
2411
+ saveRefreshToken(_token) {
2412
+ // H-05: no-op. El refresh token ya no se persiste en localStorage; lo
2413
+ // transporta una cookie HttpOnly emitida por el backend. Se mantiene el
2414
+ // método para no romper llamadores existentes durante la transición.
2419
2415
  }
2420
2416
  /**
2421
2417
  * Guarda los permisos actualizados.
@@ -6442,6 +6438,13 @@ class OAuthService {
6442
6438
  this.popup = null;
6443
6439
  this.messageHandler = null;
6444
6440
  this.checkClosedInterval = null;
6441
+ /**
6442
+ * Nonce del flujo OAuth en curso (M-07). Vive SOLO en memoria — un XSS no lo
6443
+ * puede leer de localStorage/sessionStorage. Viaja al backend (client_nonce),
6444
+ * vuelve en el callback, y se verifica acá → rechaza un oauth_callback_data
6445
+ * forjado por XSS.
6446
+ */
6447
+ this.expectedNonce = null;
6445
6448
  }
6446
6449
  /**
6447
6450
  * Inicia flujo OAuth en popup.
@@ -6452,9 +6455,14 @@ class OAuthService {
6452
6455
  */
6453
6456
  startFlow(provider) {
6454
6457
  return new Observable(observer => {
6458
+ // Nonce de flujo (M-07): vive solo en este objeto (no en storage), viaja
6459
+ // al backend en client_nonce y vuelve en el callback. El opener lo verifica
6460
+ // → un oauth_callback_data forjado en localStorage por XSS no lo conoce.
6461
+ const nonce = this.generateNonce();
6462
+ this.expectedNonce = nonce;
6455
6463
  // Construir URL de inicio
6456
6464
  const redirectUri = `${window.location.origin}/auth/oauth/callback`;
6457
- const startUrl = `${this.config?.apiUrl}/v2/auth/oauth/${provider}/start?redirect_uri=${encodeURIComponent(redirectUri)}`;
6465
+ const startUrl = `${this.config?.apiUrl}/v2/auth/oauth/${provider}/start?redirect_uri=${encodeURIComponent(redirectUri)}&client_nonce=${encodeURIComponent(nonce)}`;
6458
6466
  // Abrir popup centrado
6459
6467
  const width = 500;
6460
6468
  const height = 600;
@@ -6480,6 +6488,11 @@ class OAuthService {
6480
6488
  if (data?.type !== 'oauth-callback') {
6481
6489
  return;
6482
6490
  }
6491
+ // Verificar el nonce del flujo (M-07): si no coincide, no es nuestro
6492
+ // callback → ignorar (no consumir el observable).
6493
+ if (!this.isNonceValid(data)) {
6494
+ return;
6495
+ }
6483
6496
  // Limpiar
6484
6497
  this.cleanup();
6485
6498
  // Emitir resultado dentro de NgZone para trigger change detection
@@ -6512,11 +6525,16 @@ class OAuthService {
6512
6525
  observer.error(storedData.error);
6513
6526
  }
6514
6527
  else if (storedData.tokens) {
6515
- console.log('[OAuthService] Retrieved tokens from localStorage fallback', {
6516
- keys: Object.keys(storedData.tokens),
6517
- hasFirebaseToken: !!storedData.tokens.firebaseToken,
6518
- firebaseTokenLength: storedData.tokens.firebaseToken?.length ?? 0,
6519
- });
6528
+ // Gate de diagnóstico: la metadata de tokens (keys, presencia y
6529
+ // longitud del firebaseToken) no debe filtrarse a la consola en
6530
+ // producción (L4 del audit). Solo en dev.
6531
+ if (isDevMode()) {
6532
+ console.log('[OAuthService] Retrieved tokens from localStorage fallback', {
6533
+ keys: Object.keys(storedData.tokens),
6534
+ hasFirebaseToken: !!storedData.tokens.firebaseToken,
6535
+ firebaseTokenLength: storedData.tokens.firebaseToken?.length ?? 0,
6536
+ });
6537
+ }
6520
6538
  observer.next(storedData.tokens);
6521
6539
  observer.complete();
6522
6540
  }
@@ -6669,6 +6687,18 @@ class OAuthService {
6669
6687
  }
6670
6688
  const data = JSON.parse(dataStr);
6671
6689
  this.clearLocalStorageFallback();
6690
+ // El camino localStorage es el que un XSS podría forjar (M-07). El
6691
+ // postMessage ya valida origin + tipo; acá replicamos esas defensas y
6692
+ // sumamos la verificación del nonce de flujo + forma del payload.
6693
+ if (data?.type !== 'oauth-callback') {
6694
+ return null;
6695
+ }
6696
+ if (!this.isNonceValid(data)) {
6697
+ return null;
6698
+ }
6699
+ if (!this.isCallbackShapeValid(data)) {
6700
+ return null;
6701
+ }
6672
6702
  return data;
6673
6703
  }
6674
6704
  catch (e) {
@@ -6704,6 +6734,47 @@ class OAuthService {
6704
6734
  this.popup.close();
6705
6735
  }
6706
6736
  this.popup = null;
6737
+ // El nonce ya fue verificado (si correspondía) antes de llegar acá; se
6738
+ // limpia para no reusarlo en un flujo futuro (M-07).
6739
+ this.expectedNonce = null;
6740
+ }
6741
+ /**
6742
+ * Genera un nonce aleatorio (CSPRNG) url-safe para el flujo OAuth (M-07).
6743
+ */
6744
+ generateNonce() {
6745
+ const bytes = new Uint8Array(16);
6746
+ crypto.getRandomValues(bytes);
6747
+ let bin = '';
6748
+ bytes.forEach(b => (bin += String.fromCharCode(b)));
6749
+ // base64url sin padding.
6750
+ return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
6751
+ }
6752
+ /**
6753
+ * Verifica que el callback traiga el nonce del flujo actual (M-07).
6754
+ *
6755
+ * Estricto: si el opener generó un nonce (siempre, en clientes nuevos) exige
6756
+ * que el callback lo traiga y coincida. Un payload forjado en localStorage por
6757
+ * XSS no conoce el nonce (vive solo en memoria) → se rechaza.
6758
+ *
6759
+ * ⚠️ Requiere que el backend ya eche `client_nonce` en el callback. Deploy:
6760
+ * backend ANTES que el frontend, si no este check rechaza callbacks legítimos
6761
+ * (sin nonce) durante la ventana de transición.
6762
+ */
6763
+ isNonceValid(data) {
6764
+ if (!this.expectedNonce) {
6765
+ // No hay flujo con nonce activo (caso legacy / link flow) → no bloquear.
6766
+ return true;
6767
+ }
6768
+ return data?.nonce === this.expectedNonce;
6769
+ }
6770
+ /**
6771
+ * Valida la forma del payload del callback (M-07): debe ser un error o tokens
6772
+ * como objeto. Bloquea inyecciones malformadas (primitivos, shapes basura).
6773
+ */
6774
+ isCallbackShapeValid(data) {
6775
+ const hasError = !!data.error && typeof data.error === 'object';
6776
+ const hasTokens = !!data.tokens && typeof data.tokens === 'object';
6777
+ return hasError || hasTokens;
6707
6778
  }
6708
6779
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthService, deps: [{ token: VALTECH_AUTH_CONFIG, optional: true }, { token: i1$3.HttpClient }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
6709
6780
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthService, providedIn: 'root' }); }
@@ -8122,8 +8193,12 @@ class AuthService {
8122
8193
  this.setupFirestoreRBACSync(claims.uid);
8123
8194
  }
8124
8195
  }
8125
- else if (storedState.refreshToken) {
8126
- // 4. Token expirado pero hay refresh token.
8196
+ else if (storedState.accessToken) {
8197
+ // 4. Token de acceso expirado. El refresh token vive en la cookie
8198
+ // HttpOnly (H-05), no en storage — así que el disparador ya no es
8199
+ // "hay refresh en storage" sino "había un access token" (sesión
8200
+ // previa). El /refresh de fondo usa la cookie; si no es válida, falla
8201
+ // y caemos a signOut (catch más abajo).
8127
8202
  //
8128
8203
  // NO bloqueamos el bootstrap esperando la red: un /refresh en una
8129
8204
  // Lambda fría son 2-5s de pantalla en blanco (Angular no renderiza
@@ -8383,20 +8458,21 @@ class AuthService {
8383
8458
  * que el cliente debe guardar para el próximo refresh.
8384
8459
  */
8385
8460
  refreshAccessToken() {
8461
+ // H-05: el refresh token vive en una cookie HttpOnly (no en storage). El
8462
+ // gate ya NO exige tenerlo en memoria — la cookie lo aporta. Durante la
8463
+ // transición, si todavía hay un refresh en estado (sesión vieja), se manda
8464
+ // en el body (el backend prioriza body sobre cookie). withCredentials hace
8465
+ // que el browser envíe la cookie en la request cross-site.
8386
8466
  const refreshToken = this.state().refreshToken;
8387
- if (!refreshToken) {
8388
- return throwError(() => ({
8389
- code: 'NO_REFRESH_TOKEN',
8390
- message: 'No hay token de refresco',
8391
- }));
8392
- }
8393
- return this.http.post(`${this.baseUrl}/refresh`, { refreshToken }).pipe(tap(response => {
8467
+ const body = refreshToken ? { refreshToken } : {};
8468
+ return this.http
8469
+ .post(`${this.baseUrl}/refresh`, body, { withCredentials: true })
8470
+ .pipe(tap(response => {
8394
8471
  const expiresAt = Date.now() + response.expiresIn * 1000;
8395
- // Token rotation: guardar nuevo access token Y refresh token
8472
+ // Token rotation: el access token se guarda; el refresh rotado viaja en
8473
+ // la cookie HttpOnly (NO se persiste en localStorage — H-05).
8396
8474
  this.stateService.updateAccessToken(response.accessToken, response.expiresIn);
8397
- this.stateService.updateRefreshToken(response.refreshToken); // NUEVO: guardar refresh rotado
8398
8475
  this.storageService.saveAccessToken(response.accessToken, expiresAt);
8399
- this.storageService.saveRefreshToken(response.refreshToken); // NUEVO: persistir refresh rotado
8400
8476
  if (this.config?.enableFirebaseIntegration && response.firebaseToken) {
8401
8477
  this.signInWithFirebase(response.firebaseToken);
8402
8478
  }
@@ -8472,13 +8548,16 @@ class AuthService {
8472
8548
  const refreshToken = this.state().refreshToken;
8473
8549
  // Eliminar dispositivo del backend antes de cerrar sesión
8474
8550
  await this.unregisterDevice();
8475
- // Notificar al backend (fire and forget)
8476
- if (refreshToken) {
8477
- this.http
8478
- .post(`${this.baseUrl}/logout`, { refreshToken })
8479
- .pipe(catchError(() => of(null)))
8480
- .subscribe();
8481
- }
8551
+ // Notificar al backend (fire and forget). El refresh token va por la cookie
8552
+ // HttpOnly (H-05); si aún hay uno en estado (sesión vieja) se manda en el
8553
+ // body. El backend revoca la sesión y expira la cookie. withCredentials
8554
+ // envía la cookie cross-site.
8555
+ this.http
8556
+ .post(`${this.baseUrl}/logout`, refreshToken ? { refreshToken } : {}, {
8557
+ withCredentials: true,
8558
+ })
8559
+ .pipe(catchError(() => of(null)))
8560
+ .subscribe();
8482
8561
  // Cerrar sesión de Firebase si está integrado
8483
8562
  this.signOutFirebase();
8484
8563
  this.clearState();
@@ -11514,7 +11593,7 @@ class LinkProcessorService {
11514
11593
  : '';
11515
11594
  const typeClass = isExternal ? externalLinkClass : internalLinkClass;
11516
11595
  const classes = `${linkClass} ${typeClass}`.trim();
11517
- const linkHtml = `<a href="${url}"${target} class="${classes}">${linkText}</a>`;
11596
+ const linkHtml = `<a href="${this.safeHref(url)}"${target} class="${classes}">${this.escapeHtml(linkText)}</a>`;
11518
11597
  processedText =
11519
11598
  processedText.substring(0, startIndex) + linkHtml + processedText.substring(endIndex);
11520
11599
  }
@@ -11548,7 +11627,7 @@ class LinkProcessorService {
11548
11627
  hasLinks = true;
11549
11628
  const target = openExternalInNewTab ? ' target="_blank" rel="noopener noreferrer"' : '';
11550
11629
  const classes = `${linkClass} ${externalLinkClass}`.trim();
11551
- const linkHtml = `<a href="${cleanUrl}"${target} class="${classes}">${cleanUrl}</a>`;
11630
+ const linkHtml = `<a href="${this.safeHref(cleanUrl)}"${target} class="${classes}">${this.escapeHtml(cleanUrl)}</a>`;
11552
11631
  // Reemplazar el URL original con el enlace + puntuación si existía
11553
11632
  const replacement = punctuationRemoved ? linkHtml + punctuation : linkHtml;
11554
11633
  processedText =
@@ -11579,7 +11658,7 @@ class LinkProcessorService {
11579
11658
  hasLinks = true;
11580
11659
  const target = openInternalInNewTab ? ' target="_blank"' : '';
11581
11660
  const classes = `${linkClass} ${internalLinkClass}`.trim();
11582
- const linkHtml = `<a href="${route}"${target} class="${classes}">${route}</a>`;
11661
+ const linkHtml = `<a href="${this.safeHref(route)}"${target} class="${classes}">${this.escapeHtml(route)}</a>`;
11583
11662
  const replacement = `${prefix}${linkHtml}`;
11584
11663
  processedText =
11585
11664
  processedText.substring(0, startIndex) + replacement + processedText.substring(endIndex);
@@ -11590,6 +11669,28 @@ class LinkProcessorService {
11590
11669
  }
11591
11670
  return text;
11592
11671
  }
11672
+ /**
11673
+ * Devuelve un valor seguro para el atributo href. Como processLinks usa
11674
+ * bypassSecurityTrustHtml (desactiva la sanitización automática de Angular),
11675
+ * cada URL se valida explícitamente: el sanitizador de Angular neutraliza
11676
+ * esquemas peligrosos (javascript:, data:, vbscript:) prefijándolos con
11677
+ * `unsafe:`, y luego se escapa el resultado para el contexto de atributo —
11678
+ * evitando que una URL como `[x](javascript:alert(1))` o `http://a" onx="..."`
11679
+ * inyecte script o atributos.
11680
+ */
11681
+ safeHref(url) {
11682
+ const sanitized = this.sanitizer.sanitize(SecurityContext.URL, url) ?? '';
11683
+ return this.escapeHtml(sanitized);
11684
+ }
11685
+ /** Escapa los caracteres especiales de HTML (texto y valores de atributo). */
11686
+ escapeHtml(value) {
11687
+ return value
11688
+ .replace(/&/g, '&amp;')
11689
+ .replace(/</g, '&lt;')
11690
+ .replace(/>/g, '&gt;')
11691
+ .replace(/"/g, '&quot;')
11692
+ .replace(/'/g, '&#39;');
11693
+ }
11593
11694
  /**
11594
11695
  * Detecta si un texto contiene enlaces (URLs, rutas internas o enlaces Markdown).
11595
11696
  *
@@ -32719,12 +32820,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
32719
32820
  class OAuthCallbackComponent {
32720
32821
  constructor() {
32721
32822
  this.message = 'Procesando autenticación...';
32823
+ /**
32824
+ * Nonce que el opener generó y pasó por el flujo (round-trip backend). Se
32825
+ * reenvía al opener en cada mensaje para que verifique el origen del callback
32826
+ * y rechace un oauth_callback_data forjado en localStorage por XSS (M-07).
32827
+ */
32828
+ this.clientNonce = null;
32722
32829
  }
32723
32830
  ngOnInit() {
32724
32831
  this.processCallback();
32725
32832
  }
32726
32833
  processCallback() {
32727
32834
  const params = new URLSearchParams(window.location.search);
32835
+ this.clientNonce = params.get('client_nonce');
32728
32836
  // DEBUG: Log all received params
32729
32837
  console.log('[OAuthCallback] URL params received:', {
32730
32838
  url: window.location.href,
@@ -32823,6 +32931,11 @@ class OAuthCallbackComponent {
32823
32931
  this.closeAfterDelay();
32824
32932
  }
32825
32933
  sendToParent(data) {
32934
+ // Adjuntar el nonce del opener (M-07) a TODO mensaje (success, MFA, error)
32935
+ // para que el opener verifique que el callback es de su propio flujo.
32936
+ if (this.clientNonce) {
32937
+ data.nonce = this.clientNonce;
32938
+ }
32826
32939
  // Siempre guardar en localStorage como fallback (para COOP issues)
32827
32940
  try {
32828
32941
  localStorage.setItem('oauth_callback_data', JSON.stringify(data));
@@ -47559,6 +47672,11 @@ addIcons({ businessOutline, checkmarkCircleOutline });
47559
47672
  * `Settings.SwitchOrg`.
47560
47673
  */
47561
47674
  class SwitchOrgModalComponent {
47675
+ onLogoError(orgId) {
47676
+ const next = new Set(this.failedLogos());
47677
+ next.add(orgId);
47678
+ this.failedLogos.set(next);
47679
+ }
47562
47680
  constructor() {
47563
47681
  this.i18n = inject(I18nService);
47564
47682
  this.auth = inject(AuthService);
@@ -47572,6 +47690,10 @@ class SwitchOrgModalComponent {
47572
47690
  this.query = signal('');
47573
47691
  this.switchingId = signal(null);
47574
47692
  this.user = computed(() => this.auth.user());
47693
+ /** Avatar de usuario que falló al cargar → cae a iniciales. */
47694
+ this.userAvatarFailed = signal(false);
47695
+ /** IDs de orgs cuyo logo falló al cargar → caen al ícono business-outline. */
47696
+ this.failedLogos = signal(new Set());
47575
47697
  this.userInitials = computed(() => {
47576
47698
  const name = this.user()?.name ?? this.user()?.email ?? '?';
47577
47699
  return name.slice(0, 2).toUpperCase();
@@ -47662,8 +47784,8 @@ class SwitchOrgModalComponent {
47662
47784
 
47663
47785
  <div class="user-strip">
47664
47786
  <div class="user-strip__avatar">
47665
- @if (user()?.avatarUrl) {
47666
- <img [src]="user()!.avatarUrl!" [alt]="user()?.name ?? ''" />
47787
+ @if (user()?.avatarUrl && !userAvatarFailed()) {
47788
+ <img [src]="user()!.avatarUrl!" [alt]="user()?.name ?? ''" (error)="userAvatarFailed.set(true)" />
47667
47789
  } @else {
47668
47790
  <span>{{ userInitials() }}</span>
47669
47791
  }
@@ -47710,8 +47832,8 @@ class SwitchOrgModalComponent {
47710
47832
  (click)="onSelect(org)"
47711
47833
  >
47712
47834
  <div class="org-row__icon">
47713
- @if (org.logoUrl) {
47714
- <img [src]="org.logoUrl" [alt]="org.name" />
47835
+ @if (org.logoUrl && !failedLogos().has(org.id)) {
47836
+ <img [src]="org.logoUrl" [alt]="org.name" (error)="onLogoError(org.id)" />
47715
47837
  } @else {
47716
47838
  <ion-icon name="business-outline" />
47717
47839
  }
@@ -47774,8 +47896,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
47774
47896
 
47775
47897
  <div class="user-strip">
47776
47898
  <div class="user-strip__avatar">
47777
- @if (user()?.avatarUrl) {
47778
- <img [src]="user()!.avatarUrl!" [alt]="user()?.name ?? ''" />
47899
+ @if (user()?.avatarUrl && !userAvatarFailed()) {
47900
+ <img [src]="user()!.avatarUrl!" [alt]="user()?.name ?? ''" (error)="userAvatarFailed.set(true)" />
47779
47901
  } @else {
47780
47902
  <span>{{ userInitials() }}</span>
47781
47903
  }
@@ -47822,8 +47944,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
47822
47944
  (click)="onSelect(org)"
47823
47945
  >
47824
47946
  <div class="org-row__icon">
47825
- @if (org.logoUrl) {
47826
- <img [src]="org.logoUrl" [alt]="org.name" />
47947
+ @if (org.logoUrl && !failedLogos().has(org.id)) {
47948
+ <img [src]="org.logoUrl" [alt]="org.name" (error)="onLogoError(org.id)" />
47827
47949
  } @else {
47828
47950
  <ion-icon name="business-outline" />
47829
47951
  }
@@ -48768,6 +48890,8 @@ const EDIT_ORG_MODAL_I18N = {
48768
48890
  saveSuccess: 'Organización actualizada.',
48769
48891
  saveError: 'No se pudo guardar. Intenta de nuevo.',
48770
48892
  fillRequired: 'Completa todos los campos requeridos.',
48893
+ removeLogo: 'Quitar logo',
48894
+ removeLogoSuccess: 'Logo eliminado.',
48771
48895
  },
48772
48896
  en: {
48773
48897
  title: 'Edit organization',
@@ -48783,6 +48907,8 @@ const EDIT_ORG_MODAL_I18N = {
48783
48907
  saveSuccess: 'Organization updated.',
48784
48908
  saveError: 'Could not save. Please try again.',
48785
48909
  fillRequired: 'Please fill in all required fields.',
48910
+ removeLogo: 'Remove logo',
48911
+ removeLogoSuccess: 'Logo removed.',
48786
48912
  },
48787
48913
  };
48788
48914
 
@@ -48816,6 +48942,7 @@ class EditOrgModalComponent {
48816
48942
  this.i18nNamespace = DEFAULT_NAMESPACE$c;
48817
48943
  this.saving = signal(false);
48818
48944
  this.logoUrl = signal(null);
48945
+ this.removingLogo = signal(false);
48819
48946
  this.orgInitials = computed(() => {
48820
48947
  const name = this.org?.name ?? '';
48821
48948
  return name.slice(0, 2).toUpperCase() || '?';
@@ -48952,6 +49079,28 @@ class EditOrgModalComponent {
48952
49079
  });
48953
49080
  }
48954
49081
  }
49082
+ async onRemoveLogo() {
49083
+ const orgId = this.org?.id ?? this.activeOrgId;
49084
+ if (!orgId || this.removingLogo())
49085
+ return;
49086
+ this.removingLogo.set(true);
49087
+ try {
49088
+ const updated = await firstValueFrom(this.orgService.updateOrg(orgId, { clearLogo: true }));
49089
+ this.logoUrl.set(null);
49090
+ this.toast.show({ message: this.t('removeLogoSuccess'), color: 'dark', duration: 3000 });
49091
+ this.onSuccess?.(updated);
49092
+ }
49093
+ catch (err) {
49094
+ this.errors.handle(err, {
49095
+ context: 'editOrgModal.removeLogo',
49096
+ fallbackKey: 'saveError',
49097
+ i18nNamespace: this.i18nNamespace,
49098
+ });
49099
+ }
49100
+ finally {
49101
+ this.removingLogo.set(false);
49102
+ }
49103
+ }
48955
49104
  dismiss() {
48956
49105
  this._modalRef?.dismiss(null, 'cancel');
48957
49106
  }
@@ -48959,7 +49108,7 @@ class EditOrgModalComponent {
48959
49108
  return this.i18n.t(key, this.i18nNamespace);
48960
49109
  }
48961
49110
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EditOrgModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
48962
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: EditOrgModalComponent, isStandalone: true, selector: "val-edit-org-modal", inputs: { _modalRef: "_modalRef", org: "org", onSuccess: "onSuccess", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
49111
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: EditOrgModalComponent, isStandalone: true, selector: "val-edit-org-modal", inputs: { _modalRef: "_modalRef", org: "org", onSuccess: "onSuccess", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
48963
49112
  <ion-header>
48964
49113
  <ion-toolbar>
48965
49114
  <ion-buttons slot="end">
@@ -48979,6 +49128,13 @@ class EditOrgModalComponent {
48979
49128
  (uploaded)="onLogoUploaded($event)"
48980
49129
  />
48981
49130
  </div>
49131
+ @if (logoUrl()) {
49132
+ <div class="logo-remove">
49133
+ <ion-button fill="clear" size="small" color="dark" [disabled]="removingLogo()" (click)="onRemoveLogo()">
49134
+ {{ t('removeLogo') }}
49135
+ </ion-button>
49136
+ </div>
49137
+ }
48982
49138
  <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
48983
49139
  <val-title
48984
49140
  [props]="{
@@ -48995,7 +49151,7 @@ class EditOrgModalComponent {
48995
49151
  </ion-button>
48996
49152
  </div>
48997
49153
  </ion-content>
48998
- `, isInline: true, styles: [".modal-close-bottom{margin-top:16px}.logo-upload-wrapper{display:flex;justify-content:center;padding:8px 0 16px}\n"], dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: AvatarUploadComponent, selector: "val-avatar-upload", inputs: ["props", "customPath", "customThumbPath", "skipBackendSync"], outputs: ["uploaded", "error", "uploadStart"] }] }); }
49154
+ `, isInline: true, styles: [".modal-close-bottom{margin-top:16px}.logo-upload-wrapper{display:flex;justify-content:center;padding:8px 0 4px}.logo-remove{display:flex;justify-content:center;margin-bottom:12px}\n"], dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: AvatarUploadComponent, selector: "val-avatar-upload", inputs: ["props", "customPath", "customThumbPath", "skipBackendSync"], outputs: ["uploaded", "error", "uploadStart"] }] }); }
48999
49155
  }
49000
49156
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EditOrgModalComponent, decorators: [{
49001
49157
  type: Component,
@@ -49029,6 +49185,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49029
49185
  (uploaded)="onLogoUploaded($event)"
49030
49186
  />
49031
49187
  </div>
49188
+ @if (logoUrl()) {
49189
+ <div class="logo-remove">
49190
+ <ion-button fill="clear" size="small" color="dark" [disabled]="removingLogo()" (click)="onRemoveLogo()">
49191
+ {{ t('removeLogo') }}
49192
+ </ion-button>
49193
+ </div>
49194
+ }
49032
49195
  <val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
49033
49196
  <val-title
49034
49197
  [props]="{
@@ -49045,7 +49208,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49045
49208
  </ion-button>
49046
49209
  </div>
49047
49210
  </ion-content>
49048
- `, styles: [".modal-close-bottom{margin-top:16px}.logo-upload-wrapper{display:flex;justify-content:center;padding:8px 0 16px}\n"] }]
49211
+ `, styles: [".modal-close-bottom{margin-top:16px}.logo-upload-wrapper{display:flex;justify-content:center;padding:8px 0 4px}.logo-remove{display:flex;justify-content:center;margin-bottom:12px}\n"] }]
49049
49212
  }], ctorParameters: () => [], propDecorators: { _modalRef: [{
49050
49213
  type: Input
49051
49214
  }], org: [{
@@ -51952,6 +52115,8 @@ class OrganizationViewComponent {
51952
52115
  };
51953
52116
  });
51954
52117
  this.org = signal(null);
52118
+ /** Logo de org que falló al cargar → oculta el bloque (cae a la card sin logo). */
52119
+ this.logoFailed = signal(false);
51955
52120
  this.loading = signal(false);
51956
52121
  this.leaving = signal(false);
51957
52122
  this.permissionsOpen = signal(false);
@@ -52323,6 +52488,7 @@ class OrganizationViewComponent {
52323
52488
  return new Promise(resolve => {
52324
52489
  this.orgService.getOrg(orgId).subscribe({
52325
52490
  next: org => {
52491
+ this.logoFailed.set(false);
52326
52492
  this.org.set(org);
52327
52493
  this.loading.set(false);
52328
52494
  resolve();
@@ -52495,9 +52661,9 @@ class OrganizationViewComponent {
52495
52661
  } @else {
52496
52662
  @if (org(); as o) {
52497
52663
  <div class="org-info-card">
52498
- @if (o.logoUrl) {
52664
+ @if (o.logoUrl && !logoFailed()) {
52499
52665
  <div class="org-info-logo">
52500
- <img class="org-logo-img" [src]="o.logoUrl" [alt]="o.name" />
52666
+ <img class="org-logo-img" [src]="o.logoUrl" [alt]="o.name" (error)="logoFailed.set(true)" />
52501
52667
  </div>
52502
52668
  }
52503
52669
  <div class="org-info-field">
@@ -52803,9 +52969,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
52803
52969
  } @else {
52804
52970
  @if (org(); as o) {
52805
52971
  <div class="org-info-card">
52806
- @if (o.logoUrl) {
52972
+ @if (o.logoUrl && !logoFailed()) {
52807
52973
  <div class="org-info-logo">
52808
- <img class="org-logo-img" [src]="o.logoUrl" [alt]="o.name" />
52974
+ <img class="org-logo-img" [src]="o.logoUrl" [alt]="o.name" (error)="logoFailed.set(true)" />
52809
52975
  </div>
52810
52976
  }
52811
52977
  <div class="org-info-field">
@@ -66588,6 +66754,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
66588
66754
  args: [{ providedIn: 'root' }]
66589
66755
  }] });
66590
66756
 
66757
+ const DEFAULT_VISIBILITY = 'ORG';
66758
+ /**
66759
+ * ¿Este viewer puede ENVIAR una solicitud de este tipo? Espeja el gate
66760
+ * server-side (`backend/.../request: submissionTargetOrg`). Es solo UX — el
66761
+ * backend siempre valida —, pero evita que cada app reimplemente (mal) el filtro:
66762
+ *
66763
+ * - PUBLIC → cualquiera (anónimo solo si allowAnonymous).
66764
+ * - AUTHENTICATED → logueado + verificado (cross-org).
66765
+ * - ORG/INTERNAL → miembro de la org dueña (activeOrg === type.orgId).
66766
+ */
66767
+ function canSubmitRequestType(type, ctx) {
66768
+ const visibility = type.visibility ?? DEFAULT_VISIBILITY;
66769
+ switch (visibility) {
66770
+ case 'PUBLIC':
66771
+ return ctx.isAuthenticated || type.allowAnonymous;
66772
+ case 'AUTHENTICATED':
66773
+ return ctx.isAuthenticated && (ctx.isVerified ?? true);
66774
+ case 'ORG':
66775
+ case 'INTERNAL':
66776
+ return ctx.isAuthenticated && (!type.orgId || ctx.activeOrg === type.orgId);
66777
+ default:
66778
+ return false;
66779
+ }
66780
+ }
66781
+ /** Filtra el catálogo a los tipos que el viewer puede enviar. */
66782
+ function selectableRequestTypes(types, ctx) {
66783
+ return types.filter(t => canSubmitRequestType(t, ctx));
66784
+ }
66785
+ /**
66786
+ * Modo de envío a usar: `'anonymous'` solo para tipos PUBLIC + allowAnonymous sin
66787
+ * sesión; en el resto `'authenticated'`. Mapea a
66788
+ * `RequestService.createAnonymousRequest` vs `createRequest`.
66789
+ */
66790
+ function requestSubmitMode(type, ctx) {
66791
+ const visibility = type.visibility ?? DEFAULT_VISIBILITY;
66792
+ if (visibility === 'PUBLIC' && !ctx.isAuthenticated && type.allowAnonymous) {
66793
+ return 'anonymous';
66794
+ }
66795
+ return 'authenticated';
66796
+ }
66797
+
66591
66798
  /**
66592
66799
  * Default values for ArticleStripMetadata.
66593
66800
  */
@@ -67584,5 +67791,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
67584
67791
  * Generated bundle index. Do not edit.
67585
67792
  */
67586
67793
 
67587
- export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, APP_VERSION_PLATFORM_PLUGIN, APP_VERSION_REMOTE_PLUGIN, 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, ApiKeyCreateModalComponent, ApiKeyService, ApiKeysModalComponent, ApiKeysViewComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, 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_BUTTON_PRESETS, 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_LOGIN_LOGO, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_PRESETS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DatePickerComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FieldListComponent, 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, HrefComponent, I18nService, IMAGE_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, IONIC_COLORS$5 as IONIC_COLORS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, 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, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PermissionCatalogService, PermissionSelectorComponent, PermissionsModalComponent, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QrScannerComponent, 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, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketCardComponent, 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_SETTINGS_MENU_LINKS, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VALTECH_WHATSAPP_CONFIG, VERSION, ValtechErrorService, WhatsappFabComponent, WhatsappService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, button, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createPageState, createPermissionLabeler, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, iconButton, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isIonicColor, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccountRoutes, provideValtechAds, provideValtechApiKeysRoutes, provideValtechAppConfig, provideValtechAppVersion, provideValtechAppVersionHttp, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, provideValtechWhatsapp, query, renderPatternSvgInner, replaceSpecialChars, resolveColor, resolveInputDefaultValue, resolveIonicColor, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
67794
+ export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, APP_VERSION_PLATFORM_PLUGIN, APP_VERSION_REMOTE_PLUGIN, 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, ApiKeyCreateModalComponent, ApiKeyService, ApiKeysModalComponent, ApiKeysViewComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, 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_BUTTON_PRESETS, 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_LOGIN_LOGO, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_PRESETS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DatePickerComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FieldListComponent, 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, HrefComponent, I18nService, IMAGE_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, IONIC_COLORS$5 as IONIC_COLORS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, 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, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PermissionCatalogService, PermissionSelectorComponent, PermissionsModalComponent, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QrScannerComponent, 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, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketCardComponent, 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_SETTINGS_MENU_LINKS, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VALTECH_WHATSAPP_CONFIG, VERSION, ValtechErrorService, WhatsappFabComponent, WhatsappService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, button, canSubmitRequestType, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createPageState, createPermissionLabeler, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, iconButton, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isIonicColor, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccountRoutes, provideValtechAds, provideValtechApiKeysRoutes, provideValtechAppConfig, provideValtechAppVersion, provideValtechAppVersionHttp, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, provideValtechWhatsapp, query, renderPatternSvgInner, replaceSpecialChars, requestSubmitMode, resolveColor, resolveInputDefaultValue, resolveIonicColor, resolveWebBaseUrl, roleGuard, selectableRequestTypes, storagePaths, superAdminGuard, toArticle };
67588
67795
  //# sourceMappingURL=valtech-components.mjs.map