valtech-components 2.0.969 → 2.0.971
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.
- package/esm2022/lib/components/molecules/content-reaction/content-reaction.component.mjs +2 -1
- package/esm2022/lib/components/organisms/profile-view/profile-view.component.mjs +601 -0
- package/esm2022/lib/components/organisms/profile-view/profile-view.i18n.mjs +75 -0
- package/esm2022/lib/components/organisms/profile-view/profile.routes.mjs +28 -0
- package/esm2022/lib/components/organisms/profile-view/types.mjs +2 -0
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +4 -1
- package/fesm2022/valtech-components.mjs +785 -106
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/organisms/profile-view/profile-view.component.d.ts +84 -0
- package/lib/components/organisms/profile-view/profile-view.i18n.d.ts +12 -0
- package/lib/components/organisms/profile-view/profile.routes.d.ts +23 -0
- package/lib/components/organisms/profile-view/types.d.ts +32 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +3 -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, ElementRef, Injector, isSignal, ViewEncapsulation } 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, ElementRef, viewChild, Injector, isSignal, ViewEncapsulation } from '@angular/core';
|
|
3
3
|
import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, shareReplay, race, timer, forkJoin } from 'rxjs';
|
|
4
4
|
import { catchError, switchMap, finalize, filter, take, map as map$1, tap, retry, timeout, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
|
|
5
5
|
import * as i1$3 from '@angular/common/http';
|
|
@@ -17,7 +17,7 @@ import * as i1$2 from '@angular/fire/storage';
|
|
|
17
17
|
import { provideStorage, getStorage, connectStorageEmulator, ref, uploadBytesResumable, getDownloadURL, getMetadata, deleteObject, listAll } from '@angular/fire/storage';
|
|
18
18
|
import { takeUntilDestroyed, toSignal, toObservable } from '@angular/core/rxjs-interop';
|
|
19
19
|
import * as i1$4 from '@angular/router';
|
|
20
|
-
import { Router, NavigationEnd, RouterLink, RouterOutlet, RouterModule } from '@angular/router';
|
|
20
|
+
import { Router, NavigationEnd, RouterLink, ActivatedRoute, RouterOutlet, RouterModule } from '@angular/router';
|
|
21
21
|
import * as i2 from '@ionic/angular';
|
|
22
22
|
import { IonicModule, ToastController, NavController } from '@ionic/angular';
|
|
23
23
|
import { isSupported, getMessaging as getMessaging$1 } from 'firebase/messaging';
|
|
@@ -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.
|
|
57
|
+
const VERSION = '2.0.971';
|
|
58
58
|
|
|
59
59
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
60
60
|
let isRefreshing = false;
|
|
@@ -41489,6 +41489,786 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
41489
41489
|
type: Input
|
|
41490
41490
|
}] } });
|
|
41491
41491
|
|
|
41492
|
+
/**
|
|
41493
|
+
* PageRefreshService — bus del pull-to-refresh estándar del factory.
|
|
41494
|
+
*
|
|
41495
|
+
* **Por qué existe:** `val-page-wrapper` posee un único `<ion-content>` y todas
|
|
41496
|
+
* las páginas se renderizan dentro vía `<router-outlet>`. Un `<ion-refresher>`
|
|
41497
|
+
* tiene que vivir dentro de ese `ion-content`, así que el refresher NO puede
|
|
41498
|
+
* declararse en el template de cada página — vive una sola vez en
|
|
41499
|
+
* `val-page-wrapper`. Este servicio es el puente: la página activa registra
|
|
41500
|
+
* *qué hacer* al refrescar; el page-wrapper dispara el gesto y cierra el spinner.
|
|
41501
|
+
*
|
|
41502
|
+
* **Uso en una página** (ver `frontend/CLAUDE.md` — es el patrón estándar):
|
|
41503
|
+
*
|
|
41504
|
+
* ```ts
|
|
41505
|
+
* export class MiPage implements ViewWillEnter, ViewWillLeave {
|
|
41506
|
+
* private pageRefresh = inject(PageRefreshService);
|
|
41507
|
+
*
|
|
41508
|
+
* ionViewWillEnter(): void {
|
|
41509
|
+
* this.pageRefresh.register(() => this.reload());
|
|
41510
|
+
* }
|
|
41511
|
+
* ionViewWillLeave(): void {
|
|
41512
|
+
* this.pageRefresh.unregister();
|
|
41513
|
+
* }
|
|
41514
|
+
*
|
|
41515
|
+
* private async reload(): Promise<void> {
|
|
41516
|
+
* // re-fetch / re-suscribir streams / etc.
|
|
41517
|
+
* }
|
|
41518
|
+
* }
|
|
41519
|
+
* ```
|
|
41520
|
+
*
|
|
41521
|
+
* Páginas que no llaman `register()` simplemente no muestran refresher —
|
|
41522
|
+
* opt-in, sin impacto.
|
|
41523
|
+
*/
|
|
41524
|
+
class PageRefreshService {
|
|
41525
|
+
constructor() {
|
|
41526
|
+
this.handler = null;
|
|
41527
|
+
/**
|
|
41528
|
+
* `true` cuando hay una página con handler registrado. `val-page-wrapper`
|
|
41529
|
+
* renderiza el `<val-refresher>` sólo cuando esto es `true`.
|
|
41530
|
+
*/
|
|
41531
|
+
this.hasHandler = signal(false);
|
|
41532
|
+
}
|
|
41533
|
+
/**
|
|
41534
|
+
* Registra el handler de refresh de la página activa. Llamar en
|
|
41535
|
+
* `ionViewWillEnter`. Si ya había uno, lo reemplaza (sólo hay una página
|
|
41536
|
+
* activa a la vez en el router-outlet de Ionic).
|
|
41537
|
+
*/
|
|
41538
|
+
register(handler) {
|
|
41539
|
+
this.handler = handler;
|
|
41540
|
+
this.hasHandler.set(true);
|
|
41541
|
+
}
|
|
41542
|
+
/**
|
|
41543
|
+
* Quita el handler. Llamar en `ionViewWillLeave` para que el refresher
|
|
41544
|
+
* desaparezca al salir de la vista.
|
|
41545
|
+
*/
|
|
41546
|
+
unregister() {
|
|
41547
|
+
this.handler = null;
|
|
41548
|
+
this.hasHandler.set(false);
|
|
41549
|
+
}
|
|
41550
|
+
/**
|
|
41551
|
+
* Ejecuta el handler registrado y espera a que termine. Lo invoca
|
|
41552
|
+
* `val-page-wrapper` al detectar el gesto de pull. No-op si no hay handler.
|
|
41553
|
+
*/
|
|
41554
|
+
async run() {
|
|
41555
|
+
if (!this.handler)
|
|
41556
|
+
return;
|
|
41557
|
+
await this.handler();
|
|
41558
|
+
}
|
|
41559
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PageRefreshService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
41560
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PageRefreshService, providedIn: 'root' }); }
|
|
41561
|
+
}
|
|
41562
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PageRefreshService, decorators: [{
|
|
41563
|
+
type: Injectable,
|
|
41564
|
+
args: [{ providedIn: 'root' }]
|
|
41565
|
+
}] });
|
|
41566
|
+
/**
|
|
41567
|
+
* Conecta una página al pull-to-refresh estándar en UNA línea, sin lifecycle
|
|
41568
|
+
* hooks manuales: registra el handler y lo desregistra al destruir la página
|
|
41569
|
+
* (vía `DestroyRef`).
|
|
41570
|
+
*
|
|
41571
|
+
* DEBE llamarse en un injection context (constructor o field initializer).
|
|
41572
|
+
*
|
|
41573
|
+
* @example
|
|
41574
|
+
* ```ts
|
|
41575
|
+
* export class MiPage {
|
|
41576
|
+
* private data = createRefreshableStream(() => this.svc.getX());
|
|
41577
|
+
* readonly items = this.data.data;
|
|
41578
|
+
*
|
|
41579
|
+
* constructor() {
|
|
41580
|
+
* connectPageRefresh(() => this.data.reload());
|
|
41581
|
+
* }
|
|
41582
|
+
* }
|
|
41583
|
+
* ```
|
|
41584
|
+
*
|
|
41585
|
+
* Reemplaza el patrón `ngOnInit`→`register` / `ngOnDestroy`→`unregister`.
|
|
41586
|
+
*/
|
|
41587
|
+
function connectPageRefresh(handler) {
|
|
41588
|
+
const svc = inject(PageRefreshService);
|
|
41589
|
+
const destroyRef = inject(DestroyRef);
|
|
41590
|
+
svc.register(handler);
|
|
41591
|
+
destroyRef.onDestroy(() => svc.unregister());
|
|
41592
|
+
}
|
|
41593
|
+
|
|
41594
|
+
/**
|
|
41595
|
+
* Defaults i18n (es/en) embebidos en `val-profile-view`. Auto-registrados en el
|
|
41596
|
+
* constructor del componente si el consumer no proveyó el namespace
|
|
41597
|
+
* (`Settings.Profile` por default). Garantiza que nunca haya una key faltante
|
|
41598
|
+
* evaluada por cada change-detection (ver nota en valtech-components/CLAUDE.md
|
|
41599
|
+
* sobre el storm de CD con `val-debug-console`). El consumer puede override
|
|
41600
|
+
* registrando el mismo namespace antes de que el componente monte.
|
|
41601
|
+
*
|
|
41602
|
+
* SOLO incluye las keys que la vista realmente usa.
|
|
41603
|
+
*/
|
|
41604
|
+
const PROFILE_VIEW_I18N = {
|
|
41605
|
+
es: {
|
|
41606
|
+
pageTitle: 'Perfil',
|
|
41607
|
+
pageDescription: 'Administra tu informacion personal',
|
|
41608
|
+
name: 'Nombre completo',
|
|
41609
|
+
namePlaceholder: 'Tu nombre completo',
|
|
41610
|
+
nameHint: 'Tu nombre real que se mostrara en tu perfil.',
|
|
41611
|
+
email: 'Correo electronico',
|
|
41612
|
+
handle: 'Nombre de usuario',
|
|
41613
|
+
handlePlaceholder: 'usuario',
|
|
41614
|
+
handleHint: 'Tu identificador unico (@usuario). Otros pueden buscarte con este nombre.',
|
|
41615
|
+
handleInUse: 'Ese nombre de usuario ya está tomado',
|
|
41616
|
+
handleError: 'Error al actualizar el nombre de usuario',
|
|
41617
|
+
phoneInUse: 'Ese teléfono ya está en uso por otra cuenta',
|
|
41618
|
+
phoneInvalid: 'Formato de teléfono inválido (usa E.164: +56...)',
|
|
41619
|
+
phone: 'Telefono',
|
|
41620
|
+
phonePlaceholder: '+1 234 567 8900',
|
|
41621
|
+
phoneHint: 'Opcional. Para recuperacion de cuenta y notificaciones.',
|
|
41622
|
+
save: 'Guardar cambios',
|
|
41623
|
+
saving: 'Guardando...',
|
|
41624
|
+
saveSuccess: 'Perfil actualizado',
|
|
41625
|
+
saveError: 'Error al actualizar el perfil',
|
|
41626
|
+
usernameInvalid: 'Solo letras, numeros y guion bajo',
|
|
41627
|
+
avatarSuccess: 'Foto actualizada',
|
|
41628
|
+
avatarError: 'Error al actualizar la foto',
|
|
41629
|
+
changePhoto: 'Cambiar foto',
|
|
41630
|
+
viewPhoto: 'Ver foto',
|
|
41631
|
+
errorTitle: 'No pudimos cargar tu perfil',
|
|
41632
|
+
offlineTitle: 'Sin conexión',
|
|
41633
|
+
offlineHint: 'Revisa tu conexión a internet e intenta nuevamente.',
|
|
41634
|
+
retry: 'Reintentar',
|
|
41635
|
+
},
|
|
41636
|
+
en: {
|
|
41637
|
+
pageTitle: 'Profile',
|
|
41638
|
+
pageDescription: 'Manage your personal information',
|
|
41639
|
+
name: 'Full name',
|
|
41640
|
+
namePlaceholder: 'Your full name',
|
|
41641
|
+
nameHint: 'Your real name that will be displayed on your profile.',
|
|
41642
|
+
email: 'Email',
|
|
41643
|
+
handle: 'Username',
|
|
41644
|
+
handlePlaceholder: 'username',
|
|
41645
|
+
handleHint: 'Your unique identifier (@username). Others can search for you with this name.',
|
|
41646
|
+
handleInUse: 'That username is already taken',
|
|
41647
|
+
handleError: 'Failed to update username',
|
|
41648
|
+
phoneInUse: 'That phone is already in use by another account',
|
|
41649
|
+
phoneInvalid: 'Invalid phone format (use E.164: +1...)',
|
|
41650
|
+
phone: 'Phone',
|
|
41651
|
+
phonePlaceholder: '+1 234 567 8900',
|
|
41652
|
+
phoneHint: 'Optional. For account recovery and notifications.',
|
|
41653
|
+
save: 'Save changes',
|
|
41654
|
+
saving: 'Saving...',
|
|
41655
|
+
saveSuccess: 'Profile updated',
|
|
41656
|
+
saveError: 'Failed to update profile',
|
|
41657
|
+
usernameInvalid: 'Only letters, numbers and underscore',
|
|
41658
|
+
avatarSuccess: 'Photo updated',
|
|
41659
|
+
avatarError: 'Failed to update photo',
|
|
41660
|
+
changePhoto: 'Change photo',
|
|
41661
|
+
viewPhoto: 'View photo',
|
|
41662
|
+
errorTitle: "We couldn't load your profile",
|
|
41663
|
+
offlineTitle: 'Offline',
|
|
41664
|
+
offlineHint: 'Check your internet connection and try again.',
|
|
41665
|
+
retry: 'Retry',
|
|
41666
|
+
},
|
|
41667
|
+
};
|
|
41668
|
+
|
|
41669
|
+
/** Patrón sincronizado con backend handleRegex: lowercase alfanum + underscore. */
|
|
41670
|
+
const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
|
|
41671
|
+
const HANDLE_MIN = 3;
|
|
41672
|
+
const HANDLE_MAX = 20;
|
|
41673
|
+
const DEFAULT_NAMESPACE = 'Settings.Profile';
|
|
41674
|
+
/**
|
|
41675
|
+
* `val-profile-view` — vista Perfil full-feature autocontenida (organism).
|
|
41676
|
+
*
|
|
41677
|
+
* Edita los campos públicos del usuario en UN solo formulario con UN solo botón
|
|
41678
|
+
* "Guardar". Wireup:
|
|
41679
|
+
* - Avatar — val-avatar-upload (Firebase Storage + backend sync). Separado del
|
|
41680
|
+
* form porque es upload sync independiente.
|
|
41681
|
+
* - Handle (@usuario) — InputType.HANDLE (val-username-input con check de
|
|
41682
|
+
* disponibilidad async resuelto vía AuthService inyectado).
|
|
41683
|
+
* - Nombre — InputType.TEXT.
|
|
41684
|
+
* - Teléfono — InputType.PHONE.
|
|
41685
|
+
*
|
|
41686
|
+
* Submit handler dispatcha:
|
|
41687
|
+
* 1. Si handle cambió → updateHandle PRIMERO (uniqueness).
|
|
41688
|
+
* 2. Si name o phone cambió → updateProfile con ambos.
|
|
41689
|
+
*
|
|
41690
|
+
* Lectura del perfil: suscribe al doc Firestore /users/{uid} (mirror que mantiene
|
|
41691
|
+
* el backend). Reactivo cross-tab + auto-refresca tras save. Fallback REST
|
|
41692
|
+
* (auth.getProfile) si el doc no existe o el listener falla.
|
|
41693
|
+
*
|
|
41694
|
+
* NO renderiza ion-content — vive dentro de val-page-wrapper.
|
|
41695
|
+
*
|
|
41696
|
+
* Auto-registra sus defaults i18n (es/en) en el constructor si el consumer no
|
|
41697
|
+
* proveyó el namespace configurado (default `Settings.Profile`).
|
|
41698
|
+
*/
|
|
41699
|
+
class ProfileViewComponent {
|
|
41700
|
+
/** Namespace i18n resuelto (capturado una vez para llamadas no-reactivas). */
|
|
41701
|
+
get ns() {
|
|
41702
|
+
return this.resolvedConfig().i18nNamespace;
|
|
41703
|
+
}
|
|
41704
|
+
constructor() {
|
|
41705
|
+
this.nav = inject(NavigationService);
|
|
41706
|
+
this.auth = inject(AuthService);
|
|
41707
|
+
this.i18n = inject(I18nService);
|
|
41708
|
+
this.toast = inject(ToastService);
|
|
41709
|
+
this.errors = inject(ValtechErrorService);
|
|
41710
|
+
this.firebase = inject(FirebaseService);
|
|
41711
|
+
this.collections = inject(FirestoreCollectionFactory);
|
|
41712
|
+
this.route = inject(ActivatedRoute, { optional: true });
|
|
41713
|
+
/** Config mergeada con defaults. @Input gana sobre route data. */
|
|
41714
|
+
this.resolvedConfig = computed(() => {
|
|
41715
|
+
const fromRoute = (this.route?.snapshot.data['profileConfig'] ?? {});
|
|
41716
|
+
const merged = { ...fromRoute, ...(this.config ?? {}) };
|
|
41717
|
+
return {
|
|
41718
|
+
showAvatar: merged.showAvatar ?? true,
|
|
41719
|
+
showHandle: merged.showHandle ?? true,
|
|
41720
|
+
showPhone: merged.showPhone ?? true,
|
|
41721
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
41722
|
+
onSaved: merged.onSaved,
|
|
41723
|
+
onAvatarUploaded: merged.onAvatarUploaded,
|
|
41724
|
+
};
|
|
41725
|
+
});
|
|
41726
|
+
/**
|
|
41727
|
+
* Colección global /users/{uid} (skipAppPrefix: true — el doc vive fuera de
|
|
41728
|
+
* apps/{appId}/... porque es cross-app, ver firestore.rules).
|
|
41729
|
+
*/
|
|
41730
|
+
this.usersCol = this.collections.create('users', {
|
|
41731
|
+
skipAppPrefix: true,
|
|
41732
|
+
});
|
|
41733
|
+
this.formCmp = viewChild('form');
|
|
41734
|
+
this.profile = signal(null);
|
|
41735
|
+
this.loading = signal(true);
|
|
41736
|
+
this.saving = signal(false);
|
|
41737
|
+
this.loadError = signal(null);
|
|
41738
|
+
this._userId = computed(() => this.auth.user()?.userId ?? null);
|
|
41739
|
+
this.errorState = computed(() => {
|
|
41740
|
+
this.i18n.lang();
|
|
41741
|
+
const err = this.loadError();
|
|
41742
|
+
if (!err)
|
|
41743
|
+
return null;
|
|
41744
|
+
return createErrorStateProps(err, {
|
|
41745
|
+
title: {
|
|
41746
|
+
offline: this.t('offlineTitle'),
|
|
41747
|
+
error: this.t('errorTitle'),
|
|
41748
|
+
},
|
|
41749
|
+
description: {
|
|
41750
|
+
offline: this.t('offlineHint'),
|
|
41751
|
+
error: interpretError(err).message,
|
|
41752
|
+
},
|
|
41753
|
+
retryLabel: this.t('retry'),
|
|
41754
|
+
onRetry: () => this.loadFromApi(),
|
|
41755
|
+
retrying: this.loading(),
|
|
41756
|
+
});
|
|
41757
|
+
});
|
|
41758
|
+
this.avatarProps = computed(() => {
|
|
41759
|
+
this.i18n.lang();
|
|
41760
|
+
const p = this.profile();
|
|
41761
|
+
return {
|
|
41762
|
+
currentUrl: p?.avatarUrl,
|
|
41763
|
+
initials: this.initialsFromName(p?.name),
|
|
41764
|
+
size: 96,
|
|
41765
|
+
editable: true,
|
|
41766
|
+
showViewButton: true,
|
|
41767
|
+
i18nNamespace: this.ns,
|
|
41768
|
+
};
|
|
41769
|
+
});
|
|
41770
|
+
this.formMeta = this.buildFormMeta();
|
|
41771
|
+
// Auto-registro i18n — respeta override del consumer. El namespace puede
|
|
41772
|
+
// venir de @Input.config o del route data; lo resolvemos acá.
|
|
41773
|
+
const ns = this.ns;
|
|
41774
|
+
if (!this.i18n.hasNamespace(ns)) {
|
|
41775
|
+
this.i18n.registerContent(ns, PROFILE_VIEW_I18N);
|
|
41776
|
+
}
|
|
41777
|
+
this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
|
|
41778
|
+
connectPageRefresh(() => this.loadFromApi());
|
|
41779
|
+
effect(onCleanup => {
|
|
41780
|
+
const uid = this._userId();
|
|
41781
|
+
if (!uid) {
|
|
41782
|
+
this.profile.set(null);
|
|
41783
|
+
this.loading.set(false);
|
|
41784
|
+
return;
|
|
41785
|
+
}
|
|
41786
|
+
this.loading.set(true);
|
|
41787
|
+
let sub = null;
|
|
41788
|
+
let cancelled = false;
|
|
41789
|
+
let apiFallbackUsed = false;
|
|
41790
|
+
void this.firebase.whenFirebaseAuthReady().then(() => {
|
|
41791
|
+
if (cancelled)
|
|
41792
|
+
return;
|
|
41793
|
+
sub = this.usersCol.watch(uid).subscribe({
|
|
41794
|
+
next: doc => {
|
|
41795
|
+
if (doc) {
|
|
41796
|
+
this.profile.set(this.mapMirrorToProfile(doc));
|
|
41797
|
+
this.loadError.set(null);
|
|
41798
|
+
this.loading.set(false);
|
|
41799
|
+
}
|
|
41800
|
+
else if (!apiFallbackUsed) {
|
|
41801
|
+
apiFallbackUsed = true;
|
|
41802
|
+
void this.loadFromApi();
|
|
41803
|
+
}
|
|
41804
|
+
},
|
|
41805
|
+
error: () => {
|
|
41806
|
+
if (!apiFallbackUsed) {
|
|
41807
|
+
apiFallbackUsed = true;
|
|
41808
|
+
void this.loadFromApi();
|
|
41809
|
+
}
|
|
41810
|
+
},
|
|
41811
|
+
});
|
|
41812
|
+
});
|
|
41813
|
+
onCleanup(() => {
|
|
41814
|
+
cancelled = true;
|
|
41815
|
+
sub?.unsubscribe();
|
|
41816
|
+
});
|
|
41817
|
+
}, { allowSignalWrites: true });
|
|
41818
|
+
// saving → mutar state (val-form.ngDoCheck lo detecta).
|
|
41819
|
+
effect(() => {
|
|
41820
|
+
const isSaving = this.saving();
|
|
41821
|
+
const state = isSaving ? ComponentStates.WORKING : ComponentStates.ENABLED;
|
|
41822
|
+
this.formMeta.state = state;
|
|
41823
|
+
this.formMeta.actions.state = state;
|
|
41824
|
+
this.formMeta.actions.text = isSaving ? this.t('saving') : this.t('save');
|
|
41825
|
+
});
|
|
41826
|
+
// profile loaded + form mounted → patchValue al FormGroup.
|
|
41827
|
+
effect(() => {
|
|
41828
|
+
const p = this.profile();
|
|
41829
|
+
const form = this.formCmp()?.Form;
|
|
41830
|
+
if (!p || !form)
|
|
41831
|
+
return;
|
|
41832
|
+
form.patchValue({
|
|
41833
|
+
handle: p.handle || '',
|
|
41834
|
+
name: p.name || '',
|
|
41835
|
+
phone: p.phone || '',
|
|
41836
|
+
}, { emitEvent: false });
|
|
41837
|
+
});
|
|
41838
|
+
// i18n.lang change → mutar labels (rare, no afecta typing).
|
|
41839
|
+
effect(() => {
|
|
41840
|
+
this.i18n.lang();
|
|
41841
|
+
this.applyI18nLabels();
|
|
41842
|
+
});
|
|
41843
|
+
}
|
|
41844
|
+
/** Construcción inicial — UNA vez. Gatea handle/phone según config. */
|
|
41845
|
+
buildFormMeta() {
|
|
41846
|
+
const cfg = this.resolvedConfig();
|
|
41847
|
+
const handleField = {
|
|
41848
|
+
token: 'profile-handle',
|
|
41849
|
+
name: 'handle',
|
|
41850
|
+
label: this.t('handle'),
|
|
41851
|
+
hint: this.t('handleHint'),
|
|
41852
|
+
placeholder: this.t('handlePlaceholder'),
|
|
41853
|
+
type: InputType.HANDLE,
|
|
41854
|
+
order: 1,
|
|
41855
|
+
validators: [
|
|
41856
|
+
Validators.required,
|
|
41857
|
+
Validators.minLength(HANDLE_MIN),
|
|
41858
|
+
Validators.maxLength(HANDLE_MAX),
|
|
41859
|
+
Validators.pattern(HANDLE_PATTERN),
|
|
41860
|
+
],
|
|
41861
|
+
errors: {
|
|
41862
|
+
required: this.t('usernameInvalid'),
|
|
41863
|
+
minlength: this.t('usernameInvalid'),
|
|
41864
|
+
maxlength: this.t('usernameInvalid'),
|
|
41865
|
+
pattern: this.t('usernameInvalid'),
|
|
41866
|
+
},
|
|
41867
|
+
value: '',
|
|
41868
|
+
range: { min: HANDLE_MIN, max: HANDLE_MAX },
|
|
41869
|
+
state: ComponentStates.ENABLED,
|
|
41870
|
+
};
|
|
41871
|
+
const nameField = {
|
|
41872
|
+
token: 'profile-name',
|
|
41873
|
+
name: 'name',
|
|
41874
|
+
label: this.t('name'),
|
|
41875
|
+
hint: this.t('nameHint'),
|
|
41876
|
+
placeholder: this.t('namePlaceholder'),
|
|
41877
|
+
type: InputType.TEXT,
|
|
41878
|
+
order: 2,
|
|
41879
|
+
validators: [Validators.required, Validators.minLength(2), Validators.maxLength(100)],
|
|
41880
|
+
errors: {},
|
|
41881
|
+
value: '',
|
|
41882
|
+
state: ComponentStates.ENABLED,
|
|
41883
|
+
};
|
|
41884
|
+
const phoneField = {
|
|
41885
|
+
token: 'profile-phone',
|
|
41886
|
+
name: 'phone',
|
|
41887
|
+
label: this.t('phone'),
|
|
41888
|
+
hint: this.t('phoneHint'),
|
|
41889
|
+
placeholder: this.t('phonePlaceholder'),
|
|
41890
|
+
type: InputType.PHONE,
|
|
41891
|
+
order: 3,
|
|
41892
|
+
validators: [],
|
|
41893
|
+
errors: {},
|
|
41894
|
+
value: '',
|
|
41895
|
+
state: ComponentStates.ENABLED,
|
|
41896
|
+
};
|
|
41897
|
+
// Filtrado por config: el name siempre va; handle/phone son opt-out.
|
|
41898
|
+
const fields = [];
|
|
41899
|
+
if (cfg.showHandle)
|
|
41900
|
+
fields.push(handleField);
|
|
41901
|
+
fields.push(nameField);
|
|
41902
|
+
if (cfg.showPhone)
|
|
41903
|
+
fields.push(phoneField);
|
|
41904
|
+
const submitButton = {
|
|
41905
|
+
text: this.t('save'),
|
|
41906
|
+
color: 'dark',
|
|
41907
|
+
type: 'submit',
|
|
41908
|
+
fill: 'solid',
|
|
41909
|
+
size: 'default',
|
|
41910
|
+
shape: 'round',
|
|
41911
|
+
expand: 'block',
|
|
41912
|
+
state: ComponentStates.ENABLED,
|
|
41913
|
+
token: 'profile-save',
|
|
41914
|
+
};
|
|
41915
|
+
return {
|
|
41916
|
+
name: '',
|
|
41917
|
+
state: ComponentStates.ENABLED,
|
|
41918
|
+
sections: [
|
|
41919
|
+
{
|
|
41920
|
+
name: '',
|
|
41921
|
+
order: 1,
|
|
41922
|
+
fields,
|
|
41923
|
+
},
|
|
41924
|
+
],
|
|
41925
|
+
actions: submitButton,
|
|
41926
|
+
};
|
|
41927
|
+
}
|
|
41928
|
+
/** Re-aplica labels/hints/placeholders en cambio de idioma. */
|
|
41929
|
+
applyI18nLabels() {
|
|
41930
|
+
const section = this.formMeta.sections[0];
|
|
41931
|
+
for (const field of section.fields) {
|
|
41932
|
+
switch (field.name) {
|
|
41933
|
+
case 'handle':
|
|
41934
|
+
field.label = this.t('handle');
|
|
41935
|
+
field.hint = this.t('handleHint');
|
|
41936
|
+
field.placeholder = this.t('handlePlaceholder');
|
|
41937
|
+
field.errors = {
|
|
41938
|
+
required: this.t('usernameInvalid'),
|
|
41939
|
+
minlength: this.t('usernameInvalid'),
|
|
41940
|
+
maxlength: this.t('usernameInvalid'),
|
|
41941
|
+
pattern: this.t('usernameInvalid'),
|
|
41942
|
+
};
|
|
41943
|
+
break;
|
|
41944
|
+
case 'name':
|
|
41945
|
+
field.label = this.t('name');
|
|
41946
|
+
field.hint = this.t('nameHint');
|
|
41947
|
+
field.placeholder = this.t('namePlaceholder');
|
|
41948
|
+
break;
|
|
41949
|
+
case 'phone':
|
|
41950
|
+
field.label = this.t('phone');
|
|
41951
|
+
field.hint = this.t('phoneHint');
|
|
41952
|
+
field.placeholder = this.t('phonePlaceholder');
|
|
41953
|
+
break;
|
|
41954
|
+
}
|
|
41955
|
+
}
|
|
41956
|
+
const button = this.formMeta.actions;
|
|
41957
|
+
button.text = this.saving() ? this.t('saving') : this.t('save');
|
|
41958
|
+
}
|
|
41959
|
+
/**
|
|
41960
|
+
* Fallback REST. Capa 4 (UI declarativa): NO toasteamos acá — el
|
|
41961
|
+
* val-empty-state ES la UI del error.
|
|
41962
|
+
*/
|
|
41963
|
+
async loadFromApi() {
|
|
41964
|
+
this.loading.set(true);
|
|
41965
|
+
try {
|
|
41966
|
+
const p = await firstValueFrom(this.auth.getProfile());
|
|
41967
|
+
this.profile.set(p);
|
|
41968
|
+
this.loadError.set(null);
|
|
41969
|
+
}
|
|
41970
|
+
catch (err) {
|
|
41971
|
+
this.loadError.set(err);
|
|
41972
|
+
}
|
|
41973
|
+
finally {
|
|
41974
|
+
this.loading.set(false);
|
|
41975
|
+
}
|
|
41976
|
+
}
|
|
41977
|
+
mapMirrorToProfile(doc) {
|
|
41978
|
+
return {
|
|
41979
|
+
operationId: '',
|
|
41980
|
+
userId: doc.userId,
|
|
41981
|
+
email: doc.email,
|
|
41982
|
+
name: doc.name ?? '',
|
|
41983
|
+
handle: doc.handle,
|
|
41984
|
+
phone: doc.phone,
|
|
41985
|
+
avatarUrl: doc.avatarUrl,
|
|
41986
|
+
emailVerified: false,
|
|
41987
|
+
phoneVerified: false,
|
|
41988
|
+
mfaEnabled: false,
|
|
41989
|
+
createdAt: '',
|
|
41990
|
+
updatedAt: '',
|
|
41991
|
+
};
|
|
41992
|
+
}
|
|
41993
|
+
async onSubmit(event) {
|
|
41994
|
+
if (this.saving())
|
|
41995
|
+
return;
|
|
41996
|
+
const current = this.profile();
|
|
41997
|
+
if (!current)
|
|
41998
|
+
return;
|
|
41999
|
+
const cfg = this.resolvedConfig();
|
|
42000
|
+
const fields = event.fields || {};
|
|
42001
|
+
const nextHandle = String(fields['handle'] || '')
|
|
42002
|
+
.trim()
|
|
42003
|
+
.toLowerCase();
|
|
42004
|
+
const nextName = String(fields['name'] || '').trim();
|
|
42005
|
+
const nextPhone = String(fields['phone'] || '').trim();
|
|
42006
|
+
const handleChanged = cfg.showHandle && !!nextHandle && nextHandle !== (current.handle || '').toLowerCase();
|
|
42007
|
+
const nameChanged = !!nextName && nextName !== (current.name || '');
|
|
42008
|
+
const phoneChanged = cfg.showPhone && nextPhone !== (current.phone || '');
|
|
42009
|
+
if (!handleChanged && !nameChanged && !phoneChanged) {
|
|
42010
|
+
this.toast.show({
|
|
42011
|
+
message: this.t('saveSuccess'),
|
|
42012
|
+
duration: 2000,
|
|
42013
|
+
color: 'dark',
|
|
42014
|
+
position: 'top',
|
|
42015
|
+
});
|
|
42016
|
+
return;
|
|
42017
|
+
}
|
|
42018
|
+
this.saving.set(true);
|
|
42019
|
+
try {
|
|
42020
|
+
if (handleChanged) {
|
|
42021
|
+
try {
|
|
42022
|
+
await firstValueFrom(this.auth.updateHandle(nextHandle));
|
|
42023
|
+
}
|
|
42024
|
+
catch (err) {
|
|
42025
|
+
this.errors.handle(err, {
|
|
42026
|
+
context: 'profile.updateHandle',
|
|
42027
|
+
i18nMap: { AUTH_USERNAME_EXISTS: 'handleInUse' },
|
|
42028
|
+
fallbackKey: 'handleError',
|
|
42029
|
+
i18nNamespace: this.ns,
|
|
42030
|
+
});
|
|
42031
|
+
await this.loadFromApi();
|
|
42032
|
+
return;
|
|
42033
|
+
}
|
|
42034
|
+
}
|
|
42035
|
+
if (nameChanged || phoneChanged) {
|
|
42036
|
+
const payload = {};
|
|
42037
|
+
if (nameChanged)
|
|
42038
|
+
payload.name = nextName;
|
|
42039
|
+
if (phoneChanged)
|
|
42040
|
+
payload.phone = nextPhone;
|
|
42041
|
+
try {
|
|
42042
|
+
await firstValueFrom(this.auth.updateProfile(payload));
|
|
42043
|
+
}
|
|
42044
|
+
catch (err) {
|
|
42045
|
+
this.errors.handle(err, {
|
|
42046
|
+
context: 'profile.updateProfile',
|
|
42047
|
+
i18nMap: {
|
|
42048
|
+
AUTHV2_PHONE_EXISTS: 'phoneInUse',
|
|
42049
|
+
AUTHV2_INVALID_PHONE: 'phoneInvalid',
|
|
42050
|
+
},
|
|
42051
|
+
fallbackKey: 'saveError',
|
|
42052
|
+
i18nNamespace: this.ns,
|
|
42053
|
+
});
|
|
42054
|
+
await this.loadFromApi();
|
|
42055
|
+
return;
|
|
42056
|
+
}
|
|
42057
|
+
}
|
|
42058
|
+
this.toast.show({
|
|
42059
|
+
message: this.t('saveSuccess'),
|
|
42060
|
+
duration: 2500,
|
|
42061
|
+
color: 'dark',
|
|
42062
|
+
position: 'top',
|
|
42063
|
+
});
|
|
42064
|
+
cfg.onSaved?.(this.profile() ?? current);
|
|
42065
|
+
}
|
|
42066
|
+
finally {
|
|
42067
|
+
this.saving.set(false);
|
|
42068
|
+
}
|
|
42069
|
+
}
|
|
42070
|
+
onAvatarUploaded(result) {
|
|
42071
|
+
this.toast.show({
|
|
42072
|
+
message: this.t('avatarSuccess'),
|
|
42073
|
+
duration: 2500,
|
|
42074
|
+
color: 'dark',
|
|
42075
|
+
position: 'top',
|
|
42076
|
+
});
|
|
42077
|
+
this.resolvedConfig().onAvatarUploaded?.(result);
|
|
42078
|
+
}
|
|
42079
|
+
onAvatarError(_err) {
|
|
42080
|
+
this.toast.show({
|
|
42081
|
+
message: this.t('avatarError'),
|
|
42082
|
+
duration: 3000,
|
|
42083
|
+
color: 'dark',
|
|
42084
|
+
position: 'top',
|
|
42085
|
+
});
|
|
42086
|
+
}
|
|
42087
|
+
/** Lee del namespace configurado. */
|
|
42088
|
+
t(key) {
|
|
42089
|
+
return this.i18n.t(key, this.ns);
|
|
42090
|
+
}
|
|
42091
|
+
initialsFromName(name) {
|
|
42092
|
+
if (!name)
|
|
42093
|
+
return '?';
|
|
42094
|
+
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
42095
|
+
if (parts.length === 0)
|
|
42096
|
+
return '?';
|
|
42097
|
+
const first = parts[0]?.[0] ?? '';
|
|
42098
|
+
const last = parts.length > 1 ? (parts[parts.length - 1]?.[0] ?? '') : '';
|
|
42099
|
+
return (first + last).toUpperCase() || '?';
|
|
42100
|
+
}
|
|
42101
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProfileViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
42102
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ProfileViewComponent, isStandalone: true, selector: "val-profile-view", inputs: { config: "config" }, viewQueries: [{ propertyName: "formCmp", first: true, predicate: ["form"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
42103
|
+
<div class="page">
|
|
42104
|
+
<header class="page-header">
|
|
42105
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: t('pageTitle') }" />
|
|
42106
|
+
<val-title
|
|
42107
|
+
[props]="{
|
|
42108
|
+
size: 'large',
|
|
42109
|
+
color: 'dark',
|
|
42110
|
+
bold: false,
|
|
42111
|
+
content: t('pageDescription'),
|
|
42112
|
+
}"
|
|
42113
|
+
/>
|
|
42114
|
+
</header>
|
|
42115
|
+
|
|
42116
|
+
@if (loading()) {
|
|
42117
|
+
<val-skeleton-layout [props]="{ preset: 'form', rows: 3, showAvatar: true }" />
|
|
42118
|
+
} @else if (errorState()) {
|
|
42119
|
+
<val-empty-state [props]="errorState()!" />
|
|
42120
|
+
} @else {
|
|
42121
|
+
@if (profile(); as p) {
|
|
42122
|
+
@if (resolvedConfig().showAvatar) {
|
|
42123
|
+
<section class="settings-section avatar-section">
|
|
42124
|
+
<val-avatar-upload
|
|
42125
|
+
[props]="avatarProps()"
|
|
42126
|
+
(uploaded)="onAvatarUploaded($event)"
|
|
42127
|
+
(error)="onAvatarError($event)"
|
|
42128
|
+
/>
|
|
42129
|
+
<div class="avatar-meta">
|
|
42130
|
+
<val-title
|
|
42131
|
+
[props]="{
|
|
42132
|
+
size: 'medium',
|
|
42133
|
+
color: 'dark',
|
|
42134
|
+
bold: true,
|
|
42135
|
+
content: p.name || '—',
|
|
42136
|
+
}"
|
|
42137
|
+
/>
|
|
42138
|
+
<val-text
|
|
42139
|
+
[props]="{
|
|
42140
|
+
size: 'small',
|
|
42141
|
+
color: 'dark',
|
|
42142
|
+
bold: false,
|
|
42143
|
+
content: p.email,
|
|
42144
|
+
}"
|
|
42145
|
+
/>
|
|
42146
|
+
@if (p.handle) {
|
|
42147
|
+
<val-text
|
|
42148
|
+
[props]="{
|
|
42149
|
+
size: 'small',
|
|
42150
|
+
color: 'dark',
|
|
42151
|
+
bold: true,
|
|
42152
|
+
content: '@' + p.handle,
|
|
42153
|
+
}"
|
|
42154
|
+
/>
|
|
42155
|
+
}
|
|
42156
|
+
</div>
|
|
42157
|
+
</section>
|
|
42158
|
+
}
|
|
42159
|
+
|
|
42160
|
+
<val-form #form [props]="formMeta" (onSubmit)="onSubmit($event)" />
|
|
42161
|
+
}
|
|
42162
|
+
}
|
|
42163
|
+
</div>
|
|
42164
|
+
`, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.page-header h1{font-size:22px;font-weight:700;margin:0 0 4px}.hint{font-size:13px;opacity:.7;margin:0 0 12px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: AvatarUploadComponent, selector: "val-avatar-upload", inputs: ["props"], outputs: ["uploaded", "error", "uploadStart"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { 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: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }] }); }
|
|
42165
|
+
}
|
|
42166
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProfileViewComponent, decorators: [{
|
|
42167
|
+
type: Component,
|
|
42168
|
+
args: [{ selector: 'val-profile-view', standalone: true, imports: [
|
|
42169
|
+
CommonModule,
|
|
42170
|
+
ReactiveFormsModule,
|
|
42171
|
+
AvatarUploadComponent,
|
|
42172
|
+
EmptyStateComponent,
|
|
42173
|
+
FormComponent,
|
|
42174
|
+
DisplayComponent,
|
|
42175
|
+
TextComponent,
|
|
42176
|
+
TitleComponent,
|
|
42177
|
+
SkeletonLayoutComponent,
|
|
42178
|
+
], template: `
|
|
42179
|
+
<div class="page">
|
|
42180
|
+
<header class="page-header">
|
|
42181
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: t('pageTitle') }" />
|
|
42182
|
+
<val-title
|
|
42183
|
+
[props]="{
|
|
42184
|
+
size: 'large',
|
|
42185
|
+
color: 'dark',
|
|
42186
|
+
bold: false,
|
|
42187
|
+
content: t('pageDescription'),
|
|
42188
|
+
}"
|
|
42189
|
+
/>
|
|
42190
|
+
</header>
|
|
42191
|
+
|
|
42192
|
+
@if (loading()) {
|
|
42193
|
+
<val-skeleton-layout [props]="{ preset: 'form', rows: 3, showAvatar: true }" />
|
|
42194
|
+
} @else if (errorState()) {
|
|
42195
|
+
<val-empty-state [props]="errorState()!" />
|
|
42196
|
+
} @else {
|
|
42197
|
+
@if (profile(); as p) {
|
|
42198
|
+
@if (resolvedConfig().showAvatar) {
|
|
42199
|
+
<section class="settings-section avatar-section">
|
|
42200
|
+
<val-avatar-upload
|
|
42201
|
+
[props]="avatarProps()"
|
|
42202
|
+
(uploaded)="onAvatarUploaded($event)"
|
|
42203
|
+
(error)="onAvatarError($event)"
|
|
42204
|
+
/>
|
|
42205
|
+
<div class="avatar-meta">
|
|
42206
|
+
<val-title
|
|
42207
|
+
[props]="{
|
|
42208
|
+
size: 'medium',
|
|
42209
|
+
color: 'dark',
|
|
42210
|
+
bold: true,
|
|
42211
|
+
content: p.name || '—',
|
|
42212
|
+
}"
|
|
42213
|
+
/>
|
|
42214
|
+
<val-text
|
|
42215
|
+
[props]="{
|
|
42216
|
+
size: 'small',
|
|
42217
|
+
color: 'dark',
|
|
42218
|
+
bold: false,
|
|
42219
|
+
content: p.email,
|
|
42220
|
+
}"
|
|
42221
|
+
/>
|
|
42222
|
+
@if (p.handle) {
|
|
42223
|
+
<val-text
|
|
42224
|
+
[props]="{
|
|
42225
|
+
size: 'small',
|
|
42226
|
+
color: 'dark',
|
|
42227
|
+
bold: true,
|
|
42228
|
+
content: '@' + p.handle,
|
|
42229
|
+
}"
|
|
42230
|
+
/>
|
|
42231
|
+
}
|
|
42232
|
+
</div>
|
|
42233
|
+
</section>
|
|
42234
|
+
}
|
|
42235
|
+
|
|
42236
|
+
<val-form #form [props]="formMeta" (onSubmit)="onSubmit($event)" />
|
|
42237
|
+
}
|
|
42238
|
+
}
|
|
42239
|
+
</div>
|
|
42240
|
+
`, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.page-header h1{font-size:22px;font-weight:700;margin:0 0 4px}.hint{font-size:13px;opacity:.7;margin:0 0 12px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"] }]
|
|
42241
|
+
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
42242
|
+
type: Input
|
|
42243
|
+
}] } });
|
|
42244
|
+
|
|
42245
|
+
/**
|
|
42246
|
+
* Helper para montar la vista Perfil (`val-profile-view`) como ruta en una app
|
|
42247
|
+
* del factory. El `config` se pasa por route `data` (`profileConfig`) y el
|
|
42248
|
+
* componente lo lee como fallback de su `@Input() config`.
|
|
42249
|
+
*
|
|
42250
|
+
* @example
|
|
42251
|
+
* ```ts
|
|
42252
|
+
* // settings.routes.ts
|
|
42253
|
+
* export const settingsRoutes: Routes = [
|
|
42254
|
+
* ...provideValtechProfileRoutes(),
|
|
42255
|
+
* { path: 'preferences', loadComponent: () => ... },
|
|
42256
|
+
* ];
|
|
42257
|
+
*
|
|
42258
|
+
* // con config acotada:
|
|
42259
|
+
* ...provideValtechProfileRoutes({ config: { showPhone: false } }),
|
|
42260
|
+
* ```
|
|
42261
|
+
*/
|
|
42262
|
+
function provideValtechProfileRoutes(opts) {
|
|
42263
|
+
return [
|
|
42264
|
+
{
|
|
42265
|
+
path: opts?.path ?? 'profile',
|
|
42266
|
+
component: ProfileViewComponent,
|
|
42267
|
+
data: { profileConfig: opts?.config },
|
|
42268
|
+
},
|
|
42269
|
+
];
|
|
42270
|
+
}
|
|
42271
|
+
|
|
41492
42272
|
/** Built-in label sets for the three platform locales. */
|
|
41493
42273
|
const CALLOUT_LABELS = {
|
|
41494
42274
|
es: {
|
|
@@ -42451,108 +43231,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
42451
43231
|
type: Output
|
|
42452
43232
|
}] } });
|
|
42453
43233
|
|
|
42454
|
-
/**
|
|
42455
|
-
* PageRefreshService — bus del pull-to-refresh estándar del factory.
|
|
42456
|
-
*
|
|
42457
|
-
* **Por qué existe:** `val-page-wrapper` posee un único `<ion-content>` y todas
|
|
42458
|
-
* las páginas se renderizan dentro vía `<router-outlet>`. Un `<ion-refresher>`
|
|
42459
|
-
* tiene que vivir dentro de ese `ion-content`, así que el refresher NO puede
|
|
42460
|
-
* declararse en el template de cada página — vive una sola vez en
|
|
42461
|
-
* `val-page-wrapper`. Este servicio es el puente: la página activa registra
|
|
42462
|
-
* *qué hacer* al refrescar; el page-wrapper dispara el gesto y cierra el spinner.
|
|
42463
|
-
*
|
|
42464
|
-
* **Uso en una página** (ver `frontend/CLAUDE.md` — es el patrón estándar):
|
|
42465
|
-
*
|
|
42466
|
-
* ```ts
|
|
42467
|
-
* export class MiPage implements ViewWillEnter, ViewWillLeave {
|
|
42468
|
-
* private pageRefresh = inject(PageRefreshService);
|
|
42469
|
-
*
|
|
42470
|
-
* ionViewWillEnter(): void {
|
|
42471
|
-
* this.pageRefresh.register(() => this.reload());
|
|
42472
|
-
* }
|
|
42473
|
-
* ionViewWillLeave(): void {
|
|
42474
|
-
* this.pageRefresh.unregister();
|
|
42475
|
-
* }
|
|
42476
|
-
*
|
|
42477
|
-
* private async reload(): Promise<void> {
|
|
42478
|
-
* // re-fetch / re-suscribir streams / etc.
|
|
42479
|
-
* }
|
|
42480
|
-
* }
|
|
42481
|
-
* ```
|
|
42482
|
-
*
|
|
42483
|
-
* Páginas que no llaman `register()` simplemente no muestran refresher —
|
|
42484
|
-
* opt-in, sin impacto.
|
|
42485
|
-
*/
|
|
42486
|
-
class PageRefreshService {
|
|
42487
|
-
constructor() {
|
|
42488
|
-
this.handler = null;
|
|
42489
|
-
/**
|
|
42490
|
-
* `true` cuando hay una página con handler registrado. `val-page-wrapper`
|
|
42491
|
-
* renderiza el `<val-refresher>` sólo cuando esto es `true`.
|
|
42492
|
-
*/
|
|
42493
|
-
this.hasHandler = signal(false);
|
|
42494
|
-
}
|
|
42495
|
-
/**
|
|
42496
|
-
* Registra el handler de refresh de la página activa. Llamar en
|
|
42497
|
-
* `ionViewWillEnter`. Si ya había uno, lo reemplaza (sólo hay una página
|
|
42498
|
-
* activa a la vez en el router-outlet de Ionic).
|
|
42499
|
-
*/
|
|
42500
|
-
register(handler) {
|
|
42501
|
-
this.handler = handler;
|
|
42502
|
-
this.hasHandler.set(true);
|
|
42503
|
-
}
|
|
42504
|
-
/**
|
|
42505
|
-
* Quita el handler. Llamar en `ionViewWillLeave` para que el refresher
|
|
42506
|
-
* desaparezca al salir de la vista.
|
|
42507
|
-
*/
|
|
42508
|
-
unregister() {
|
|
42509
|
-
this.handler = null;
|
|
42510
|
-
this.hasHandler.set(false);
|
|
42511
|
-
}
|
|
42512
|
-
/**
|
|
42513
|
-
* Ejecuta el handler registrado y espera a que termine. Lo invoca
|
|
42514
|
-
* `val-page-wrapper` al detectar el gesto de pull. No-op si no hay handler.
|
|
42515
|
-
*/
|
|
42516
|
-
async run() {
|
|
42517
|
-
if (!this.handler)
|
|
42518
|
-
return;
|
|
42519
|
-
await this.handler();
|
|
42520
|
-
}
|
|
42521
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PageRefreshService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
42522
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PageRefreshService, providedIn: 'root' }); }
|
|
42523
|
-
}
|
|
42524
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PageRefreshService, decorators: [{
|
|
42525
|
-
type: Injectable,
|
|
42526
|
-
args: [{ providedIn: 'root' }]
|
|
42527
|
-
}] });
|
|
42528
|
-
/**
|
|
42529
|
-
* Conecta una página al pull-to-refresh estándar en UNA línea, sin lifecycle
|
|
42530
|
-
* hooks manuales: registra el handler y lo desregistra al destruir la página
|
|
42531
|
-
* (vía `DestroyRef`).
|
|
42532
|
-
*
|
|
42533
|
-
* DEBE llamarse en un injection context (constructor o field initializer).
|
|
42534
|
-
*
|
|
42535
|
-
* @example
|
|
42536
|
-
* ```ts
|
|
42537
|
-
* export class MiPage {
|
|
42538
|
-
* private data = createRefreshableStream(() => this.svc.getX());
|
|
42539
|
-
* readonly items = this.data.data;
|
|
42540
|
-
*
|
|
42541
|
-
* constructor() {
|
|
42542
|
-
* connectPageRefresh(() => this.data.reload());
|
|
42543
|
-
* }
|
|
42544
|
-
* }
|
|
42545
|
-
* ```
|
|
42546
|
-
*
|
|
42547
|
-
* Reemplaza el patrón `ngOnInit`→`register` / `ngOnDestroy`→`unregister`.
|
|
42548
|
-
*/
|
|
42549
|
-
function connectPageRefresh(handler) {
|
|
42550
|
-
const svc = inject(PageRefreshService);
|
|
42551
|
-
const destroyRef = inject(DestroyRef);
|
|
42552
|
-
svc.register(handler);
|
|
42553
|
-
destroyRef.onDestroy(() => svc.unregister());
|
|
42554
|
-
}
|
|
42555
|
-
|
|
42556
43234
|
class NetworkStatusService {
|
|
42557
43235
|
constructor() {
|
|
42558
43236
|
this._isOnline = signal(typeof navigator !== 'undefined' ? navigator.onLine : true);
|
|
@@ -48790,6 +49468,7 @@ class ContentReactionComponent {
|
|
|
48790
49468
|
...s,
|
|
48791
49469
|
isLoading: false,
|
|
48792
49470
|
isSubmitted: true,
|
|
49471
|
+
comment: '',
|
|
48793
49472
|
hadPreviousReaction: !useAnonymous, // Solo para autenticados
|
|
48794
49473
|
}));
|
|
48795
49474
|
this.reactionSubmit.emit({
|
|
@@ -52654,5 +53333,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
52654
53333
|
* Generated bundle index. Do not edit.
|
|
52655
53334
|
*/
|
|
52656
53335
|
|
|
52657
|
-
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, 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, 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, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgService, OrgSwitchService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, 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, SegmentControlComponent, SelectSearchComponent, SessionService, 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, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildFooterLinks, buildPath, 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, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechPresets, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
53336
|
+
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, 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, 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, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgService, OrgSwitchService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, 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, 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, SegmentControlComponent, SelectSearchComponent, SessionService, 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, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildFooterLinks, buildPath, 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, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechPresets, provideValtechProfileRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
52658
53337
|
//# sourceMappingURL=valtech-components.mjs.map
|