valtech-components 2.0.498 → 2.0.501
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/atoms/button/button.component.mjs +87 -48
- package/esm2022/lib/components/molecules/action-header/action-header.component.mjs +1 -1
- package/esm2022/lib/components/molecules/ad-slot/ad-slot.component.mjs +249 -0
- package/esm2022/lib/components/molecules/button-group/button-group.component.mjs +1 -1
- package/esm2022/lib/components/molecules/card/card.component.mjs +2 -2
- package/esm2022/lib/components/molecules/file-input/file-input.component.mjs +1 -1
- package/esm2022/lib/components/molecules/raffle-status-card/raffle-status-card.component.mjs +2 -2
- package/esm2022/lib/components/organisms/article/article.component.mjs +2 -2
- package/esm2022/lib/components/organisms/menu/menu.component.mjs +1 -1
- package/esm2022/lib/components/templates/page-template/page-template.component.mjs +1 -1
- package/esm2022/lib/services/ads/ads-consent.service.mjs +152 -0
- package/esm2022/lib/services/ads/ads-loader.service.mjs +160 -0
- package/esm2022/lib/services/ads/ads.service.mjs +449 -0
- package/esm2022/lib/services/ads/config.mjs +118 -0
- package/esm2022/lib/services/ads/index.mjs +14 -0
- package/esm2022/lib/services/ads/types.mjs +23 -0
- package/esm2022/lib/services/auth/auth.service.mjs +103 -6
- package/esm2022/lib/services/auth/index.mjs +4 -1
- package/esm2022/lib/services/auth/oauth-callback.component.mjs +141 -0
- package/esm2022/lib/services/auth/oauth.service.mjs +250 -0
- package/esm2022/lib/services/auth/types.mjs +1 -1
- package/esm2022/lib/services/firebase/analytics-error-handler.mjs +141 -0
- package/esm2022/lib/services/firebase/analytics-router-tracker.mjs +99 -0
- package/esm2022/lib/services/firebase/analytics.service.mjs +597 -0
- package/esm2022/lib/services/firebase/config.mjs +21 -2
- package/esm2022/lib/services/firebase/index.mjs +6 -1
- package/esm2022/public-api.mjs +6 -1
- package/fesm2022/valtech-components.mjs +2739 -239
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/atoms/button/button.component.d.ts +30 -6
- package/lib/components/molecules/ad-slot/ad-slot.component.d.ts +78 -0
- package/lib/components/organisms/article/article.component.d.ts +3 -3
- package/lib/services/ads/ads-consent.service.d.ts +59 -0
- package/lib/services/ads/ads-loader.service.d.ts +46 -0
- package/lib/services/ads/ads.service.d.ts +123 -0
- package/lib/services/ads/config.d.ts +69 -0
- package/lib/services/ads/index.d.ts +10 -0
- package/lib/services/ads/types.d.ts +163 -0
- package/lib/services/auth/auth.service.d.ts +56 -3
- package/lib/services/auth/index.d.ts +2 -0
- package/lib/services/auth/oauth-callback.component.d.ts +34 -0
- package/lib/services/auth/oauth.service.d.ts +90 -0
- package/lib/services/auth/types.d.ts +69 -0
- package/lib/services/firebase/analytics-error-handler.d.ts +54 -0
- package/lib/services/firebase/analytics-router-tracker.d.ts +51 -0
- package/lib/services/firebase/analytics.service.d.ts +256 -0
- package/lib/services/firebase/index.d.ts +4 -0
- package/package.json +1 -1
- package/public-api.d.ts +2 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { EventEmitter, Component, Input, Output, Injectable,
|
|
2
|
+
import { EventEmitter, Component, Input, Output, Injectable, signal, makeEnvironmentProviders, APP_INITIALIZER, inject, HostListener, Pipe, ChangeDetectionStrategy, ViewChild, computed, ChangeDetectorRef, ElementRef, PLATFORM_ID, Inject, ErrorHandler, DestroyRef, InjectionToken, Optional, runInInjectionContext, effect, Injector } from '@angular/core';
|
|
3
3
|
import * as i2$1 from '@ionic/angular/standalone';
|
|
4
4
|
import { IonAvatar, IonCard, IonIcon, IonButton, IonSpinner, IonText, IonModal, IonHeader, IonToolbar, IonContent, IonButtons, IonTitle, IonProgressBar, IonSkeletonText, IonFab, IonFabButton, IonFabList, IonLabel, IonCardContent, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCheckbox, IonTextarea, IonDatetime, IonDatetimeButton, IonInput, IonSelect, IonSelectOption, IonRadioGroup, IonRadio, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonAccordion, IonAccordionGroup, IonItem, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonChip, IonPopover, IonList, IonNote, ToastController as ToastController$1, IonCol, IonRow, IonMenuButton, IonFooter, IonListHeader, IonInfiniteScroll, IonInfiniteScrollContent, IonGrid, MenuController, IonMenu, IonMenuToggle, AlertController } from '@ionic/angular/standalone';
|
|
5
5
|
import * as i1 from '@angular/common';
|
|
@@ -13,7 +13,7 @@ import * as i1$2 from '@angular/platform-browser';
|
|
|
13
13
|
import QRCodeStyling from 'qr-code-styling';
|
|
14
14
|
import * as i1$3 from '@angular/forms';
|
|
15
15
|
import { ReactiveFormsModule, FormsModule, FormControl, Validators } from '@angular/forms';
|
|
16
|
-
import { BehaviorSubject, filter, map, distinctUntilChanged, Subject, throwError, firstValueFrom, of, from } from 'rxjs';
|
|
16
|
+
import { BehaviorSubject, filter, map, distinctUntilChanged, Subject, throwError, Observable, firstValueFrom, of, from } from 'rxjs';
|
|
17
17
|
import * as i1$4 from 'ng-otp-input';
|
|
18
18
|
import { NgOtpInputComponent, NgOtpInputModule } from 'ng-otp-input';
|
|
19
19
|
import * as i2 from '@ionic/angular';
|
|
@@ -27,6 +27,7 @@ import 'prismjs/components/prism-typescript';
|
|
|
27
27
|
import 'prismjs/components/prism-bash';
|
|
28
28
|
import Swiper from 'swiper';
|
|
29
29
|
import { Navigation, Pagination, EffectFade, EffectCube, EffectCoverflow, EffectFlip, Autoplay } from 'swiper/modules';
|
|
30
|
+
import { Analytics, logEvent, setUserId, setUserProperties, provideAnalytics, getAnalytics } from '@angular/fire/analytics';
|
|
30
31
|
import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
|
|
31
32
|
import * as i1$5 from '@angular/fire/auth';
|
|
32
33
|
import { provideAuth, getAuth, connectAuthEmulator, authState, signInWithCustomToken, signOut } from '@angular/fire/auth';
|
|
@@ -35,7 +36,8 @@ import { provideFirestore, getFirestore, connectFirestoreEmulator, enableIndexed
|
|
|
35
36
|
import { provideMessaging, getMessaging, Messaging, getToken, deleteToken, onMessage } from '@angular/fire/messaging';
|
|
36
37
|
import * as i1$7 from '@angular/fire/storage';
|
|
37
38
|
import { provideStorage, getStorage, connectStorageEmulator, ref, uploadBytesResumable, getDownloadURL, getMetadata, deleteObject, listAll } from '@angular/fire/storage';
|
|
38
|
-
import {
|
|
39
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
40
|
+
import { filter as filter$1, catchError, switchMap, finalize, take, tap, map as map$1 } from 'rxjs/operators';
|
|
39
41
|
import * as i1$8 from '@angular/common/http';
|
|
40
42
|
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
41
43
|
|
|
@@ -243,6 +245,154 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
243
245
|
type: Output
|
|
244
246
|
}] } });
|
|
245
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Servicio para gestionar presets de componentes.
|
|
250
|
+
*
|
|
251
|
+
* Los presets permiten definir configuraciones reutilizables
|
|
252
|
+
* de componentes (tamaño, color, variante, etc.) que se pueden
|
|
253
|
+
* aplicar con un nombre semántico.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* // En un componente
|
|
257
|
+
* presets = inject(PresetService);
|
|
258
|
+
*
|
|
259
|
+
* // Obtener preset
|
|
260
|
+
* const buttonProps = this.presets.get<ButtonMetadata>('button', 'primary-action');
|
|
261
|
+
* // { size: 'large', color: 'primary', fill: 'solid' }
|
|
262
|
+
*/
|
|
263
|
+
class PresetService {
|
|
264
|
+
constructor() {
|
|
265
|
+
this._presets = signal({});
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Obtiene un preset específico para un componente
|
|
269
|
+
*
|
|
270
|
+
* @param component Tipo de componente (ej: 'button', 'card', 'input')
|
|
271
|
+
* @param presetName Nombre del preset (ej: 'primary-action', 'compact')
|
|
272
|
+
* @returns Propiedades del preset o objeto vacío si no existe
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* // Obtener preset de botón
|
|
276
|
+
* const props = presets.get<ButtonMetadata>('button', 'primary-action');
|
|
277
|
+
*
|
|
278
|
+
* // Usar en componente
|
|
279
|
+
* <val-button [props]="props"></val-button>
|
|
280
|
+
*/
|
|
281
|
+
get(component, presetName) {
|
|
282
|
+
const componentPresets = this._presets()[component];
|
|
283
|
+
if (!componentPresets) {
|
|
284
|
+
console.warn(`[presets] No presets registered for component: ${component}`);
|
|
285
|
+
return {};
|
|
286
|
+
}
|
|
287
|
+
const preset = componentPresets[presetName];
|
|
288
|
+
if (!preset) {
|
|
289
|
+
console.warn(`[presets] Preset '${presetName}' not found for component: ${component}`);
|
|
290
|
+
return {};
|
|
291
|
+
}
|
|
292
|
+
return preset;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Verifica si existe un preset
|
|
296
|
+
*/
|
|
297
|
+
has(component, presetName) {
|
|
298
|
+
return !!this._presets()[component]?.[presetName];
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Registra presets de la aplicación
|
|
302
|
+
*
|
|
303
|
+
* @param presets Configuración de presets
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* presets.registerPresets({
|
|
307
|
+
* button: {
|
|
308
|
+
* 'primary-action': { size: 'large', color: 'primary' },
|
|
309
|
+
* },
|
|
310
|
+
* card: {
|
|
311
|
+
* 'feature': { variant: 'elevated' },
|
|
312
|
+
* }
|
|
313
|
+
* });
|
|
314
|
+
*/
|
|
315
|
+
registerPresets(presets) {
|
|
316
|
+
this._presets.set(presets);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Agrega presets para un componente específico (merge con existentes)
|
|
320
|
+
*/
|
|
321
|
+
registerComponentPresets(component, presets) {
|
|
322
|
+
this._presets.update((current) => ({
|
|
323
|
+
...current,
|
|
324
|
+
[component]: {
|
|
325
|
+
...current[component],
|
|
326
|
+
...presets,
|
|
327
|
+
},
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Obtiene todos los nombres de presets para un componente
|
|
332
|
+
*/
|
|
333
|
+
getPresetNames(component) {
|
|
334
|
+
return Object.keys(this._presets()[component] || {});
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Obtiene todos los componentes con presets registrados
|
|
338
|
+
*/
|
|
339
|
+
getRegisteredComponents() {
|
|
340
|
+
return Object.keys(this._presets());
|
|
341
|
+
}
|
|
342
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
343
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, providedIn: 'root' }); }
|
|
344
|
+
}
|
|
345
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, decorators: [{
|
|
346
|
+
type: Injectable,
|
|
347
|
+
args: [{ providedIn: 'root' }]
|
|
348
|
+
}] });
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Configura el sistema de presets de Valtech Components.
|
|
352
|
+
*
|
|
353
|
+
* @param presets Configuración de presets por componente
|
|
354
|
+
* @returns Providers para agregar en app.config.ts
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* // app.config.ts
|
|
358
|
+
* import { provideValtechPresets } from 'valtech-components';
|
|
359
|
+
*
|
|
360
|
+
* export const appConfig: ApplicationConfig = {
|
|
361
|
+
* providers: [
|
|
362
|
+
* provideValtechPresets({
|
|
363
|
+
* button: {
|
|
364
|
+
* 'primary-action': { size: 'large', color: 'primary', fill: 'solid' },
|
|
365
|
+
* 'secondary': { size: 'medium', color: 'secondary', fill: 'outline' },
|
|
366
|
+
* 'danger': { size: 'medium', color: 'danger', fill: 'solid' },
|
|
367
|
+
* },
|
|
368
|
+
* card: {
|
|
369
|
+
* 'feature': { variant: 'elevated', padding: 'large' },
|
|
370
|
+
* 'compact': { variant: 'flat', padding: 'small' },
|
|
371
|
+
* },
|
|
372
|
+
* input: {
|
|
373
|
+
* 'form-field': { size: 'medium', fill: 'outline', labelPosition: 'floating' },
|
|
374
|
+
* }
|
|
375
|
+
* }),
|
|
376
|
+
* ]
|
|
377
|
+
* };
|
|
378
|
+
*/
|
|
379
|
+
function provideValtechPresets(presets) {
|
|
380
|
+
return makeEnvironmentProviders([
|
|
381
|
+
{
|
|
382
|
+
provide: APP_INITIALIZER,
|
|
383
|
+
useFactory: (presetService) => {
|
|
384
|
+
return () => {
|
|
385
|
+
presetService.registerPresets(presets);
|
|
386
|
+
};
|
|
387
|
+
},
|
|
388
|
+
deps: [PresetService],
|
|
389
|
+
multi: true,
|
|
390
|
+
},
|
|
391
|
+
]);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Service
|
|
395
|
+
|
|
246
396
|
const ENABLED = 'ENABLED';
|
|
247
397
|
const DISABLED = 'DISABLED';
|
|
248
398
|
const WORKING = 'WORKING';
|
|
@@ -477,7 +627,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
477
627
|
/**
|
|
478
628
|
* val-button
|
|
479
629
|
*
|
|
480
|
-
* A customizable button supporting icons, loading state, and
|
|
630
|
+
* A customizable button supporting icons, loading state, navigation, and presets.
|
|
631
|
+
*
|
|
632
|
+
* @example With preset (recommended with i18n):
|
|
633
|
+
* <val-button preset="primary-action" [props]="{ text: 'Submit' | t }"></val-button>
|
|
481
634
|
*
|
|
482
635
|
* @example Static text:
|
|
483
636
|
* <val-button [props]="{
|
|
@@ -488,41 +641,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
488
641
|
* icon: { name: 'save', slot: 'start' }
|
|
489
642
|
* }" (onClick)="handler()"></val-button>
|
|
490
643
|
*
|
|
491
|
-
* @input
|
|
644
|
+
* @input preset: string - Name of preset to apply (e.g., 'primary-action', 'danger')
|
|
645
|
+
* @input props: ButtonMetadata - Configuration for the button (overrides preset values)
|
|
492
646
|
* @output onClick - Emits when the button is clicked
|
|
493
647
|
*/
|
|
494
648
|
class ButtonComponent {
|
|
495
|
-
constructor(download,
|
|
649
|
+
constructor(download, _icon, navigation) {
|
|
496
650
|
this.download = download;
|
|
497
651
|
this.navigation = navigation;
|
|
498
652
|
this.states = ComponentStates;
|
|
653
|
+
this.presets = inject(PresetService);
|
|
499
654
|
/**
|
|
500
655
|
* The text to display on the button.
|
|
501
656
|
*/
|
|
502
657
|
this.displayText = '';
|
|
658
|
+
/**
|
|
659
|
+
* Button configuration. Values here override preset values.
|
|
660
|
+
* When using presets, only partial props are needed (preset provides defaults).
|
|
661
|
+
*/
|
|
662
|
+
this.props = {};
|
|
663
|
+
/**
|
|
664
|
+
* Resolved props after merging preset + explicit props.
|
|
665
|
+
* Preset values are overridden by explicit props.
|
|
666
|
+
*/
|
|
667
|
+
this.resolvedProps = {};
|
|
503
668
|
/**
|
|
504
669
|
* Event emitted when the button is clicked.
|
|
505
670
|
*/
|
|
506
671
|
this.onClick = new EventEmitter();
|
|
507
672
|
}
|
|
508
673
|
ngOnInit() {
|
|
674
|
+
this.resolveProps();
|
|
509
675
|
this.setupDisplayText();
|
|
510
676
|
}
|
|
677
|
+
ngOnChanges(changes) {
|
|
678
|
+
if (changes['preset'] || changes['props']) {
|
|
679
|
+
this.resolveProps();
|
|
680
|
+
this.setupDisplayText();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Merge preset configuration with explicit props.
|
|
685
|
+
* Explicit props take precedence over preset values.
|
|
686
|
+
*/
|
|
687
|
+
resolveProps() {
|
|
688
|
+
const presetProps = this.preset
|
|
689
|
+
? this.presets.get('button', this.preset)
|
|
690
|
+
: {};
|
|
691
|
+
// Merge: preset defaults < explicit props
|
|
692
|
+
this.resolvedProps = {
|
|
693
|
+
...presetProps,
|
|
694
|
+
...this.props,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
511
697
|
/**
|
|
512
|
-
* Set up the text content based on the props configuration.
|
|
698
|
+
* Set up the text content based on the resolved props configuration.
|
|
513
699
|
*/
|
|
514
700
|
setupDisplayText() {
|
|
515
|
-
if (this.
|
|
516
|
-
if (this.
|
|
517
|
-
this.displayText = this.interpolateContent(this.
|
|
701
|
+
if (this.resolvedProps.text) {
|
|
702
|
+
if (this.resolvedProps.contentInterpolation) {
|
|
703
|
+
this.displayText = this.interpolateContent(this.resolvedProps.text, this.resolvedProps.contentInterpolation);
|
|
518
704
|
}
|
|
519
705
|
else {
|
|
520
|
-
this.displayText = this.
|
|
706
|
+
this.displayText = this.resolvedProps.text;
|
|
521
707
|
}
|
|
522
708
|
}
|
|
523
|
-
else if (this.
|
|
709
|
+
else if (this.resolvedProps.contentFallback) {
|
|
524
710
|
// Backwards compatibility: use fallback if text is not provided
|
|
525
|
-
this.displayText = this.
|
|
711
|
+
this.displayText = this.resolvedProps.contentFallback;
|
|
526
712
|
}
|
|
527
713
|
else {
|
|
528
714
|
this.displayText = '';
|
|
@@ -538,38 +724,38 @@ class ButtonComponent {
|
|
|
538
724
|
});
|
|
539
725
|
}
|
|
540
726
|
clickHandler() {
|
|
541
|
-
if (this.
|
|
727
|
+
if (this.resolvedProps.state === this.states.DISABLED) {
|
|
542
728
|
return;
|
|
543
729
|
}
|
|
544
|
-
if (this.
|
|
545
|
-
this.navigation.navigateByUrl(this.
|
|
730
|
+
if (this.resolvedProps.actionType === ActionType.APP_NAVIGATION) {
|
|
731
|
+
this.navigation.navigateByUrl(this.resolvedProps.link);
|
|
546
732
|
}
|
|
547
|
-
if (this.
|
|
548
|
-
this.download.downloadLinkFromBrowser(this.
|
|
733
|
+
if (this.resolvedProps.download) {
|
|
734
|
+
this.download.downloadLinkFromBrowser(this.resolvedProps.download);
|
|
549
735
|
}
|
|
550
|
-
if (this.
|
|
551
|
-
this.
|
|
736
|
+
if (this.resolvedProps.handler) {
|
|
737
|
+
this.resolvedProps.handler(this.resolvedProps.ref);
|
|
552
738
|
}
|
|
553
|
-
this.onClick.emit(this.
|
|
739
|
+
this.onClick.emit(this.resolvedProps.token);
|
|
554
740
|
}
|
|
555
741
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonComponent, deps: [{ token: DownloadService }, { token: IconService }, { token: NavigationService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
556
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ButtonComponent, isStandalone: true, selector: "val-button", inputs: { props: "props" }, outputs: { onClick: "onClick" }, ngImport: i0, template: `
|
|
742
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ButtonComponent, isStandalone: true, selector: "val-button", inputs: { preset: "preset", props: "props" }, outputs: { onClick: "onClick" }, usesOnChanges: true, ngImport: i0, template: `
|
|
557
743
|
<ion-button
|
|
558
|
-
[type]="
|
|
559
|
-
[color]="
|
|
560
|
-
[expand]="
|
|
561
|
-
[fill]="
|
|
562
|
-
[size]="
|
|
563
|
-
[href]="
|
|
564
|
-
[target]="
|
|
565
|
-
[shape]="
|
|
744
|
+
[type]="resolvedProps.type"
|
|
745
|
+
[color]="resolvedProps.color"
|
|
746
|
+
[expand]="resolvedProps.expand"
|
|
747
|
+
[fill]="resolvedProps.fill"
|
|
748
|
+
[size]="resolvedProps.size"
|
|
749
|
+
[href]="resolvedProps.href"
|
|
750
|
+
[target]="resolvedProps.target"
|
|
751
|
+
[shape]="resolvedProps.shape"
|
|
566
752
|
(click)="clickHandler()"
|
|
567
|
-
[disabled]="
|
|
568
|
-
[ngClass]="
|
|
753
|
+
[disabled]="resolvedProps.state === states.DISABLED"
|
|
754
|
+
[ngClass]="resolvedProps.size ? [resolvedProps.size] : []"
|
|
569
755
|
>
|
|
570
|
-
<ion-icon *ngIf="
|
|
571
|
-
<ion-spinner *ngIf="
|
|
572
|
-
<ion-text *ngIf="
|
|
756
|
+
<ion-icon *ngIf="resolvedProps.icon" [slot]="resolvedProps.icon.slot" [name]="resolvedProps.icon.name"></ion-icon>
|
|
757
|
+
<ion-spinner *ngIf="resolvedProps.state === states.WORKING" name="circular"></ion-spinner>
|
|
758
|
+
<ion-text *ngIf="resolvedProps.state !== states.WORKING">{{ displayText }}</ion-text>
|
|
573
759
|
</ion-button>
|
|
574
760
|
`, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-button{font-family:var(--ion-default-font),Arial,sans-serif;min-width:6.25rem;margin:0}ion-button.small{--padding-bottom: 12px;--padding-end: 14px;--padding-start: 14px;--padding-top: 12px}ion-button.button-clear{padding:0;margin:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }] }); }
|
|
575
761
|
}
|
|
@@ -577,24 +763,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
577
763
|
type: Component,
|
|
578
764
|
args: [{ selector: 'val-button', standalone: true, imports: [CommonModule, IonButton, IonIcon, IonSpinner, IonText], template: `
|
|
579
765
|
<ion-button
|
|
580
|
-
[type]="
|
|
581
|
-
[color]="
|
|
582
|
-
[expand]="
|
|
583
|
-
[fill]="
|
|
584
|
-
[size]="
|
|
585
|
-
[href]="
|
|
586
|
-
[target]="
|
|
587
|
-
[shape]="
|
|
766
|
+
[type]="resolvedProps.type"
|
|
767
|
+
[color]="resolvedProps.color"
|
|
768
|
+
[expand]="resolvedProps.expand"
|
|
769
|
+
[fill]="resolvedProps.fill"
|
|
770
|
+
[size]="resolvedProps.size"
|
|
771
|
+
[href]="resolvedProps.href"
|
|
772
|
+
[target]="resolvedProps.target"
|
|
773
|
+
[shape]="resolvedProps.shape"
|
|
588
774
|
(click)="clickHandler()"
|
|
589
|
-
[disabled]="
|
|
590
|
-
[ngClass]="
|
|
775
|
+
[disabled]="resolvedProps.state === states.DISABLED"
|
|
776
|
+
[ngClass]="resolvedProps.size ? [resolvedProps.size] : []"
|
|
591
777
|
>
|
|
592
|
-
<ion-icon *ngIf="
|
|
593
|
-
<ion-spinner *ngIf="
|
|
594
|
-
<ion-text *ngIf="
|
|
778
|
+
<ion-icon *ngIf="resolvedProps.icon" [slot]="resolvedProps.icon.slot" [name]="resolvedProps.icon.name"></ion-icon>
|
|
779
|
+
<ion-spinner *ngIf="resolvedProps.state === states.WORKING" name="circular"></ion-spinner>
|
|
780
|
+
<ion-text *ngIf="resolvedProps.state !== states.WORKING">{{ displayText }}</ion-text>
|
|
595
781
|
</ion-button>
|
|
596
782
|
`, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-button{font-family:var(--ion-default-font),Arial,sans-serif;min-width:6.25rem;margin:0}ion-button.small{--padding-bottom: 12px;--padding-end: 14px;--padding-start: 14px;--padding-top: 12px}ion-button.button-clear{padding:0;margin:0}\n"] }]
|
|
597
|
-
}], ctorParameters: () => [{ type: DownloadService }, { type: IconService }, { type: NavigationService }], propDecorators: {
|
|
783
|
+
}], ctorParameters: () => [{ type: DownloadService }, { type: IconService }, { type: NavigationService }], propDecorators: { preset: [{
|
|
784
|
+
type: Input
|
|
785
|
+
}], props: [{
|
|
598
786
|
type: Input
|
|
599
787
|
}], onClick: [{
|
|
600
788
|
type: Output
|
|
@@ -4189,7 +4377,7 @@ class ButtonGroupComponent {
|
|
|
4189
4377
|
[ngStyle]="{ width: props.buttons.length === 1 ? '100%' : 'auto' }"
|
|
4190
4378
|
></val-button>
|
|
4191
4379
|
</div>
|
|
4192
|
-
`, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}val-button{display:inline-block;margin-top:.25rem;margin-right:.25rem}.group-container{width:100%}.group-container.left{text-align:left}.group-container.center{text-align:center}.group-container.right{text-align:right}.group-container.column{display:flex;flex-direction:column-reverse}.group-container.spaced{display:flex;justify-content:space-between}.group-container.leftocenter{text-align:left}@media (min-width: 768px){.group-container.leftocenter{text-align:center}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
|
|
4380
|
+
`, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}val-button{display:inline-block;margin-top:.25rem;margin-right:.25rem}.group-container{width:100%}.group-container.left{text-align:left}.group-container.center{text-align:center}.group-container.right{text-align:right}.group-container.column{display:flex;flex-direction:column-reverse}.group-container.spaced{display:flex;justify-content:space-between}.group-container.leftocenter{text-align:left}@media (min-width: 768px){.group-container.leftocenter{text-align:center}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
|
|
4193
4381
|
}
|
|
4194
4382
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonGroupComponent, decorators: [{
|
|
4195
4383
|
type: Component,
|
|
@@ -4409,7 +4597,7 @@ class CardComponent {
|
|
|
4409
4597
|
</ng-container>
|
|
4410
4598
|
</ion-buttons>
|
|
4411
4599
|
</ion-card>
|
|
4412
|
-
`, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-card.tapable{transition:transform .3s ease,box-shadow .3s ease}ion-card.tapable:hover{transform:scale(1.01);box-shadow:.1875rem .625rem .5rem #1219541a}.tapable{cursor:pointer}.checker{display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-header{padding:10px;display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-content{padding-left:10px;padding-top:4px;padding-bottom:10px}.complex{border-radius:16px}img{border-radius:24px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { 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: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
|
|
4600
|
+
`, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-card.tapable{transition:transform .3s ease,box-shadow .3s ease}ion-card.tapable:hover{transform:scale(1.01);box-shadow:.1875rem .625rem .5rem #1219541a}.tapable{cursor:pointer}.checker{display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-header{padding:10px;display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-content{padding-left:10px;padding-top:4px;padding-bottom:10px}.complex{border-radius:16px}img{border-radius:24px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { 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: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
|
|
4413
4601
|
}
|
|
4414
4602
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardComponent, decorators: [{
|
|
4415
4603
|
type: Component,
|
|
@@ -4977,7 +5165,7 @@ class FileInputComponent {
|
|
|
4977
5165
|
</div>
|
|
4978
5166
|
<val-button [props]="contrastButton" (onClick)="fileInput.click()"></val-button>
|
|
4979
5167
|
</div>
|
|
4980
|
-
`, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:.5rem 0}@media (min-width: 768px){.input{margin:.75rem 0}}.file-container{margin-top:.25rem}.name-container{display:flex;flex-direction:row;align-items:center}\n"], dependencies: [{ kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
|
|
5168
|
+
`, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:.5rem 0}@media (min-width: 768px){.input{margin:.75rem 0}}.file-container{margin-top:.25rem}.name-container{display:flex;flex-direction:row;align-items:center}\n"], dependencies: [{ kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
|
|
4981
5169
|
}
|
|
4982
5170
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileInputComponent, decorators: [{
|
|
4983
5171
|
type: Component,
|
|
@@ -7833,7 +8021,7 @@ class ActionHeaderComponent {
|
|
|
7833
8021
|
<val-display [props]="props.title" />
|
|
7834
8022
|
<val-button [props]="props.action" />
|
|
7835
8023
|
</section>
|
|
7836
|
-
`, isInline: true, styles: [".header-content-container{width:100%;display:flex;align-items:center;justify-content:space-between}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
|
|
8024
|
+
`, isInline: true, styles: [".header-content-container{width:100%;display:flex;align-items:center;justify-content:space-between}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
|
|
7837
8025
|
}
|
|
7838
8026
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ActionHeaderComponent, decorators: [{
|
|
7839
8027
|
type: Component,
|
|
@@ -13626,7 +13814,7 @@ class RaffleStatusCardComponent {
|
|
|
13626
13814
|
}
|
|
13627
13815
|
</div>
|
|
13628
13816
|
</article>
|
|
13629
|
-
`, isInline: true, styles: [":host{display:block}.raffle-card{display:flex;flex-direction:column;background:var(--ion-color-light);border-radius:12px;overflow:hidden;box-shadow:0 2px 8px #00000014;transition:transform .2s,box-shadow .2s}.raffle-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px #0000001f}.card-image{position:relative;aspect-ratio:16/9;overflow:hidden}.card-image val-image{width:100%;height:100%}.card-image val-image ::ng-deep img{width:100%;height:100%;object-fit:cover}.status-badge{position:absolute;top:12px;left:12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:4px 10px;border-radius:4px}.status-badge.inline{position:static;margin-bottom:8px}.countdown-overlay{position:absolute;bottom:0;left:0;right:0;padding:8px 12px;background:linear-gradient(transparent,#000000b3)}.card-content{display:flex;flex-direction:column;gap:12px;padding:16px}.card-header{display:flex;flex-direction:column;gap:4px}.card-title{font-size:18px;font-weight:600;color:var(--ion-color-dark);margin:0;line-height:1.3}.card-description{font-size:14px;color:var(--ion-color-medium-shade);margin:0;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.beneficiary-info{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--ion-color-medium-shade)}.beneficiary-info ion-icon{font-size:16px;color:var(--ion-color-danger)}.beneficiary-info .beneficiary-image{width:24px;height:24px;border-radius:50%;overflow:hidden}.prize-preview{padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.prize-header{display:flex;align-items:center;gap:4px;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--ion-color-medium);margin-bottom:4px}.prize-header ion-icon{font-size:14px;color:var(--ion-color-warning)}.prize-name{font-size:15px;font-weight:600;color:var(--ion-color-dark)}.prize-value{font-size:18px;font-weight:700;color:var(--ion-color-success);margin-top:4px}.progress-section{display:flex;flex-direction:column;gap:6px}.progress-header{display:flex;justify-content:space-between;align-items:center}.progress-label{font-size:12px;color:var(--ion-color-medium-shade)}.progress-value{font-size:14px;font-weight:600;color:var(--ion-color-dark)}ion-progress-bar{height:8px;border-radius:4px;--background: var(--ion-color-light-shade)}.ticket-stats{display:flex;align-items:center;justify-content:center;gap:16px;padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.stat{display:flex;align-items:center;gap:6px;font-size:13px}.stat ion-icon{font-size:18px;color:var(--ion-color-primary)}.stat-value{font-weight:700;color:var(--ion-color-dark);font-size:16px}.stat-label{color:var(--ion-color-medium-shade)}.stat-divider{width:1px;height:24px;background:var(--ion-color-medium-tint)}.price-section{display:flex;justify-content:center;padding:8px 0}.dates-section{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--ion-color-medium-shade)}.date-item{display:flex;align-items:center;gap:6px}.date-item ion-icon{font-size:16px;color:var(--ion-color-primary)}.card-actions{display:flex;gap:8px;margin-top:4px}.card-actions val-button{flex:1}.variant-featured{border:2px solid var(--ion-color-warning)}.variant-featured .card-image{aspect-ratio:2/1}.variant-featured .card-title{font-size:22px}.variant-compact .card-content{padding:12px;gap:8px}.variant-compact .card-title{font-size:15px}.variant-compact .card-description{font-size:13px;-webkit-line-clamp:1}.variant-compact .ticket-stats,.variant-compact .dates-section{display:none}.variant-horizontal{flex-direction:row}.variant-horizontal .card-image{width:40%;min-width:150px;aspect-ratio:1}.variant-horizontal .card-content{flex:1;justify-content:center}.size-small .card-content{padding:12px;gap:8px}.size-small .card-title{font-size:15px}.size-small .card-description{font-size:12px}.size-small .ticket-stats{padding:8px}.size-small .stat-value{font-size:14px}.size-large .card-content{padding:24px;gap:16px}.size-large .card-title{font-size:24px}.size-large .card-description{font-size:16px}.size-large .prize-name{font-size:18px}.size-large .prize-value{font-size:22px}.status-upcoming{opacity:.9}.status-sold_out .card-image:after{content:\"\";position:absolute;inset:0;background:#0000004d}.status-completed .card-actions{opacity:.5;pointer-events:none}.status-cancelled{opacity:.6}.status-cancelled .card-image:after{content:\"\";position:absolute;inset:0;background:#0006}@media (max-width: 480px){.variant-horizontal{flex-direction:column}.variant-horizontal .card-image{width:100%;aspect-ratio:16/9}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonProgressBar, selector: "ion-progress-bar", inputs: ["buffer", "color", "mode", "reversed", "type", "value"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: CountdownComponent, selector: "val-countdown", inputs: ["props"], outputs: ["complete", "tick"] }, { kind: "component", type: PriceTagComponent, selector: "val-price-tag", inputs: ["props"] }] }); }
|
|
13817
|
+
`, isInline: true, styles: [":host{display:block}.raffle-card{display:flex;flex-direction:column;background:var(--ion-color-light);border-radius:12px;overflow:hidden;box-shadow:0 2px 8px #00000014;transition:transform .2s,box-shadow .2s}.raffle-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px #0000001f}.card-image{position:relative;aspect-ratio:16/9;overflow:hidden}.card-image val-image{width:100%;height:100%}.card-image val-image ::ng-deep img{width:100%;height:100%;object-fit:cover}.status-badge{position:absolute;top:12px;left:12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:4px 10px;border-radius:4px}.status-badge.inline{position:static;margin-bottom:8px}.countdown-overlay{position:absolute;bottom:0;left:0;right:0;padding:8px 12px;background:linear-gradient(transparent,#000000b3)}.card-content{display:flex;flex-direction:column;gap:12px;padding:16px}.card-header{display:flex;flex-direction:column;gap:4px}.card-title{font-size:18px;font-weight:600;color:var(--ion-color-dark);margin:0;line-height:1.3}.card-description{font-size:14px;color:var(--ion-color-medium-shade);margin:0;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.beneficiary-info{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--ion-color-medium-shade)}.beneficiary-info ion-icon{font-size:16px;color:var(--ion-color-danger)}.beneficiary-info .beneficiary-image{width:24px;height:24px;border-radius:50%;overflow:hidden}.prize-preview{padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.prize-header{display:flex;align-items:center;gap:4px;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--ion-color-medium);margin-bottom:4px}.prize-header ion-icon{font-size:14px;color:var(--ion-color-warning)}.prize-name{font-size:15px;font-weight:600;color:var(--ion-color-dark)}.prize-value{font-size:18px;font-weight:700;color:var(--ion-color-success);margin-top:4px}.progress-section{display:flex;flex-direction:column;gap:6px}.progress-header{display:flex;justify-content:space-between;align-items:center}.progress-label{font-size:12px;color:var(--ion-color-medium-shade)}.progress-value{font-size:14px;font-weight:600;color:var(--ion-color-dark)}ion-progress-bar{height:8px;border-radius:4px;--background: var(--ion-color-light-shade)}.ticket-stats{display:flex;align-items:center;justify-content:center;gap:16px;padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.stat{display:flex;align-items:center;gap:6px;font-size:13px}.stat ion-icon{font-size:18px;color:var(--ion-color-primary)}.stat-value{font-weight:700;color:var(--ion-color-dark);font-size:16px}.stat-label{color:var(--ion-color-medium-shade)}.stat-divider{width:1px;height:24px;background:var(--ion-color-medium-tint)}.price-section{display:flex;justify-content:center;padding:8px 0}.dates-section{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--ion-color-medium-shade)}.date-item{display:flex;align-items:center;gap:6px}.date-item ion-icon{font-size:16px;color:var(--ion-color-primary)}.card-actions{display:flex;gap:8px;margin-top:4px}.card-actions val-button{flex:1}.variant-featured{border:2px solid var(--ion-color-warning)}.variant-featured .card-image{aspect-ratio:2/1}.variant-featured .card-title{font-size:22px}.variant-compact .card-content{padding:12px;gap:8px}.variant-compact .card-title{font-size:15px}.variant-compact .card-description{font-size:13px;-webkit-line-clamp:1}.variant-compact .ticket-stats,.variant-compact .dates-section{display:none}.variant-horizontal{flex-direction:row}.variant-horizontal .card-image{width:40%;min-width:150px;aspect-ratio:1}.variant-horizontal .card-content{flex:1;justify-content:center}.size-small .card-content{padding:12px;gap:8px}.size-small .card-title{font-size:15px}.size-small .card-description{font-size:12px}.size-small .ticket-stats{padding:8px}.size-small .stat-value{font-size:14px}.size-large .card-content{padding:24px;gap:16px}.size-large .card-title{font-size:24px}.size-large .card-description{font-size:16px}.size-large .prize-name{font-size:18px}.size-large .prize-value{font-size:22px}.status-upcoming{opacity:.9}.status-sold_out .card-image:after{content:\"\";position:absolute;inset:0;background:#0000004d}.status-completed .card-actions{opacity:.5;pointer-events:none}.status-cancelled{opacity:.6}.status-cancelled .card-image:after{content:\"\";position:absolute;inset:0;background:#0006}@media (max-width: 480px){.variant-horizontal{flex-direction:column}.variant-horizontal .card-image{width:100%;aspect-ratio:16/9}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonProgressBar, selector: "ion-progress-bar", inputs: ["buffer", "color", "mode", "reversed", "type", "value"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: CountdownComponent, selector: "val-countdown", inputs: ["props"], outputs: ["complete", "tick"] }, { kind: "component", type: PriceTagComponent, selector: "val-price-tag", inputs: ["props"] }] }); }
|
|
13630
13818
|
}
|
|
13631
13819
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RaffleStatusCardComponent, decorators: [{
|
|
13632
13820
|
type: Component,
|
|
@@ -15976,7 +16164,7 @@ class ArticleComponent {
|
|
|
15976
16164
|
</ng-container>
|
|
15977
16165
|
</div>
|
|
15978
16166
|
</article>
|
|
15979
|
-
`, isInline: true, styles: [".val-article{width:100%;margin:0 auto;line-height:1.6;color:var(--ion-color-dark, #1f2937);display:block;position:static}.val-article--centered{margin:0 auto}.val-article--light{background-color:var(--ion-color-light, #ffffff);color:var(--ion-color-dark, #1f2937)}.val-article--dark{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article__element{position:relative}.val-article__element:last-child{margin-bottom:0!important}.val-article__spacing-top--none{margin-top:0!important}.val-article__spacing-top--small{margin-top:.5rem!important}.val-article__spacing-top--medium{margin-top:1rem!important}.val-article__spacing-top--large{margin-top:1.5rem!important}.val-article__spacing-top--xlarge{margin-top:2.5rem!important}.val-article__spacing-bottom--none{margin-bottom:0!important}.val-article__spacing-bottom--small{margin-bottom:.5rem!important}.val-article__spacing-bottom--medium{margin-bottom:1rem!important}.val-article__spacing-bottom--large{margin-bottom:1.5rem!important}.val-article__spacing-bottom--xlarge{margin-bottom:2.5rem!important}.val-article__spacing-horizontal--none{margin-left:0!important;margin-right:0!important}.val-article__spacing-horizontal--small{margin-left:.5rem!important;margin-right:.5rem!important}.val-article__spacing-horizontal--medium{margin-left:1rem!important;margin-right:1rem!important}.val-article__spacing-horizontal--large{margin-left:1.5rem!important;margin-right:1.5rem!important}.val-article__spacing-horizontal--xlarge{margin-left:2.5rem!important;margin-right:2.5rem!important}.val-article__quote{border-left:4px solid #0969da;padding:1rem;background-color:var(--ion-color-light-shade, #f8f9fa);border-radius:0 8px 8px 0;font-style:italic}.val-article__quote-content{margin-bottom:.5rem;font-size:1.1em}.val-article__quote-author{font-size:.9em;color:var(--ion-color-medium, #6c757d);font-style:normal;font-weight:500;text-align:right}.val-article__quote-source{font-weight:400;opacity:.8}.val-article__highlight{padding:1rem;background-color:var(--ion-color-warning-tint, #fff3cd);border:1px solid var(--ion-color-warning, #ffc107);border-radius:4px}.val-article__highlight--rounded{border-radius:12px}.val-article__code{border-radius:8px;overflow:hidden;border:1px solid #e1e5e9}.val-article__code-language{background-color:var(--ion-color-medium-tint, #f8f9fa);padding:.5rem 1rem;font-size:.875em;font-weight:500;color:var(--ion-color-dark, #495057);border-bottom:1px solid #e1e5e9;text-transform:uppercase;letter-spacing:.5px}.val-article__code-content{background-color:#f6f8fa;color:#24292e;padding:1rem;margin:0;overflow-x:auto;font-family:SFMono-Regular,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875em;line-height:1.5}.val-article__code-content code{background:none;padding:0;font-size:inherit;color:inherit}.val-article__code-content--dark{background-color:#161b22;color:#f0f6fc}.val-article__list{padding-left:1.5rem}.val-article__list--ordered{list-style-type:decimal}.val-article__list-item{margin-bottom:.5rem;line-height:1.6;position:relative}.val-article__list-item:last-child{margin-bottom:0}.val-article__list-check{color:var(--ion-color-success, #28a745);font-weight:700;margin-right:.5rem;position:absolute;left:-1.5rem}.val-article .val-article__list:has(.val-article__list-check){list-style:none;padding-left:1rem}.val-article__button-container--left{text-align:left}.val-article__button-container--center{text-align:center}.val-article__button-container--right{text-align:right}.val-article__separator{text-align:center}.val-article__separator-line{border:none;height:1px;background-color:#d0d7de;margin:0}.val-article__separator-line--thin{height:1px}.val-article__separator-line--thick{height:3px}.val-article__separator-dots{color:#d0d7de;font-size:1.5em;letter-spacing:.5em;-webkit-user-select:none;user-select:none}.val-article__separator-space{height:0}.val-article__image{text-align:center}.val-article__image--left{text-align:left}.val-article__image--center{text-align:center}.val-article__image--right{text-align:right}.val-article__image-content{max-width:100%;height:auto;border-radius:4px;box-shadow:0 2px 8px #0000001a;transition:transform .2s ease}.val-article__image-content:hover{transform:scale(1.02)}.val-article__image-content--rounded{border-radius:12px}.val-article__image-caption{margin-top:.5rem;font-size:.875em;color:var(--ion-color-medium, #6c757d);font-style:italic;text-align:center}.val-article__video{text-align:center}.val-article__video-content{max-width:100%;height:auto;border-radius:8px;box-shadow:0 4px 12px #00000026}.val-article__video-title{margin-top:.5rem;font-size:.9em;color:var(--ion-color-medium, #6c757d);font-weight:500}.val-article__custom *{max-width:100%}@media (max-width: 768px){.val-article__code-content{font-size:.8em;padding:.5rem}.val-article__image-content:hover{transform:none}.val-article__quote,.val-article__highlight{padding:.5rem}}@media (prefers-color-scheme: dark){.val-article--auto{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article--auto .val-article__quote{background-color:#ffffff0d}.val-article--auto .val-article__highlight{background-color:#ffc1071a;border-color:var(--ion-color-warning-shade, #e0a800)}.val-article--auto .val-article__code-content{background-color:#161b22;color:#f0f6fc}}.val-article__element{animation:fadeInUp .3s ease-out}@keyframes fadeInUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@media (prefers-contrast: high){.val-article__separator-line{background-color:currentColor;opacity:.5}.val-article__quote{border-left-width:6px}}@media (prefers-reduced-motion: reduce){.val-article .val-article__element{animation:none}.val-article__image-content{transition:none}.val-article__image-content:hover{transform:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: CodeDisplayComponent, selector: "val-code-display", inputs: ["props"] }, { kind: "component", type: NotesBoxComponent, selector: "val-notes-box", inputs: ["props"] }, { kind: "component", type: PlainCodeBoxComponent, selector: "val-plain-code-box", inputs: ["props"] }] }); }
|
|
16167
|
+
`, isInline: true, styles: [".val-article{width:100%;margin:0 auto;line-height:1.6;color:var(--ion-color-dark, #1f2937);display:block;position:static}.val-article--centered{margin:0 auto}.val-article--light{background-color:var(--ion-color-light, #ffffff);color:var(--ion-color-dark, #1f2937)}.val-article--dark{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article__element{position:relative}.val-article__element:last-child{margin-bottom:0!important}.val-article__spacing-top--none{margin-top:0!important}.val-article__spacing-top--small{margin-top:.5rem!important}.val-article__spacing-top--medium{margin-top:1rem!important}.val-article__spacing-top--large{margin-top:1.5rem!important}.val-article__spacing-top--xlarge{margin-top:2.5rem!important}.val-article__spacing-bottom--none{margin-bottom:0!important}.val-article__spacing-bottom--small{margin-bottom:.5rem!important}.val-article__spacing-bottom--medium{margin-bottom:1rem!important}.val-article__spacing-bottom--large{margin-bottom:1.5rem!important}.val-article__spacing-bottom--xlarge{margin-bottom:2.5rem!important}.val-article__spacing-horizontal--none{margin-left:0!important;margin-right:0!important}.val-article__spacing-horizontal--small{margin-left:.5rem!important;margin-right:.5rem!important}.val-article__spacing-horizontal--medium{margin-left:1rem!important;margin-right:1rem!important}.val-article__spacing-horizontal--large{margin-left:1.5rem!important;margin-right:1.5rem!important}.val-article__spacing-horizontal--xlarge{margin-left:2.5rem!important;margin-right:2.5rem!important}.val-article__quote{border-left:4px solid #0969da;padding:1rem;background-color:var(--ion-color-light-shade, #f8f9fa);border-radius:0 8px 8px 0;font-style:italic}.val-article__quote-content{margin-bottom:.5rem;font-size:1.1em}.val-article__quote-author{font-size:.9em;color:var(--ion-color-medium, #6c757d);font-style:normal;font-weight:500;text-align:right}.val-article__quote-source{font-weight:400;opacity:.8}.val-article__highlight{padding:1rem;background-color:var(--ion-color-warning-tint, #fff3cd);border:1px solid var(--ion-color-warning, #ffc107);border-radius:4px}.val-article__highlight--rounded{border-radius:12px}.val-article__code{border-radius:8px;overflow:hidden;border:1px solid #e1e5e9}.val-article__code-language{background-color:var(--ion-color-medium-tint, #f8f9fa);padding:.5rem 1rem;font-size:.875em;font-weight:500;color:var(--ion-color-dark, #495057);border-bottom:1px solid #e1e5e9;text-transform:uppercase;letter-spacing:.5px}.val-article__code-content{background-color:#f6f8fa;color:#24292e;padding:1rem;margin:0;overflow-x:auto;font-family:SFMono-Regular,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875em;line-height:1.5}.val-article__code-content code{background:none;padding:0;font-size:inherit;color:inherit}.val-article__code-content--dark{background-color:#161b22;color:#f0f6fc}.val-article__list{padding-left:1.5rem}.val-article__list--ordered{list-style-type:decimal}.val-article__list-item{margin-bottom:.5rem;line-height:1.6;position:relative}.val-article__list-item:last-child{margin-bottom:0}.val-article__list-check{color:var(--ion-color-success, #28a745);font-weight:700;margin-right:.5rem;position:absolute;left:-1.5rem}.val-article .val-article__list:has(.val-article__list-check){list-style:none;padding-left:1rem}.val-article__button-container--left{text-align:left}.val-article__button-container--center{text-align:center}.val-article__button-container--right{text-align:right}.val-article__separator{text-align:center}.val-article__separator-line{border:none;height:1px;background-color:#d0d7de;margin:0}.val-article__separator-line--thin{height:1px}.val-article__separator-line--thick{height:3px}.val-article__separator-dots{color:#d0d7de;font-size:1.5em;letter-spacing:.5em;-webkit-user-select:none;user-select:none}.val-article__separator-space{height:0}.val-article__image{text-align:center}.val-article__image--left{text-align:left}.val-article__image--center{text-align:center}.val-article__image--right{text-align:right}.val-article__image-content{max-width:100%;height:auto;border-radius:4px;box-shadow:0 2px 8px #0000001a;transition:transform .2s ease}.val-article__image-content:hover{transform:scale(1.02)}.val-article__image-content--rounded{border-radius:12px}.val-article__image-caption{margin-top:.5rem;font-size:.875em;color:var(--ion-color-medium, #6c757d);font-style:italic;text-align:center}.val-article__video{text-align:center}.val-article__video-content{max-width:100%;height:auto;border-radius:8px;box-shadow:0 4px 12px #00000026}.val-article__video-title{margin-top:.5rem;font-size:.9em;color:var(--ion-color-medium, #6c757d);font-weight:500}.val-article__custom *{max-width:100%}@media (max-width: 768px){.val-article__code-content{font-size:.8em;padding:.5rem}.val-article__image-content:hover{transform:none}.val-article__quote,.val-article__highlight{padding:.5rem}}@media (prefers-color-scheme: dark){.val-article--auto{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article--auto .val-article__quote{background-color:#ffffff0d}.val-article--auto .val-article__highlight{background-color:#ffc1071a;border-color:var(--ion-color-warning-shade, #e0a800)}.val-article--auto .val-article__code-content{background-color:#161b22;color:#f0f6fc}}.val-article__element{animation:fadeInUp .3s ease-out}@keyframes fadeInUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@media (prefers-contrast: high){.val-article__separator-line{background-color:currentColor;opacity:.5}.val-article__quote{border-left-width:6px}}@media (prefers-reduced-motion: reduce){.val-article .val-article__element{animation:none}.val-article__image-content{transition:none}.val-article__image-content:hover{transform:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: CodeDisplayComponent, selector: "val-code-display", inputs: ["props"] }, { kind: "component", type: NotesBoxComponent, selector: "val-notes-box", inputs: ["props"] }, { kind: "component", type: PlainCodeBoxComponent, selector: "val-plain-code-box", inputs: ["props"] }] }); }
|
|
15980
16168
|
}
|
|
15981
16169
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ArticleComponent, decorators: [{
|
|
15982
16170
|
type: Component,
|
|
@@ -19912,7 +20100,7 @@ class MenuComponent {
|
|
|
19912
20100
|
/>
|
|
19913
20101
|
}
|
|
19914
20102
|
</ion-menu>
|
|
19915
|
-
`, isInline: true, styles: ["ion-menu{--min-width: 75%}@media (min-width: 768px){ion-menu{--min-width: 40%}}ion-menu ion-header{background:var(--ion-color-light)}ion-menu ion-content{--background: var(--ion-color-light)}.menu-close-button{display:block;margin:1rem .5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { 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: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
|
|
20103
|
+
`, isInline: true, styles: ["ion-menu{--min-width: 75%}@media (min-width: 768px){ion-menu{--min-width: 40%}}ion-menu ion-header{background:var(--ion-color-light)}ion-menu ion-content{--background: var(--ion-color-light)}.menu-close-button{display:block;margin:1rem .5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { 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: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
|
|
19916
20104
|
}
|
|
19917
20105
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MenuComponent, decorators: [{
|
|
19918
20106
|
type: Component,
|
|
@@ -20393,7 +20581,7 @@ class PageTemplateComponent {
|
|
|
20393
20581
|
</ion-row>
|
|
20394
20582
|
}
|
|
20395
20583
|
</ion-grid>
|
|
20396
|
-
`, isInline: true, styles: [".page-title{padding:0;font-size:1.6rem;font-weight:800}@media (min-width: 768px){.page-title{font-size:2rem}}.description-row{margin-bottom:16px}.description-container{margin-top:1rem}.back-row{margin-bottom:16px}.back-button{display:block;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: ExpandableTextComponent, selector: "val-expandable-text", inputs: ["props"] }, { kind: "component", type: IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: IonRow, selector: "ion-row" }, { kind: "component", type: IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
|
|
20584
|
+
`, isInline: true, styles: [".page-title{padding:0;font-size:1.6rem;font-weight:800}@media (min-width: 768px){.page-title{font-size:2rem}}.description-row{margin-bottom:16px}.description-container{margin-top:1rem}.back-row{margin-bottom:16px}.back-button{display:block;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: ExpandableTextComponent, selector: "val-expandable-text", inputs: ["props"] }, { kind: "component", type: IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: IonRow, selector: "ion-row" }, { kind: "component", type: IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
|
|
20397
20585
|
}
|
|
20398
20586
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PageTemplateComponent, decorators: [{
|
|
20399
20587
|
type: Component,
|
|
@@ -21482,99 +21670,942 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
21482
21670
|
*/
|
|
21483
21671
|
|
|
21484
21672
|
/**
|
|
21485
|
-
* Firebase
|
|
21673
|
+
* Analytics Service (Firebase GA4)
|
|
21486
21674
|
*
|
|
21487
|
-
*
|
|
21488
|
-
*
|
|
21489
|
-
*/
|
|
21490
|
-
/**
|
|
21491
|
-
* Token de inyección para la configuración de Firebase.
|
|
21492
|
-
* Usado internamente por los servicios de Firebase.
|
|
21675
|
+
* Servicio para tracking de eventos, page views y errores con Firebase Analytics.
|
|
21676
|
+
* Integra con el sistema de auth para user properties y respeta consent mode GDPR.
|
|
21493
21677
|
*/
|
|
21494
|
-
|
|
21678
|
+
/** Key por defecto para persistir consent en localStorage */
|
|
21679
|
+
const DEFAULT_CONSENT_STORAGE_KEY = 'analytics_consent';
|
|
21680
|
+
/** Máximo de eventos en historial de debug */
|
|
21681
|
+
const MAX_DEBUG_HISTORY = 100;
|
|
21495
21682
|
/**
|
|
21496
|
-
*
|
|
21683
|
+
* Servicio de Firebase Analytics (GA4).
|
|
21497
21684
|
*
|
|
21498
|
-
*
|
|
21499
|
-
*
|
|
21685
|
+
* Proporciona tracking de eventos, page views, errores y métricas de performance.
|
|
21686
|
+
* Soporta GDPR Consent Mode y modo debug para desarrollo.
|
|
21500
21687
|
*
|
|
21501
21688
|
* @example
|
|
21502
21689
|
* ```typescript
|
|
21503
|
-
*
|
|
21504
|
-
*
|
|
21505
|
-
*
|
|
21506
|
-
*
|
|
21507
|
-
*
|
|
21508
|
-
*
|
|
21509
|
-
*
|
|
21510
|
-
*
|
|
21511
|
-
*
|
|
21512
|
-
*
|
|
21513
|
-
*
|
|
21514
|
-
*
|
|
21515
|
-
* auth: { host: 'localhost', port: 9099 },
|
|
21516
|
-
* storage: { host: 'localhost', port: 9199 },
|
|
21517
|
-
* } : undefined,
|
|
21518
|
-
* }),
|
|
21519
|
-
* ],
|
|
21520
|
-
* });
|
|
21690
|
+
* @Component({...})
|
|
21691
|
+
* export class ProductComponent {
|
|
21692
|
+
* private analytics = inject(AnalyticsService);
|
|
21693
|
+
*
|
|
21694
|
+
* onPurchase(product: Product) {
|
|
21695
|
+
* this.analytics.logEvent('purchase', {
|
|
21696
|
+
* transaction_id: order.id,
|
|
21697
|
+
* value: order.total,
|
|
21698
|
+
* currency: 'EUR'
|
|
21699
|
+
* });
|
|
21700
|
+
* }
|
|
21701
|
+
* }
|
|
21521
21702
|
* ```
|
|
21522
21703
|
*/
|
|
21523
|
-
|
|
21524
|
-
|
|
21525
|
-
|
|
21526
|
-
|
|
21527
|
-
|
|
21528
|
-
//
|
|
21529
|
-
|
|
21530
|
-
//
|
|
21531
|
-
|
|
21532
|
-
|
|
21533
|
-
|
|
21534
|
-
|
|
21535
|
-
|
|
21536
|
-
|
|
21537
|
-
|
|
21538
|
-
|
|
21539
|
-
|
|
21540
|
-
|
|
21541
|
-
|
|
21542
|
-
|
|
21543
|
-
|
|
21544
|
-
|
|
21545
|
-
|
|
21546
|
-
|
|
21547
|
-
|
|
21548
|
-
|
|
21549
|
-
|
|
21550
|
-
|
|
21551
|
-
|
|
21552
|
-
|
|
21553
|
-
|
|
21554
|
-
|
|
21555
|
-
|
|
21556
|
-
|
|
21557
|
-
|
|
21558
|
-
|
|
21559
|
-
|
|
21560
|
-
|
|
21561
|
-
|
|
21562
|
-
|
|
21563
|
-
|
|
21564
|
-
|
|
21565
|
-
|
|
21566
|
-
|
|
21567
|
-
|
|
21568
|
-
|
|
21569
|
-
|
|
21570
|
-
|
|
21571
|
-
|
|
21572
|
-
|
|
21573
|
-
|
|
21574
|
-
|
|
21704
|
+
class AnalyticsService {
|
|
21705
|
+
constructor(injector, config, platformId) {
|
|
21706
|
+
this.injector = injector;
|
|
21707
|
+
this.config = config;
|
|
21708
|
+
this.platformId = platformId;
|
|
21709
|
+
// ===========================================================================
|
|
21710
|
+
// ESTADO (Signals)
|
|
21711
|
+
// ===========================================================================
|
|
21712
|
+
this._consentState = signal({
|
|
21713
|
+
settings: {},
|
|
21714
|
+
updatedAt: null,
|
|
21715
|
+
hasDecided: false,
|
|
21716
|
+
});
|
|
21717
|
+
this._isDebugMode = signal(false);
|
|
21718
|
+
this._debugHistory = signal([]);
|
|
21719
|
+
/** Estado de consentimiento actual (readonly) */
|
|
21720
|
+
this.consentState = this._consentState.asReadonly();
|
|
21721
|
+
/** Indica si está en modo debug */
|
|
21722
|
+
this.isDebugMode = this._isDebugMode.asReadonly();
|
|
21723
|
+
/** Indica si analytics está habilitado y funcionando */
|
|
21724
|
+
this.isEnabled = computed(() => {
|
|
21725
|
+
return this.isAnalyticsSupported() && this._consentState().settings.analytics === true;
|
|
21726
|
+
});
|
|
21727
|
+
this.analyticsConfig = config.analyticsConfig ?? {};
|
|
21728
|
+
this.consentStorageKey =
|
|
21729
|
+
this.analyticsConfig.consentStorageKey ?? DEFAULT_CONSENT_STORAGE_KEY;
|
|
21730
|
+
this.eventPrefix = this.analyticsConfig.eventPrefix ?? '';
|
|
21731
|
+
this.samplingRate = this.analyticsConfig.samplingRate ?? 1.0;
|
|
21732
|
+
this.initializeAnalytics();
|
|
21733
|
+
}
|
|
21734
|
+
// ===========================================================================
|
|
21735
|
+
// INICIALIZACIÓN
|
|
21736
|
+
// ===========================================================================
|
|
21737
|
+
/**
|
|
21738
|
+
* Inicializa el servicio de analytics
|
|
21739
|
+
*/
|
|
21740
|
+
initializeAnalytics() {
|
|
21741
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
21742
|
+
return;
|
|
21743
|
+
}
|
|
21744
|
+
// Cargar consent desde localStorage
|
|
21745
|
+
this.loadConsentFromStorage();
|
|
21746
|
+
// Configurar debug mode
|
|
21747
|
+
const debugMode = this.analyticsConfig.debugMode ?? false;
|
|
21748
|
+
this._isDebugMode.set(debugMode);
|
|
21749
|
+
// Aplicar consent inicial a gtag
|
|
21750
|
+
this.applyConsentToGtag(this._consentState().settings);
|
|
21751
|
+
// Setear user properties por defecto
|
|
21752
|
+
if (this.analyticsConfig.defaultUserProperties) {
|
|
21753
|
+
this.setUserProperties(this.analyticsConfig.defaultUserProperties);
|
|
21754
|
+
}
|
|
21755
|
+
if (debugMode) {
|
|
21756
|
+
console.log('[Analytics] Inicializado en modo debug - eventos NO se envían a Firebase');
|
|
21757
|
+
}
|
|
21758
|
+
}
|
|
21759
|
+
/**
|
|
21760
|
+
* Obtiene la instancia de Analytics de forma perezosa.
|
|
21761
|
+
* Esto evita el error de APP_INITIALIZER de AngularFire.
|
|
21762
|
+
*/
|
|
21763
|
+
getAnalyticsInstance() {
|
|
21764
|
+
if (!this.config.enableAnalytics) {
|
|
21765
|
+
return null;
|
|
21766
|
+
}
|
|
21767
|
+
try {
|
|
21768
|
+
return this.injector.get(Analytics, null);
|
|
21769
|
+
}
|
|
21770
|
+
catch {
|
|
21771
|
+
return null;
|
|
21772
|
+
}
|
|
21773
|
+
}
|
|
21774
|
+
/**
|
|
21775
|
+
* Verifica si Analytics está soportado
|
|
21776
|
+
*/
|
|
21777
|
+
isAnalyticsSupported() {
|
|
21778
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
21779
|
+
return false;
|
|
21780
|
+
}
|
|
21781
|
+
if (!this.config.enableAnalytics) {
|
|
21782
|
+
return false;
|
|
21783
|
+
}
|
|
21784
|
+
if (!this.config.firebase.measurementId) {
|
|
21785
|
+
console.warn('[Analytics] measurementId no configurado en firebase config');
|
|
21786
|
+
return false;
|
|
21787
|
+
}
|
|
21788
|
+
return true;
|
|
21789
|
+
}
|
|
21790
|
+
/**
|
|
21791
|
+
* Verifica si debe enviar el evento (sampling)
|
|
21792
|
+
*/
|
|
21793
|
+
shouldSample() {
|
|
21794
|
+
if (this.samplingRate >= 1.0) {
|
|
21795
|
+
return true;
|
|
21796
|
+
}
|
|
21797
|
+
return Math.random() < this.samplingRate;
|
|
21798
|
+
}
|
|
21799
|
+
// ===========================================================================
|
|
21800
|
+
// PAGE VIEWS
|
|
21801
|
+
// ===========================================================================
|
|
21802
|
+
/**
|
|
21803
|
+
* Registra un page view.
|
|
21804
|
+
* Normalmente se usa automáticamente via AnalyticsRouterTracker.
|
|
21805
|
+
*
|
|
21806
|
+
* @param pagePath - Ruta de la página (ej: '/products/123')
|
|
21807
|
+
* @param pageTitle - Título de la página (opcional)
|
|
21808
|
+
*/
|
|
21809
|
+
logPageView(pagePath, pageTitle) {
|
|
21810
|
+
this.logEvent('page_view', {
|
|
21811
|
+
page_path: pagePath,
|
|
21812
|
+
page_title: pageTitle ?? document?.title,
|
|
21813
|
+
page_location: window?.location?.href,
|
|
21814
|
+
});
|
|
21815
|
+
}
|
|
21816
|
+
/**
|
|
21817
|
+
* Registra un screen view (para apps tipo SPA).
|
|
21818
|
+
*
|
|
21819
|
+
* @param screenName - Nombre del screen
|
|
21820
|
+
* @param screenClass - Clase del screen (opcional)
|
|
21821
|
+
*/
|
|
21822
|
+
logScreenView(screenName, screenClass) {
|
|
21823
|
+
this.logEvent('screen_view', {
|
|
21824
|
+
screen_name: screenName,
|
|
21825
|
+
screen_class: screenClass,
|
|
21826
|
+
});
|
|
21827
|
+
}
|
|
21828
|
+
// ===========================================================================
|
|
21829
|
+
// EVENTOS
|
|
21830
|
+
// ===========================================================================
|
|
21831
|
+
/**
|
|
21832
|
+
* Registra un evento tipado GA4.
|
|
21833
|
+
*
|
|
21834
|
+
* @param eventName - Nombre del evento (tipado)
|
|
21835
|
+
* @param params - Parámetros del evento (tipados según el nombre)
|
|
21836
|
+
*
|
|
21837
|
+
* @example
|
|
21838
|
+
* ```typescript
|
|
21839
|
+
* // Evento tipado con autocompletado
|
|
21840
|
+
* analytics.logEvent('add_to_cart', {
|
|
21841
|
+
* item_id: '123',
|
|
21842
|
+
* item_name: 'Producto',
|
|
21843
|
+
* value: 99.99,
|
|
21844
|
+
* currency: 'EUR'
|
|
21845
|
+
* });
|
|
21846
|
+
* ```
|
|
21847
|
+
*/
|
|
21848
|
+
logEvent(eventName, params) {
|
|
21849
|
+
this.trackEvent(eventName, params);
|
|
21850
|
+
}
|
|
21851
|
+
/**
|
|
21852
|
+
* Registra un evento custom con parámetros libres.
|
|
21853
|
+
* Usar cuando el evento no está en el catálogo tipado.
|
|
21854
|
+
*
|
|
21855
|
+
* @param eventName - Nombre del evento custom
|
|
21856
|
+
* @param params - Parámetros libres
|
|
21857
|
+
*/
|
|
21858
|
+
logCustomEvent(eventName, params) {
|
|
21859
|
+
const prefixedName = this.eventPrefix + eventName;
|
|
21860
|
+
this.trackEvent(prefixedName, params);
|
|
21861
|
+
}
|
|
21862
|
+
/**
|
|
21863
|
+
* Lógica común para enviar eventos
|
|
21864
|
+
*/
|
|
21865
|
+
trackEvent(eventName, params) {
|
|
21866
|
+
// Verificar consent
|
|
21867
|
+
if (!this._consentState().settings.analytics) {
|
|
21868
|
+
this.addToDebugHistory('event', eventName, params, false);
|
|
21869
|
+
return;
|
|
21870
|
+
}
|
|
21871
|
+
// Aplicar sampling
|
|
21872
|
+
if (!this.shouldSample()) {
|
|
21873
|
+
return;
|
|
21874
|
+
}
|
|
21875
|
+
// Debug mode: solo log, no enviar
|
|
21876
|
+
if (this._isDebugMode()) {
|
|
21877
|
+
this.addToDebugHistory('event', eventName, params, false);
|
|
21878
|
+
console.log(`[Analytics] Event: ${eventName}`, params);
|
|
21879
|
+
return;
|
|
21880
|
+
}
|
|
21881
|
+
// Enviar a Firebase
|
|
21882
|
+
const analytics = this.getAnalyticsInstance();
|
|
21883
|
+
if (analytics) {
|
|
21884
|
+
try {
|
|
21885
|
+
logEvent(analytics, eventName, params);
|
|
21886
|
+
this.addToDebugHistory('event', eventName, params, true);
|
|
21887
|
+
}
|
|
21888
|
+
catch (error) {
|
|
21889
|
+
console.error('[Analytics] Error enviando evento:', error);
|
|
21890
|
+
}
|
|
21891
|
+
}
|
|
21892
|
+
}
|
|
21893
|
+
// ===========================================================================
|
|
21894
|
+
// ECOMMERCE
|
|
21895
|
+
// ===========================================================================
|
|
21896
|
+
/**
|
|
21897
|
+
* Registra vista de item
|
|
21898
|
+
*/
|
|
21899
|
+
logViewItem(item) {
|
|
21900
|
+
this.logEvent('view_item', {
|
|
21901
|
+
item_id: item.item_id,
|
|
21902
|
+
item_name: item.item_name,
|
|
21903
|
+
value: item.price,
|
|
21904
|
+
currency: item.currency,
|
|
21905
|
+
});
|
|
21906
|
+
}
|
|
21907
|
+
/**
|
|
21908
|
+
* Registra agregar al carrito
|
|
21909
|
+
*/
|
|
21910
|
+
logAddToCart(item, quantity = 1) {
|
|
21911
|
+
this.logEvent('add_to_cart', {
|
|
21912
|
+
item_id: item.item_id,
|
|
21913
|
+
item_name: item.item_name,
|
|
21914
|
+
value: (item.price ?? 0) * quantity,
|
|
21915
|
+
currency: item.currency,
|
|
21916
|
+
quantity,
|
|
21917
|
+
});
|
|
21918
|
+
}
|
|
21919
|
+
/**
|
|
21920
|
+
* Registra inicio de checkout
|
|
21921
|
+
*/
|
|
21922
|
+
logBeginCheckout(items, value, currency = 'EUR') {
|
|
21923
|
+
this.logEvent('begin_checkout', {
|
|
21924
|
+
value,
|
|
21925
|
+
currency,
|
|
21926
|
+
items,
|
|
21927
|
+
});
|
|
21928
|
+
}
|
|
21929
|
+
/**
|
|
21930
|
+
* Registra compra completada
|
|
21931
|
+
*/
|
|
21932
|
+
logPurchase(transactionId, items, value, currency = 'EUR') {
|
|
21933
|
+
this.logEvent('purchase', {
|
|
21934
|
+
transaction_id: transactionId,
|
|
21935
|
+
value,
|
|
21936
|
+
currency,
|
|
21937
|
+
items,
|
|
21938
|
+
});
|
|
21939
|
+
}
|
|
21940
|
+
// ===========================================================================
|
|
21941
|
+
// USER PROPERTIES
|
|
21942
|
+
// ===========================================================================
|
|
21943
|
+
/**
|
|
21944
|
+
* Setea el userId para asociar eventos con el usuario.
|
|
21945
|
+
* Llamado automáticamente si enableAuthIntegration=true.
|
|
21946
|
+
*
|
|
21947
|
+
* @param userId - ID del usuario o null para limpiar
|
|
21948
|
+
*/
|
|
21949
|
+
setUserId(userId) {
|
|
21950
|
+
if (!this.isAnalyticsSupported()) {
|
|
21951
|
+
return;
|
|
21952
|
+
}
|
|
21953
|
+
if (this._isDebugMode()) {
|
|
21954
|
+
console.log(`[Analytics] Set userId: ${userId}`);
|
|
21955
|
+
this.addToDebugHistory('user_property', 'user_id', { userId }, false);
|
|
21956
|
+
return;
|
|
21957
|
+
}
|
|
21958
|
+
const analytics = this.getAnalyticsInstance();
|
|
21959
|
+
if (analytics) {
|
|
21960
|
+
try {
|
|
21961
|
+
setUserId(analytics, userId);
|
|
21962
|
+
}
|
|
21963
|
+
catch (error) {
|
|
21964
|
+
console.error('[Analytics] Error seteando userId:', error);
|
|
21965
|
+
}
|
|
21966
|
+
}
|
|
21967
|
+
}
|
|
21968
|
+
/**
|
|
21969
|
+
* Setea propiedades del usuario para segmentación.
|
|
21970
|
+
*
|
|
21971
|
+
* @param properties - Propiedades key-value
|
|
21972
|
+
*
|
|
21973
|
+
* @example
|
|
21974
|
+
* ```typescript
|
|
21975
|
+
* analytics.setUserProperties({
|
|
21976
|
+
* subscription_tier: 'premium',
|
|
21977
|
+
* preferred_language: 'es'
|
|
21978
|
+
* });
|
|
21979
|
+
* ```
|
|
21980
|
+
*/
|
|
21981
|
+
setUserProperties(properties) {
|
|
21982
|
+
if (!this.isAnalyticsSupported()) {
|
|
21983
|
+
return;
|
|
21984
|
+
}
|
|
21985
|
+
if (this._isDebugMode()) {
|
|
21986
|
+
console.log('[Analytics] Set user properties:', properties);
|
|
21987
|
+
this.addToDebugHistory('user_property', 'properties', properties, false);
|
|
21988
|
+
return;
|
|
21989
|
+
}
|
|
21990
|
+
const analytics = this.getAnalyticsInstance();
|
|
21991
|
+
if (analytics) {
|
|
21992
|
+
try {
|
|
21993
|
+
// Convertir a Record<string, string> para Firebase
|
|
21994
|
+
const stringProps = {};
|
|
21995
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
21996
|
+
if (value !== undefined) {
|
|
21997
|
+
stringProps[key] = String(value);
|
|
21998
|
+
}
|
|
21999
|
+
}
|
|
22000
|
+
setUserProperties(analytics, stringProps);
|
|
22001
|
+
}
|
|
22002
|
+
catch (error) {
|
|
22003
|
+
console.error('[Analytics] Error seteando user properties:', error);
|
|
22004
|
+
}
|
|
22005
|
+
}
|
|
22006
|
+
}
|
|
22007
|
+
/**
|
|
22008
|
+
* Setea la organización activa (multi-tenant).
|
|
22009
|
+
* Llamado automáticamente si enableAuthIntegration=true.
|
|
22010
|
+
*
|
|
22011
|
+
* @param orgId - ID de la organización o null
|
|
22012
|
+
*/
|
|
22013
|
+
setActiveOrganization(orgId) {
|
|
22014
|
+
if (orgId) {
|
|
22015
|
+
this.setUserProperties({ active_organization: orgId });
|
|
22016
|
+
}
|
|
22017
|
+
}
|
|
22018
|
+
// ===========================================================================
|
|
22019
|
+
// ERROR TRACKING
|
|
22020
|
+
// ===========================================================================
|
|
22021
|
+
/**
|
|
22022
|
+
* Registra un error para tracking.
|
|
22023
|
+
* Integra automáticamente con Angular ErrorHandler si enableErrorTracking=true.
|
|
22024
|
+
*
|
|
22025
|
+
* @param error - Error o mensaje de error
|
|
22026
|
+
* @param context - Contexto adicional
|
|
22027
|
+
*
|
|
22028
|
+
* @example
|
|
22029
|
+
* ```typescript
|
|
22030
|
+
* try {
|
|
22031
|
+
* await riskyOperation();
|
|
22032
|
+
* } catch (error) {
|
|
22033
|
+
* analytics.logError(error, { context: 'checkout_flow' });
|
|
22034
|
+
* }
|
|
22035
|
+
* ```
|
|
22036
|
+
*/
|
|
22037
|
+
logError(error, context) {
|
|
22038
|
+
const errorMessage = error instanceof Error ? error.message : error;
|
|
22039
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
22040
|
+
const errorType = error instanceof Error ? error.name : 'Error';
|
|
22041
|
+
this.logEvent('error_occurred', {
|
|
22042
|
+
error_type: errorType,
|
|
22043
|
+
error_message: errorMessage.substring(0, 100), // Limitar longitud
|
|
22044
|
+
error_stack: errorStack?.substring(0, 500),
|
|
22045
|
+
context: context ? JSON.stringify(context) : undefined,
|
|
22046
|
+
});
|
|
22047
|
+
}
|
|
22048
|
+
/**
|
|
22049
|
+
* Registra un error fatal (crash-level).
|
|
22050
|
+
*/
|
|
22051
|
+
logFatalError(error, context) {
|
|
22052
|
+
this.logError(error, { ...context, severity: 'fatal' });
|
|
22053
|
+
}
|
|
22054
|
+
// ===========================================================================
|
|
22055
|
+
// CONSENT MODE (GDPR)
|
|
22056
|
+
// ===========================================================================
|
|
22057
|
+
/**
|
|
22058
|
+
* Actualiza el estado de consentimiento del usuario.
|
|
22059
|
+
* Afecta qué datos se recolectan y envían.
|
|
22060
|
+
*
|
|
22061
|
+
* @param consent - Settings de consentimiento
|
|
22062
|
+
*
|
|
22063
|
+
* @example
|
|
22064
|
+
* ```typescript
|
|
22065
|
+
* // Usuario acepta todo
|
|
22066
|
+
* analytics.updateConsent({ analytics: true, advertising: true });
|
|
22067
|
+
*
|
|
22068
|
+
* // Usuario rechaza publicidad
|
|
22069
|
+
* analytics.updateConsent({ analytics: true, advertising: false });
|
|
22070
|
+
* ```
|
|
22071
|
+
*/
|
|
22072
|
+
updateConsent(consent) {
|
|
22073
|
+
const newState = {
|
|
22074
|
+
settings: { ...this._consentState().settings, ...consent },
|
|
22075
|
+
updatedAt: new Date(),
|
|
22076
|
+
hasDecided: true,
|
|
22077
|
+
};
|
|
22078
|
+
this._consentState.set(newState);
|
|
22079
|
+
this.saveConsentToStorage(newState);
|
|
22080
|
+
this.applyConsentToGtag(newState.settings);
|
|
22081
|
+
this.addToDebugHistory('consent', 'update', consent, false);
|
|
22082
|
+
}
|
|
22083
|
+
/**
|
|
22084
|
+
* Deniega todo consentimiento.
|
|
22085
|
+
*/
|
|
22086
|
+
denyAllConsent() {
|
|
22087
|
+
this.updateConsent({
|
|
22088
|
+
analytics: false,
|
|
22089
|
+
advertising: false,
|
|
22090
|
+
functionality: false,
|
|
22091
|
+
security: true, // Siempre permitido
|
|
22092
|
+
});
|
|
22093
|
+
}
|
|
22094
|
+
/**
|
|
22095
|
+
* Acepta todo consentimiento.
|
|
22096
|
+
*/
|
|
22097
|
+
grantAllConsent() {
|
|
22098
|
+
this.updateConsent({
|
|
22099
|
+
analytics: true,
|
|
22100
|
+
advertising: true,
|
|
22101
|
+
functionality: true,
|
|
22102
|
+
security: true,
|
|
22103
|
+
});
|
|
22104
|
+
}
|
|
22105
|
+
/**
|
|
22106
|
+
* Obtiene el estado actual de consentimiento.
|
|
22107
|
+
*/
|
|
22108
|
+
getConsentState() {
|
|
22109
|
+
return this._consentState();
|
|
22110
|
+
}
|
|
22111
|
+
/**
|
|
22112
|
+
* Aplica consent settings a gtag (GA4 Consent Mode v2)
|
|
22113
|
+
*/
|
|
22114
|
+
applyConsentToGtag(settings) {
|
|
22115
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
22116
|
+
return;
|
|
22117
|
+
}
|
|
22118
|
+
const gtag = window.gtag;
|
|
22119
|
+
if (typeof gtag !== 'function') {
|
|
22120
|
+
return;
|
|
22121
|
+
}
|
|
22122
|
+
try {
|
|
22123
|
+
gtag('consent', 'update', {
|
|
22124
|
+
analytics_storage: settings.analytics ? 'granted' : 'denied',
|
|
22125
|
+
ad_storage: settings.advertising ? 'granted' : 'denied',
|
|
22126
|
+
ad_user_data: settings.advertising ? 'granted' : 'denied',
|
|
22127
|
+
ad_personalization: settings.advertising ? 'granted' : 'denied',
|
|
22128
|
+
functionality_storage: settings.functionality ? 'granted' : 'denied',
|
|
22129
|
+
security_storage: settings.security !== false ? 'granted' : 'denied',
|
|
22130
|
+
});
|
|
22131
|
+
}
|
|
22132
|
+
catch (error) {
|
|
22133
|
+
console.warn('[Analytics] Error aplicando consent a gtag:', error);
|
|
22134
|
+
}
|
|
22135
|
+
}
|
|
22136
|
+
/**
|
|
22137
|
+
* Carga consent desde localStorage
|
|
22138
|
+
*/
|
|
22139
|
+
loadConsentFromStorage() {
|
|
22140
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
22141
|
+
return;
|
|
22142
|
+
}
|
|
22143
|
+
try {
|
|
22144
|
+
const stored = localStorage.getItem(this.consentStorageKey);
|
|
22145
|
+
if (stored) {
|
|
22146
|
+
const parsed = JSON.parse(stored);
|
|
22147
|
+
this._consentState.set({
|
|
22148
|
+
...parsed,
|
|
22149
|
+
updatedAt: parsed.updatedAt ? new Date(parsed.updatedAt) : null,
|
|
22150
|
+
});
|
|
22151
|
+
}
|
|
22152
|
+
else if (this.analyticsConfig.defaultConsent) {
|
|
22153
|
+
// Aplicar consent por defecto si no hay guardado
|
|
22154
|
+
this._consentState.set({
|
|
22155
|
+
settings: this.analyticsConfig.defaultConsent,
|
|
22156
|
+
updatedAt: null,
|
|
22157
|
+
hasDecided: false,
|
|
22158
|
+
});
|
|
22159
|
+
}
|
|
22160
|
+
}
|
|
22161
|
+
catch (error) {
|
|
22162
|
+
console.warn('[Analytics] Error cargando consent:', error);
|
|
22163
|
+
}
|
|
22164
|
+
}
|
|
22165
|
+
/**
|
|
22166
|
+
* Guarda consent en localStorage
|
|
22167
|
+
*/
|
|
22168
|
+
saveConsentToStorage(state) {
|
|
22169
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
22170
|
+
return;
|
|
22171
|
+
}
|
|
22172
|
+
try {
|
|
22173
|
+
localStorage.setItem(this.consentStorageKey, JSON.stringify(state));
|
|
22174
|
+
}
|
|
22175
|
+
catch (error) {
|
|
22176
|
+
console.warn('[Analytics] Error guardando consent:', error);
|
|
22177
|
+
}
|
|
22178
|
+
}
|
|
22179
|
+
// ===========================================================================
|
|
22180
|
+
// TIMING / PERFORMANCE
|
|
22181
|
+
// ===========================================================================
|
|
22182
|
+
/**
|
|
22183
|
+
* Registra una métrica de timing/performance.
|
|
22184
|
+
*
|
|
22185
|
+
* @param name - Nombre de la métrica
|
|
22186
|
+
* @param valueMs - Valor en milisegundos
|
|
22187
|
+
* @param params - Parámetros adicionales
|
|
22188
|
+
*
|
|
22189
|
+
* @example
|
|
22190
|
+
* ```typescript
|
|
22191
|
+
* const start = performance.now();
|
|
22192
|
+
* await loadData();
|
|
22193
|
+
* analytics.logTiming('data_load', performance.now() - start, {
|
|
22194
|
+
* category: 'api',
|
|
22195
|
+
* endpoint: '/products'
|
|
22196
|
+
* });
|
|
22197
|
+
* ```
|
|
22198
|
+
*/
|
|
22199
|
+
logTiming(name, valueMs, params) {
|
|
22200
|
+
this.logEvent('performance_metric', {
|
|
22201
|
+
metric_name: name,
|
|
22202
|
+
value: Math.round(valueMs),
|
|
22203
|
+
unit: 'ms',
|
|
22204
|
+
...params,
|
|
22205
|
+
});
|
|
22206
|
+
}
|
|
22207
|
+
// ===========================================================================
|
|
22208
|
+
// DEBUG
|
|
22209
|
+
// ===========================================================================
|
|
22210
|
+
/**
|
|
22211
|
+
* Habilita/deshabilita modo debug.
|
|
22212
|
+
* En debug: logea a consola, no envía a Firebase.
|
|
22213
|
+
*/
|
|
22214
|
+
setDebugMode(enabled) {
|
|
22215
|
+
this._isDebugMode.set(enabled);
|
|
22216
|
+
console.log(`[Analytics] Debug mode: ${enabled ? 'ON' : 'OFF'}`);
|
|
22217
|
+
}
|
|
22218
|
+
/**
|
|
22219
|
+
* Obtiene historial de eventos (solo en debug mode).
|
|
22220
|
+
* Útil para testing y desarrollo.
|
|
22221
|
+
*/
|
|
22222
|
+
getDebugHistory() {
|
|
22223
|
+
return this._debugHistory();
|
|
22224
|
+
}
|
|
22225
|
+
/**
|
|
22226
|
+
* Limpia historial de debug.
|
|
22227
|
+
*/
|
|
22228
|
+
clearDebugHistory() {
|
|
22229
|
+
this._debugHistory.set([]);
|
|
22230
|
+
}
|
|
22231
|
+
/**
|
|
22232
|
+
* Agrega evento al historial de debug
|
|
22233
|
+
*/
|
|
22234
|
+
addToDebugHistory(type, name, params, sent = false) {
|
|
22235
|
+
if (!this._isDebugMode()) {
|
|
22236
|
+
return;
|
|
22237
|
+
}
|
|
22238
|
+
const event = {
|
|
22239
|
+
timestamp: new Date(),
|
|
22240
|
+
type,
|
|
22241
|
+
name,
|
|
22242
|
+
params,
|
|
22243
|
+
sent,
|
|
22244
|
+
};
|
|
22245
|
+
this._debugHistory.update((history) => {
|
|
22246
|
+
const newHistory = [event, ...history];
|
|
22247
|
+
return newHistory.slice(0, MAX_DEBUG_HISTORY);
|
|
22248
|
+
});
|
|
22249
|
+
}
|
|
22250
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsService, deps: [{ token: i0.Injector }, { token: VALTECH_FIREBASE_CONFIG }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
22251
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsService, providedIn: 'root' }); }
|
|
22252
|
+
}
|
|
22253
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsService, decorators: [{
|
|
22254
|
+
type: Injectable,
|
|
22255
|
+
args: [{ providedIn: 'root' }]
|
|
22256
|
+
}], ctorParameters: () => [{ type: i0.Injector }, { type: undefined, decorators: [{
|
|
22257
|
+
type: Inject,
|
|
22258
|
+
args: [VALTECH_FIREBASE_CONFIG]
|
|
22259
|
+
}] }, { type: Object, decorators: [{
|
|
22260
|
+
type: Inject,
|
|
22261
|
+
args: [PLATFORM_ID]
|
|
22262
|
+
}] }] });
|
|
22263
|
+
|
|
22264
|
+
var analytics_service = /*#__PURE__*/Object.freeze({
|
|
22265
|
+
__proto__: null,
|
|
22266
|
+
AnalyticsService: AnalyticsService
|
|
22267
|
+
});
|
|
22268
|
+
|
|
22269
|
+
/**
|
|
22270
|
+
* Analytics Error Handler
|
|
22271
|
+
*
|
|
22272
|
+
* ErrorHandler personalizado que envía errores no capturados a Firebase Analytics.
|
|
22273
|
+
* Se activa si enableErrorTracking=true en analyticsConfig.
|
|
22274
|
+
*/
|
|
22275
|
+
/**
|
|
22276
|
+
* ErrorHandler que trackea errores en Firebase Analytics.
|
|
22277
|
+
*
|
|
22278
|
+
* Captura errores no manejados de la aplicación y los envía a GA4
|
|
22279
|
+
* como eventos 'error_occurred'. También delega al ErrorHandler
|
|
22280
|
+
* default para mantener el comportamiento de console.error.
|
|
22281
|
+
*
|
|
22282
|
+
* @example
|
|
22283
|
+
* ```typescript
|
|
22284
|
+
* // Se activa automáticamente si enableErrorTracking=true
|
|
22285
|
+
* provideValtechFirebase({
|
|
22286
|
+
* firebase: environment.firebase,
|
|
22287
|
+
* enableAnalytics: true,
|
|
22288
|
+
* analyticsConfig: {
|
|
22289
|
+
* enableErrorTracking: true,
|
|
22290
|
+
* },
|
|
22291
|
+
* });
|
|
22292
|
+
* ```
|
|
22293
|
+
*/
|
|
22294
|
+
class AnalyticsErrorHandler {
|
|
22295
|
+
constructor() {
|
|
22296
|
+
this.analytics = inject(AnalyticsService);
|
|
22297
|
+
this.defaultHandler = new ErrorHandler();
|
|
22298
|
+
}
|
|
22299
|
+
/**
|
|
22300
|
+
* Maneja un error no capturado.
|
|
22301
|
+
* Envía el error a Analytics y luego al handler default.
|
|
22302
|
+
*/
|
|
22303
|
+
handleError(error) {
|
|
22304
|
+
// Enviar a Analytics
|
|
22305
|
+
try {
|
|
22306
|
+
this.trackError(error);
|
|
22307
|
+
}
|
|
22308
|
+
catch (trackingError) {
|
|
22309
|
+
// No fallar si el tracking falla
|
|
22310
|
+
console.warn('[AnalyticsErrorHandler] Error tracking failed:', trackingError);
|
|
22311
|
+
}
|
|
22312
|
+
// Delegar al handler default (console.error)
|
|
22313
|
+
this.defaultHandler.handleError(error);
|
|
22314
|
+
}
|
|
22315
|
+
/**
|
|
22316
|
+
* Trackea el error en Analytics
|
|
22317
|
+
*/
|
|
22318
|
+
trackError(error) {
|
|
22319
|
+
// Extraer información del error
|
|
22320
|
+
const errorInfo = this.extractErrorInfo(error);
|
|
22321
|
+
this.analytics.logError(errorInfo.error, {
|
|
22322
|
+
source: 'uncaught',
|
|
22323
|
+
url: this.getCurrentUrl(),
|
|
22324
|
+
...errorInfo.context,
|
|
22325
|
+
});
|
|
22326
|
+
}
|
|
22327
|
+
/**
|
|
22328
|
+
* Extrae información útil del error
|
|
22329
|
+
*/
|
|
22330
|
+
extractErrorInfo(error) {
|
|
22331
|
+
const context = {};
|
|
22332
|
+
// Error estándar
|
|
22333
|
+
if (error instanceof Error) {
|
|
22334
|
+
// Detectar errores de chunk loading (lazy loading)
|
|
22335
|
+
if (error.message.includes('Loading chunk')) {
|
|
22336
|
+
context['error_category'] = 'chunk_loading';
|
|
22337
|
+
}
|
|
22338
|
+
// Detectar errores de red
|
|
22339
|
+
if (error.message.includes('NetworkError') || error.message.includes('Failed to fetch')) {
|
|
22340
|
+
context['error_category'] = 'network';
|
|
22341
|
+
}
|
|
22342
|
+
return { error, context };
|
|
22343
|
+
}
|
|
22344
|
+
// ErrorEvent (ej: errores de script)
|
|
22345
|
+
if (typeof ErrorEvent !== 'undefined' && error instanceof ErrorEvent) {
|
|
22346
|
+
return {
|
|
22347
|
+
error: new Error(error.message || 'Script error'),
|
|
22348
|
+
context: {
|
|
22349
|
+
filename: error.filename || 'unknown',
|
|
22350
|
+
lineno: String(error.lineno || 0),
|
|
22351
|
+
colno: String(error.colno || 0),
|
|
22352
|
+
},
|
|
22353
|
+
};
|
|
22354
|
+
}
|
|
22355
|
+
// PromiseRejection
|
|
22356
|
+
if (this.isPromiseRejection(error)) {
|
|
22357
|
+
const reason = error.reason;
|
|
22358
|
+
if (reason instanceof Error) {
|
|
22359
|
+
return {
|
|
22360
|
+
error: reason,
|
|
22361
|
+
context: { error_category: 'unhandled_promise' },
|
|
22362
|
+
};
|
|
22363
|
+
}
|
|
22364
|
+
return {
|
|
22365
|
+
error: new Error(String(reason) || 'Unhandled promise rejection'),
|
|
22366
|
+
context: { error_category: 'unhandled_promise' },
|
|
22367
|
+
};
|
|
22368
|
+
}
|
|
22369
|
+
// Objeto con message
|
|
22370
|
+
if (error && typeof error === 'object' && 'message' in error) {
|
|
22371
|
+
return {
|
|
22372
|
+
error: new Error(String(error.message)),
|
|
22373
|
+
context,
|
|
22374
|
+
};
|
|
22375
|
+
}
|
|
22376
|
+
// Fallback: convertir a string
|
|
22377
|
+
return {
|
|
22378
|
+
error: new Error(String(error) || 'Unknown error'),
|
|
22379
|
+
context,
|
|
22380
|
+
};
|
|
22381
|
+
}
|
|
22382
|
+
/**
|
|
22383
|
+
* Verifica si es un PromiseRejectionEvent
|
|
22384
|
+
*/
|
|
22385
|
+
isPromiseRejection(error) {
|
|
22386
|
+
return (typeof PromiseRejectionEvent !== 'undefined' &&
|
|
22387
|
+
error instanceof PromiseRejectionEvent);
|
|
22388
|
+
}
|
|
22389
|
+
/**
|
|
22390
|
+
* Obtiene la URL actual de forma segura
|
|
22391
|
+
*/
|
|
22392
|
+
getCurrentUrl() {
|
|
22393
|
+
try {
|
|
22394
|
+
return window?.location?.href || 'unknown';
|
|
22395
|
+
}
|
|
22396
|
+
catch {
|
|
22397
|
+
return 'unknown';
|
|
22398
|
+
}
|
|
22399
|
+
}
|
|
22400
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsErrorHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
22401
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsErrorHandler }); }
|
|
22402
|
+
}
|
|
22403
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsErrorHandler, decorators: [{
|
|
22404
|
+
type: Injectable
|
|
22405
|
+
}] });
|
|
22406
|
+
|
|
22407
|
+
/**
|
|
22408
|
+
* Analytics Router Tracker
|
|
22409
|
+
*
|
|
22410
|
+
* Servicio que trackea automáticamente page views cuando el usuario navega.
|
|
22411
|
+
* Se activa automáticamente si enablePageViewTracking=true en analyticsConfig.
|
|
22412
|
+
*/
|
|
22413
|
+
/**
|
|
22414
|
+
* Tracker automático de page views via Router.
|
|
22415
|
+
*
|
|
22416
|
+
* Este servicio escucha eventos de navegación del Router y registra
|
|
22417
|
+
* page views automáticamente en Firebase Analytics.
|
|
22418
|
+
*
|
|
22419
|
+
* Se excluyen rutas configuradas en `analyticsConfig.excludeRoutes`.
|
|
22420
|
+
*
|
|
22421
|
+
* @example
|
|
22422
|
+
* ```typescript
|
|
22423
|
+
* // Se activa automáticamente si enablePageViewTracking=true
|
|
22424
|
+
* provideValtechFirebase({
|
|
22425
|
+
* firebase: environment.firebase,
|
|
22426
|
+
* enableAnalytics: true,
|
|
22427
|
+
* analyticsConfig: {
|
|
22428
|
+
* enablePageViewTracking: true,
|
|
22429
|
+
* excludeRoutes: ['/admin/*', '/debug/*'],
|
|
22430
|
+
* },
|
|
22431
|
+
* });
|
|
22432
|
+
* ```
|
|
22433
|
+
*/
|
|
22434
|
+
class AnalyticsRouterTracker {
|
|
22435
|
+
constructor(config) {
|
|
22436
|
+
this.config = config;
|
|
22437
|
+
this.analytics = inject(AnalyticsService);
|
|
22438
|
+
this.router = inject(Router);
|
|
22439
|
+
this.destroyRef = inject(DestroyRef);
|
|
22440
|
+
const analyticsConfig = config.analyticsConfig ?? {};
|
|
22441
|
+
this.enabled = analyticsConfig.enablePageViewTracking !== false;
|
|
22442
|
+
this.excludePatterns = this.compileExcludePatterns(analyticsConfig.excludeRoutes ?? []);
|
|
22443
|
+
if (this.enabled && config.enableAnalytics) {
|
|
22444
|
+
this.startTracking();
|
|
22445
|
+
}
|
|
22446
|
+
}
|
|
22447
|
+
/**
|
|
22448
|
+
* Inicia el tracking de navegación
|
|
22449
|
+
*/
|
|
22450
|
+
startTracking() {
|
|
22451
|
+
this.router.events
|
|
22452
|
+
.pipe(filter$1((event) => event instanceof NavigationEnd), filter$1((event) => !this.isExcluded(event.urlAfterRedirects)), takeUntilDestroyed(this.destroyRef))
|
|
22453
|
+
.subscribe((event) => {
|
|
22454
|
+
this.analytics.logPageView(event.urlAfterRedirects);
|
|
22455
|
+
});
|
|
22456
|
+
}
|
|
22457
|
+
/**
|
|
22458
|
+
* Compila patrones de exclusión a RegExp
|
|
22459
|
+
*/
|
|
22460
|
+
compileExcludePatterns(patterns) {
|
|
22461
|
+
return patterns.map((pattern) => {
|
|
22462
|
+
// Convertir glob pattern a regex
|
|
22463
|
+
// Ej: '/admin/*' -> /^\/admin\/.*$/
|
|
22464
|
+
const regexPattern = pattern
|
|
22465
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escapar caracteres especiales
|
|
22466
|
+
.replace(/\*/g, '.*'); // Convertir * a .*
|
|
22467
|
+
return new RegExp(`^${regexPattern}$`);
|
|
22468
|
+
});
|
|
22469
|
+
}
|
|
22470
|
+
/**
|
|
22471
|
+
* Verifica si una URL debe ser excluida del tracking
|
|
22472
|
+
*/
|
|
22473
|
+
isExcluded(url) {
|
|
22474
|
+
// Remover query params para la comparación
|
|
22475
|
+
const path = url.split('?')[0];
|
|
22476
|
+
return this.excludePatterns.some((pattern) => pattern.test(path));
|
|
22477
|
+
}
|
|
22478
|
+
/**
|
|
22479
|
+
* Registra un page view manualmente.
|
|
22480
|
+
* Útil para casos donde necesitas trackear manualmente.
|
|
22481
|
+
*/
|
|
22482
|
+
trackPageView(path, title) {
|
|
22483
|
+
if (this.isExcluded(path)) {
|
|
22484
|
+
return;
|
|
22485
|
+
}
|
|
22486
|
+
this.analytics.logPageView(path, title);
|
|
22487
|
+
}
|
|
22488
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsRouterTracker, deps: [{ token: VALTECH_FIREBASE_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
22489
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsRouterTracker, providedIn: 'root' }); }
|
|
22490
|
+
}
|
|
22491
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsRouterTracker, decorators: [{
|
|
22492
|
+
type: Injectable,
|
|
22493
|
+
args: [{ providedIn: 'root' }]
|
|
22494
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
22495
|
+
type: Inject,
|
|
22496
|
+
args: [VALTECH_FIREBASE_CONFIG]
|
|
22497
|
+
}] }] });
|
|
22498
|
+
|
|
22499
|
+
/**
|
|
22500
|
+
* Firebase Configuration
|
|
22501
|
+
*
|
|
22502
|
+
* Configuración e inicialización de Firebase para aplicaciones Angular.
|
|
22503
|
+
* Usa provideValtechFirebase() en el bootstrap de tu aplicación.
|
|
22504
|
+
*/
|
|
22505
|
+
/**
|
|
22506
|
+
* Token de inyección para la configuración de Firebase.
|
|
22507
|
+
* Usado internamente por los servicios de Firebase.
|
|
22508
|
+
*/
|
|
22509
|
+
const VALTECH_FIREBASE_CONFIG = new InjectionToken('ValtechFirebaseConfig');
|
|
22510
|
+
/**
|
|
22511
|
+
* Provee Firebase a la aplicación Angular.
|
|
22512
|
+
*
|
|
22513
|
+
* @param config - Configuración de Firebase
|
|
22514
|
+
* @returns EnvironmentProviders para usar en bootstrapApplication
|
|
22515
|
+
*
|
|
22516
|
+
* @example
|
|
22517
|
+
* ```typescript
|
|
22518
|
+
* // main.ts
|
|
22519
|
+
* import { bootstrapApplication } from '@angular/platform-browser';
|
|
22520
|
+
* import { provideValtechFirebase } from 'valtech-components';
|
|
22521
|
+
* import { environment } from './environments/environment';
|
|
22522
|
+
*
|
|
22523
|
+
* bootstrapApplication(AppComponent, {
|
|
22524
|
+
* providers: [
|
|
22525
|
+
* provideValtechFirebase({
|
|
22526
|
+
* firebase: environment.firebase,
|
|
22527
|
+
* persistence: true,
|
|
22528
|
+
* emulator: environment.useEmulators ? {
|
|
22529
|
+
* firestore: { host: 'localhost', port: 8080 },
|
|
22530
|
+
* auth: { host: 'localhost', port: 9099 },
|
|
22531
|
+
* storage: { host: 'localhost', port: 9199 },
|
|
22532
|
+
* } : undefined,
|
|
22533
|
+
* }),
|
|
22534
|
+
* ],
|
|
22535
|
+
* });
|
|
22536
|
+
* ```
|
|
22537
|
+
*/
|
|
22538
|
+
function provideValtechFirebase(config) {
|
|
22539
|
+
// Construir array de providers base
|
|
22540
|
+
const providers = [
|
|
22541
|
+
// Guardar configuración para uso en servicios
|
|
22542
|
+
{ provide: VALTECH_FIREBASE_CONFIG, useValue: config },
|
|
22543
|
+
// Inicializar Firebase App
|
|
22544
|
+
provideFirebaseApp(() => initializeApp(config.firebase)),
|
|
22545
|
+
// Firestore con soporte para emuladores y persistencia
|
|
22546
|
+
provideFirestore(() => {
|
|
22547
|
+
const firestore = getFirestore();
|
|
22548
|
+
// Conectar a emulador si está configurado
|
|
22549
|
+
if (config.emulator?.firestore) {
|
|
22550
|
+
connectFirestoreEmulator(firestore, config.emulator.firestore.host, config.emulator.firestore.port);
|
|
22551
|
+
}
|
|
22552
|
+
// Habilitar persistencia offline si está configurada
|
|
22553
|
+
if (config.persistence) {
|
|
22554
|
+
enableIndexedDbPersistence(firestore).catch((err) => {
|
|
22555
|
+
if (err.code === 'failed-precondition') {
|
|
22556
|
+
console.warn('[ValtechFirebase] Persistencia no disponible: múltiples pestañas abiertas');
|
|
22557
|
+
}
|
|
22558
|
+
else if (err.code === 'unimplemented') {
|
|
22559
|
+
console.warn('[ValtechFirebase] Persistencia no soportada en este navegador');
|
|
22560
|
+
}
|
|
22561
|
+
});
|
|
22562
|
+
}
|
|
22563
|
+
return firestore;
|
|
22564
|
+
}),
|
|
22565
|
+
// Auth con soporte para emulador
|
|
22566
|
+
provideAuth(() => {
|
|
22567
|
+
const auth = getAuth();
|
|
22568
|
+
// Conectar a emulador si está configurado
|
|
22569
|
+
if (config.emulator?.auth) {
|
|
22570
|
+
connectAuthEmulator(auth, `http://${config.emulator.auth.host}:${config.emulator.auth.port}`, { disableWarnings: true });
|
|
22571
|
+
}
|
|
22572
|
+
return auth;
|
|
22573
|
+
}),
|
|
22574
|
+
// Storage con soporte para emulador
|
|
22575
|
+
provideStorage(() => {
|
|
22576
|
+
const storage = getStorage();
|
|
22577
|
+
// Conectar a emulador si está configurado
|
|
22578
|
+
if (config.emulator?.storage) {
|
|
22579
|
+
connectStorageEmulator(storage, config.emulator.storage.host, config.emulator.storage.port);
|
|
22580
|
+
}
|
|
22581
|
+
return storage;
|
|
22582
|
+
}),
|
|
22583
|
+
];
|
|
22584
|
+
// Messaging (FCM) - solo si está explícitamente habilitado
|
|
22585
|
+
// Requiere Service Worker configurado, puede congelar la app si no está disponible
|
|
22586
|
+
if (config.enableMessaging) {
|
|
22587
|
+
// Pre-registrar SW custom antes de que Firebase lo intente
|
|
22588
|
+
if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
|
|
22589
|
+
navigator.serviceWorker.register('/firebase-messaging-sw.js').catch(console.error);
|
|
21575
22590
|
}
|
|
21576
22591
|
providers.push(provideMessaging(() => getMessaging()));
|
|
21577
22592
|
}
|
|
22593
|
+
// Analytics (GA4) - solo si está explícitamente habilitado
|
|
22594
|
+
// Requiere measurementId en firebase config
|
|
22595
|
+
if (config.enableAnalytics && config.firebase.measurementId) {
|
|
22596
|
+
providers.push(provideAnalytics(() => getAnalytics()));
|
|
22597
|
+
// Router tracker para page views automáticos (por defecto habilitado)
|
|
22598
|
+
if (config.analyticsConfig?.enablePageViewTracking !== false) {
|
|
22599
|
+
providers.push(AnalyticsRouterTracker);
|
|
22600
|
+
}
|
|
22601
|
+
// Error handler para tracking automático de errores
|
|
22602
|
+
if (config.analyticsConfig?.enableErrorTracking) {
|
|
22603
|
+
providers.push({
|
|
22604
|
+
provide: ErrorHandler,
|
|
22605
|
+
useClass: AnalyticsErrorHandler,
|
|
22606
|
+
});
|
|
22607
|
+
}
|
|
22608
|
+
}
|
|
21578
22609
|
return makeEnvironmentProviders(providers);
|
|
21579
22610
|
}
|
|
21580
22611
|
/**
|
|
@@ -25255,6 +26286,250 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
25255
26286
|
args: [VALTECH_AUTH_CONFIG]
|
|
25256
26287
|
}] }] });
|
|
25257
26288
|
|
|
26289
|
+
/**
|
|
26290
|
+
* Servicio de OAuth para login social.
|
|
26291
|
+
*
|
|
26292
|
+
* Implementa flujo OAuth server-side con popup:
|
|
26293
|
+
* 1. Frontend abre popup hacia backend
|
|
26294
|
+
* 2. Backend redirige a provider (Google, Apple, Microsoft)
|
|
26295
|
+
* 3. Usuario autoriza
|
|
26296
|
+
* 4. Backend intercambia code, genera JWT, redirige con tokens
|
|
26297
|
+
* 5. Popup envía tokens a ventana padre via postMessage
|
|
26298
|
+
*
|
|
26299
|
+
* @example
|
|
26300
|
+
* ```typescript
|
|
26301
|
+
* import { OAuthService, AuthService } from 'valtech-components';
|
|
26302
|
+
*
|
|
26303
|
+
* @Component({...})
|
|
26304
|
+
* export class LoginComponent {
|
|
26305
|
+
* private oauth = inject(OAuthService);
|
|
26306
|
+
* private auth = inject(AuthService);
|
|
26307
|
+
*
|
|
26308
|
+
* async loginWithGoogle() {
|
|
26309
|
+
* this.oauth.startFlow('google').subscribe({
|
|
26310
|
+
* next: (result) => {
|
|
26311
|
+
* // Tokens recibidos, guardar en auth state
|
|
26312
|
+
* this.auth.handleOAuthSuccess(result);
|
|
26313
|
+
* this.router.navigate(['/']);
|
|
26314
|
+
* },
|
|
26315
|
+
* error: (error) => {
|
|
26316
|
+
* console.error('OAuth failed:', error);
|
|
26317
|
+
* }
|
|
26318
|
+
* });
|
|
26319
|
+
* }
|
|
26320
|
+
* }
|
|
26321
|
+
* ```
|
|
26322
|
+
*/
|
|
26323
|
+
class OAuthService {
|
|
26324
|
+
constructor(config, http, ngZone) {
|
|
26325
|
+
this.config = config;
|
|
26326
|
+
this.http = http;
|
|
26327
|
+
this.ngZone = ngZone;
|
|
26328
|
+
this.popup = null;
|
|
26329
|
+
this.messageHandler = null;
|
|
26330
|
+
this.checkClosedInterval = null;
|
|
26331
|
+
}
|
|
26332
|
+
/**
|
|
26333
|
+
* Inicia flujo OAuth en popup.
|
|
26334
|
+
* Retorna Observable que emite cuando el usuario completa el flujo.
|
|
26335
|
+
*
|
|
26336
|
+
* @param provider - Proveedor OAuth ('google', 'apple', 'microsoft')
|
|
26337
|
+
* @returns Observable que emite OAuthResult o error
|
|
26338
|
+
*/
|
|
26339
|
+
startFlow(provider) {
|
|
26340
|
+
return new Observable(observer => {
|
|
26341
|
+
// Construir URL de inicio
|
|
26342
|
+
const redirectUri = `${window.location.origin}/auth/oauth/callback`;
|
|
26343
|
+
const startUrl = `${this.config.apiUrl}/v2/auth/oauth/${provider}/start?redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
26344
|
+
// Abrir popup centrado
|
|
26345
|
+
const width = 500;
|
|
26346
|
+
const height = 600;
|
|
26347
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
26348
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
26349
|
+
const features = `width=${width},height=${height},left=${left},top=${top},popup=yes`;
|
|
26350
|
+
this.popup = window.open(startUrl, 'oauth', features);
|
|
26351
|
+
if (!this.popup) {
|
|
26352
|
+
observer.error({
|
|
26353
|
+
code: 'POPUP_BLOCKED',
|
|
26354
|
+
message: 'El navegador bloqueó la ventana emergente. Por favor, permite popups para este sitio.',
|
|
26355
|
+
});
|
|
26356
|
+
return () => { };
|
|
26357
|
+
}
|
|
26358
|
+
// Escuchar mensajes del popup
|
|
26359
|
+
this.messageHandler = (event) => {
|
|
26360
|
+
// Validar origen
|
|
26361
|
+
if (event.origin !== window.location.origin) {
|
|
26362
|
+
return;
|
|
26363
|
+
}
|
|
26364
|
+
// Validar tipo de mensaje
|
|
26365
|
+
const data = event.data;
|
|
26366
|
+
if (data?.type !== 'oauth-callback') {
|
|
26367
|
+
return;
|
|
26368
|
+
}
|
|
26369
|
+
// Limpiar
|
|
26370
|
+
this.cleanup();
|
|
26371
|
+
// Emitir resultado dentro de NgZone para trigger change detection
|
|
26372
|
+
this.ngZone.run(() => {
|
|
26373
|
+
if (data.error) {
|
|
26374
|
+
observer.error(data.error);
|
|
26375
|
+
}
|
|
26376
|
+
else if (data.tokens) {
|
|
26377
|
+
observer.next(data.tokens);
|
|
26378
|
+
observer.complete();
|
|
26379
|
+
}
|
|
26380
|
+
else {
|
|
26381
|
+
observer.error({
|
|
26382
|
+
code: 'INVALID_RESPONSE',
|
|
26383
|
+
message: 'Respuesta inválida del servidor de autenticación',
|
|
26384
|
+
});
|
|
26385
|
+
}
|
|
26386
|
+
});
|
|
26387
|
+
};
|
|
26388
|
+
window.addEventListener('message', this.messageHandler);
|
|
26389
|
+
// Verificar si popup se cierra manualmente
|
|
26390
|
+
this.checkClosedInterval = setInterval(() => {
|
|
26391
|
+
if (this.popup?.closed) {
|
|
26392
|
+
this.cleanup();
|
|
26393
|
+
this.ngZone.run(() => {
|
|
26394
|
+
observer.error({
|
|
26395
|
+
code: 'POPUP_CLOSED',
|
|
26396
|
+
message: 'Se cerró la ventana de autenticación',
|
|
26397
|
+
});
|
|
26398
|
+
});
|
|
26399
|
+
}
|
|
26400
|
+
}, 500);
|
|
26401
|
+
// Cleanup cuando el observable se destruye
|
|
26402
|
+
return () => this.cleanup();
|
|
26403
|
+
});
|
|
26404
|
+
}
|
|
26405
|
+
/**
|
|
26406
|
+
* Inicia flujo de linking para vincular un proveedor adicional.
|
|
26407
|
+
* Requiere que el usuario esté autenticado.
|
|
26408
|
+
*
|
|
26409
|
+
* @param provider - Proveedor OAuth a vincular
|
|
26410
|
+
* @returns Observable que emite cuando se completa el linking
|
|
26411
|
+
*/
|
|
26412
|
+
startLinkFlow(provider) {
|
|
26413
|
+
return new Observable(observer => {
|
|
26414
|
+
const redirectUri = `${window.location.origin}/auth/oauth/callback`;
|
|
26415
|
+
const startUrl = `${this.config.apiUrl}/v2/auth/oauth/link/${provider}/start?redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
26416
|
+
const width = 500;
|
|
26417
|
+
const height = 600;
|
|
26418
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
26419
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
26420
|
+
const features = `width=${width},height=${height},left=${left},top=${top},popup=yes`;
|
|
26421
|
+
this.popup = window.open(startUrl, 'oauth-link', features);
|
|
26422
|
+
if (!this.popup) {
|
|
26423
|
+
observer.error({
|
|
26424
|
+
code: 'POPUP_BLOCKED',
|
|
26425
|
+
message: 'El navegador bloqueó la ventana emergente',
|
|
26426
|
+
});
|
|
26427
|
+
return () => { }; // cleanup function
|
|
26428
|
+
}
|
|
26429
|
+
this.messageHandler = (event) => {
|
|
26430
|
+
if (event.origin !== window.location.origin)
|
|
26431
|
+
return;
|
|
26432
|
+
const data = event.data;
|
|
26433
|
+
if (data?.type !== 'oauth-callback')
|
|
26434
|
+
return;
|
|
26435
|
+
this.cleanup();
|
|
26436
|
+
this.ngZone.run(() => {
|
|
26437
|
+
if (data.error) {
|
|
26438
|
+
observer.error(data.error);
|
|
26439
|
+
}
|
|
26440
|
+
else {
|
|
26441
|
+
observer.next(data.tokens || {});
|
|
26442
|
+
observer.complete();
|
|
26443
|
+
}
|
|
26444
|
+
});
|
|
26445
|
+
};
|
|
26446
|
+
window.addEventListener('message', this.messageHandler);
|
|
26447
|
+
this.checkClosedInterval = setInterval(() => {
|
|
26448
|
+
if (this.popup?.closed) {
|
|
26449
|
+
this.cleanup();
|
|
26450
|
+
this.ngZone.run(() => {
|
|
26451
|
+
observer.error({
|
|
26452
|
+
code: 'POPUP_CLOSED',
|
|
26453
|
+
message: 'Se cerró la ventana de autenticación',
|
|
26454
|
+
});
|
|
26455
|
+
});
|
|
26456
|
+
}
|
|
26457
|
+
}, 500);
|
|
26458
|
+
return () => this.cleanup();
|
|
26459
|
+
});
|
|
26460
|
+
}
|
|
26461
|
+
/**
|
|
26462
|
+
* Obtiene los proveedores OAuth vinculados al usuario.
|
|
26463
|
+
*/
|
|
26464
|
+
getLinkedProviders() {
|
|
26465
|
+
return this.http
|
|
26466
|
+
.get(`${this.config.apiUrl}/v2/auth/oauth/providers`)
|
|
26467
|
+
.pipe(catchError(error => throwError(() => ({
|
|
26468
|
+
code: error.error?.code || 'FETCH_ERROR',
|
|
26469
|
+
message: error.error?.message || 'Error al obtener proveedores vinculados',
|
|
26470
|
+
}))));
|
|
26471
|
+
}
|
|
26472
|
+
/**
|
|
26473
|
+
* Desvincula un proveedor OAuth.
|
|
26474
|
+
*/
|
|
26475
|
+
unlinkProvider(provider) {
|
|
26476
|
+
return this.http
|
|
26477
|
+
.post(`${this.config.apiUrl}/v2/auth/oauth/unlink`, { provider })
|
|
26478
|
+
.pipe(catchError(error => throwError(() => ({
|
|
26479
|
+
code: error.error?.code || 'UNLINK_ERROR',
|
|
26480
|
+
message: error.error?.message || 'Error al desvincular proveedor',
|
|
26481
|
+
}))));
|
|
26482
|
+
}
|
|
26483
|
+
/**
|
|
26484
|
+
* Establece contraseña para usuarios que solo tienen OAuth.
|
|
26485
|
+
*/
|
|
26486
|
+
setPassword(password) {
|
|
26487
|
+
return this.http
|
|
26488
|
+
.post(`${this.config.apiUrl}/v2/auth/oauth/set-password`, { password })
|
|
26489
|
+
.pipe(catchError(error => throwError(() => ({
|
|
26490
|
+
code: error.error?.code || 'SET_PASSWORD_ERROR',
|
|
26491
|
+
message: error.error?.message || 'Error al establecer contraseña',
|
|
26492
|
+
}))));
|
|
26493
|
+
}
|
|
26494
|
+
/**
|
|
26495
|
+
* Verifica si el usuario tiene contraseña establecida.
|
|
26496
|
+
*/
|
|
26497
|
+
hasPassword() {
|
|
26498
|
+
return this.http
|
|
26499
|
+
.get(`${this.config.apiUrl}/v2/auth/oauth/has-password`)
|
|
26500
|
+
.pipe(catchError(error => throwError(() => ({
|
|
26501
|
+
code: error.error?.code || 'CHECK_PASSWORD_ERROR',
|
|
26502
|
+
message: error.error?.message || 'Error al verificar contraseña',
|
|
26503
|
+
}))));
|
|
26504
|
+
}
|
|
26505
|
+
/**
|
|
26506
|
+
* Limpia recursos del popup.
|
|
26507
|
+
*/
|
|
26508
|
+
cleanup() {
|
|
26509
|
+
if (this.messageHandler) {
|
|
26510
|
+
window.removeEventListener('message', this.messageHandler);
|
|
26511
|
+
this.messageHandler = null;
|
|
26512
|
+
}
|
|
26513
|
+
if (this.checkClosedInterval) {
|
|
26514
|
+
clearInterval(this.checkClosedInterval);
|
|
26515
|
+
this.checkClosedInterval = null;
|
|
26516
|
+
}
|
|
26517
|
+
if (this.popup && !this.popup.closed) {
|
|
26518
|
+
this.popup.close();
|
|
26519
|
+
}
|
|
26520
|
+
this.popup = null;
|
|
26521
|
+
}
|
|
26522
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$8.HttpClient }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
26523
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthService, providedIn: 'root' }); }
|
|
26524
|
+
}
|
|
26525
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthService, decorators: [{
|
|
26526
|
+
type: Injectable,
|
|
26527
|
+
args: [{ providedIn: 'root' }]
|
|
26528
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
26529
|
+
type: Inject,
|
|
26530
|
+
args: [VALTECH_AUTH_CONFIG]
|
|
26531
|
+
}] }, { type: i1$8.HttpClient }, { type: i0.NgZone }] });
|
|
26532
|
+
|
|
25258
26533
|
/**
|
|
25259
26534
|
* Servicio principal de autenticación.
|
|
25260
26535
|
*
|
|
@@ -25278,7 +26553,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
25278
26553
|
* ```
|
|
25279
26554
|
*/
|
|
25280
26555
|
class AuthService {
|
|
25281
|
-
constructor(config, http, router, stateService, tokenService, storageService, syncService, firebaseService, messagingService, i18nService) {
|
|
26556
|
+
constructor(config, http, router, stateService, tokenService, storageService, syncService, firebaseService, oauthService, messagingService, i18nService) {
|
|
25282
26557
|
this.config = config;
|
|
25283
26558
|
this.http = http;
|
|
25284
26559
|
this.router = router;
|
|
@@ -25287,6 +26562,7 @@ class AuthService {
|
|
|
25287
26562
|
this.storageService = storageService;
|
|
25288
26563
|
this.syncService = syncService;
|
|
25289
26564
|
this.firebaseService = firebaseService;
|
|
26565
|
+
this.oauthService = oauthService;
|
|
25290
26566
|
this.messagingService = messagingService;
|
|
25291
26567
|
this.i18nService = i18nService;
|
|
25292
26568
|
// Timer para refresh proactivo
|
|
@@ -25400,6 +26676,101 @@ class AuthService {
|
|
|
25400
26676
|
}
|
|
25401
26677
|
}), catchError(error => this.handleAuthError(error)));
|
|
25402
26678
|
}
|
|
26679
|
+
// =============================================
|
|
26680
|
+
// OAUTH (Login social)
|
|
26681
|
+
// =============================================
|
|
26682
|
+
/**
|
|
26683
|
+
* Inicia sesión con OAuth (Google, Apple, Microsoft).
|
|
26684
|
+
* Abre un popup para el flujo de autenticación.
|
|
26685
|
+
*
|
|
26686
|
+
* @param provider - Proveedor OAuth ('google', 'apple', 'microsoft')
|
|
26687
|
+
* @returns Observable que emite SigninResponse cuando se completa
|
|
26688
|
+
*
|
|
26689
|
+
* @example
|
|
26690
|
+
* ```typescript
|
|
26691
|
+
* this.auth.signinWithOAuth('google').subscribe({
|
|
26692
|
+
* next: () => this.router.navigate(['/']),
|
|
26693
|
+
* error: (err) => console.error('OAuth failed:', err)
|
|
26694
|
+
* });
|
|
26695
|
+
* ```
|
|
26696
|
+
*/
|
|
26697
|
+
signinWithOAuth(provider) {
|
|
26698
|
+
this.stateService.clearError();
|
|
26699
|
+
return this.oauthService.startFlow(provider).pipe(tap(result => {
|
|
26700
|
+
// Convertir OAuthResult a SigninResponse compatible
|
|
26701
|
+
const response = {
|
|
26702
|
+
operationId: 'oauth',
|
|
26703
|
+
accessToken: result.accessToken,
|
|
26704
|
+
refreshToken: result.refreshToken,
|
|
26705
|
+
firebaseToken: result.firebaseToken,
|
|
26706
|
+
expiresIn: result.expiresIn,
|
|
26707
|
+
roles: result.roles,
|
|
26708
|
+
permissions: result.permissions,
|
|
26709
|
+
};
|
|
26710
|
+
this.handleSuccessfulAuth(response);
|
|
26711
|
+
}), map$1(result => ({
|
|
26712
|
+
operationId: 'oauth',
|
|
26713
|
+
accessToken: result.accessToken,
|
|
26714
|
+
refreshToken: result.refreshToken,
|
|
26715
|
+
firebaseToken: result.firebaseToken,
|
|
26716
|
+
expiresIn: result.expiresIn,
|
|
26717
|
+
roles: result.roles,
|
|
26718
|
+
permissions: result.permissions,
|
|
26719
|
+
})), catchError(error => {
|
|
26720
|
+
const authError = {
|
|
26721
|
+
code: error.code || 'OAUTH_ERROR',
|
|
26722
|
+
message: error.message || 'Error de autenticación OAuth',
|
|
26723
|
+
};
|
|
26724
|
+
this.stateService.setError(authError);
|
|
26725
|
+
return throwError(() => authError);
|
|
26726
|
+
}));
|
|
26727
|
+
}
|
|
26728
|
+
/**
|
|
26729
|
+
* Vincula un proveedor OAuth adicional a la cuenta actual.
|
|
26730
|
+
* Requiere que el usuario esté autenticado.
|
|
26731
|
+
*
|
|
26732
|
+
* @param provider - Proveedor OAuth a vincular
|
|
26733
|
+
*/
|
|
26734
|
+
linkOAuthProvider(provider) {
|
|
26735
|
+
return this.oauthService.startLinkFlow(provider).pipe(map$1(() => ({ success: true })), catchError(error => {
|
|
26736
|
+
const authError = {
|
|
26737
|
+
code: error.code || 'LINK_ERROR',
|
|
26738
|
+
message: error.message || 'Error al vincular proveedor',
|
|
26739
|
+
};
|
|
26740
|
+
this.stateService.setError(authError);
|
|
26741
|
+
return throwError(() => authError);
|
|
26742
|
+
}));
|
|
26743
|
+
}
|
|
26744
|
+
/**
|
|
26745
|
+
* Obtiene los proveedores OAuth vinculados al usuario.
|
|
26746
|
+
*/
|
|
26747
|
+
getLinkedProviders() {
|
|
26748
|
+
return this.oauthService.getLinkedProviders();
|
|
26749
|
+
}
|
|
26750
|
+
/**
|
|
26751
|
+
* Desvincula un proveedor OAuth de la cuenta.
|
|
26752
|
+
*
|
|
26753
|
+
* @param provider - Proveedor a desvincular
|
|
26754
|
+
*/
|
|
26755
|
+
unlinkOAuthProvider(provider) {
|
|
26756
|
+
return this.oauthService.unlinkProvider(provider);
|
|
26757
|
+
}
|
|
26758
|
+
/**
|
|
26759
|
+
* Establece contraseña para usuarios que solo tienen OAuth.
|
|
26760
|
+
* Permite que usuarios OAuth-only puedan también usar email/password.
|
|
26761
|
+
*
|
|
26762
|
+
* @param password - Nueva contraseña
|
|
26763
|
+
*/
|
|
26764
|
+
setPasswordForOAuthUser(password) {
|
|
26765
|
+
return this.oauthService.setPassword(password);
|
|
26766
|
+
}
|
|
26767
|
+
/**
|
|
26768
|
+
* Verifica si el usuario tiene contraseña establecida.
|
|
26769
|
+
* Útil para mostrar/ocultar opciones de cambio de contraseña.
|
|
26770
|
+
*/
|
|
26771
|
+
checkHasPassword() {
|
|
26772
|
+
return this.oauthService.hasPassword();
|
|
26773
|
+
}
|
|
25403
26774
|
/**
|
|
25404
26775
|
* Registra un nuevo usuario.
|
|
25405
26776
|
* El usuario queda en estado PENDING hasta verificar su email.
|
|
@@ -26093,7 +27464,7 @@ class AuthService {
|
|
|
26093
27464
|
}
|
|
26094
27465
|
return { platform: 'web', browser, os };
|
|
26095
27466
|
}
|
|
26096
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$8.HttpClient }, { token: i1$1.Router }, { token: AuthStateService }, { token: TokenService }, { token: AuthStorageService }, { token: AuthSyncService }, { token: FirebaseService }, { token: MessagingService, optional: true }, { token: I18nService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
27467
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$8.HttpClient }, { token: i1$1.Router }, { token: AuthStateService }, { token: TokenService }, { token: AuthStorageService }, { token: AuthSyncService }, { token: FirebaseService }, { token: OAuthService }, { token: MessagingService, optional: true }, { token: I18nService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
26097
27468
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, providedIn: 'root' }); }
|
|
26098
27469
|
}
|
|
26099
27470
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, decorators: [{
|
|
@@ -26102,7 +27473,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
26102
27473
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
26103
27474
|
type: Inject,
|
|
26104
27475
|
args: [VALTECH_AUTH_CONFIG]
|
|
26105
|
-
}] }, { type: i1$8.HttpClient }, { type: i1$1.Router }, { type: AuthStateService }, { type: TokenService }, { type: AuthStorageService }, { type: AuthSyncService }, { type: FirebaseService }, { type: MessagingService, decorators: [{
|
|
27476
|
+
}] }, { type: i1$8.HttpClient }, { type: i1$1.Router }, { type: AuthStateService }, { type: TokenService }, { type: AuthStorageService }, { type: AuthSyncService }, { type: FirebaseService }, { type: OAuthService }, { type: MessagingService, decorators: [{
|
|
26106
27477
|
type: Optional
|
|
26107
27478
|
}] }, { type: I18nService, decorators: [{
|
|
26108
27479
|
type: Optional
|
|
@@ -26301,6 +27672,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
26301
27672
|
type: Optional
|
|
26302
27673
|
}] }] });
|
|
26303
27674
|
|
|
27675
|
+
/**
|
|
27676
|
+
* Analytics Types
|
|
27677
|
+
*
|
|
27678
|
+
* Tipos e interfaces para el servicio de Firebase Analytics (GA4).
|
|
27679
|
+
*/
|
|
27680
|
+
|
|
26304
27681
|
/**
|
|
26305
27682
|
* Firebase Services
|
|
26306
27683
|
*
|
|
@@ -26725,6 +28102,144 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
26725
28102
|
args: [VALTECH_AUTH_CONFIG]
|
|
26726
28103
|
}] }, { type: i1$8.HttpClient }] });
|
|
26727
28104
|
|
|
28105
|
+
/**
|
|
28106
|
+
* Componente de callback para OAuth.
|
|
28107
|
+
*
|
|
28108
|
+
* Este componente procesa la respuesta del servidor OAuth y envía
|
|
28109
|
+
* los tokens a la ventana padre via postMessage.
|
|
28110
|
+
*
|
|
28111
|
+
* Debe agregarse a las rutas de la aplicación:
|
|
28112
|
+
* ```typescript
|
|
28113
|
+
* // app.routes.ts
|
|
28114
|
+
* import { OAuthCallbackComponent } from 'valtech-components';
|
|
28115
|
+
*
|
|
28116
|
+
* export const routes: Routes = [
|
|
28117
|
+
* { path: 'auth/oauth/callback', component: OAuthCallbackComponent },
|
|
28118
|
+
* // ... otras rutas
|
|
28119
|
+
* ];
|
|
28120
|
+
* ```
|
|
28121
|
+
*
|
|
28122
|
+
* El backend redirige a esta ruta con los tokens en query params:
|
|
28123
|
+
* `/auth/oauth/callback?access_token=xxx&refresh_token=xxx&expires_in=900`
|
|
28124
|
+
*
|
|
28125
|
+
* O con error:
|
|
28126
|
+
* `/auth/oauth/callback?error=INVALID_CODE&error_description=...`
|
|
28127
|
+
*/
|
|
28128
|
+
class OAuthCallbackComponent {
|
|
28129
|
+
constructor() {
|
|
28130
|
+
this.message = 'Procesando autenticación...';
|
|
28131
|
+
}
|
|
28132
|
+
ngOnInit() {
|
|
28133
|
+
this.processCallback();
|
|
28134
|
+
}
|
|
28135
|
+
processCallback() {
|
|
28136
|
+
const params = new URLSearchParams(window.location.search);
|
|
28137
|
+
// Verificar si hay error
|
|
28138
|
+
const error = params.get('error');
|
|
28139
|
+
if (error) {
|
|
28140
|
+
this.sendToParent({
|
|
28141
|
+
type: 'oauth-callback',
|
|
28142
|
+
error: {
|
|
28143
|
+
code: error,
|
|
28144
|
+
message: params.get('error_description') || 'Error de autenticación',
|
|
28145
|
+
},
|
|
28146
|
+
});
|
|
28147
|
+
this.message = 'Error de autenticación';
|
|
28148
|
+
this.closeAfterDelay();
|
|
28149
|
+
return;
|
|
28150
|
+
}
|
|
28151
|
+
// Extraer tokens
|
|
28152
|
+
const accessToken = params.get('access_token');
|
|
28153
|
+
const refreshToken = params.get('refresh_token');
|
|
28154
|
+
const expiresIn = params.get('expires_in');
|
|
28155
|
+
const firebaseToken = params.get('firebase_token');
|
|
28156
|
+
if (!accessToken || !refreshToken) {
|
|
28157
|
+
this.sendToParent({
|
|
28158
|
+
type: 'oauth-callback',
|
|
28159
|
+
error: {
|
|
28160
|
+
code: 'MISSING_TOKENS',
|
|
28161
|
+
message: 'No se recibieron los tokens de autenticación',
|
|
28162
|
+
},
|
|
28163
|
+
});
|
|
28164
|
+
this.message = 'Error: tokens no recibidos';
|
|
28165
|
+
this.closeAfterDelay();
|
|
28166
|
+
return;
|
|
28167
|
+
}
|
|
28168
|
+
// Extraer roles y permisos (pueden venir como JSON en base64)
|
|
28169
|
+
let roles;
|
|
28170
|
+
let permissions;
|
|
28171
|
+
const rolesParam = params.get('roles');
|
|
28172
|
+
const permissionsParam = params.get('permissions');
|
|
28173
|
+
if (rolesParam) {
|
|
28174
|
+
try {
|
|
28175
|
+
roles = JSON.parse(atob(rolesParam));
|
|
28176
|
+
}
|
|
28177
|
+
catch {
|
|
28178
|
+
roles = rolesParam.split(',');
|
|
28179
|
+
}
|
|
28180
|
+
}
|
|
28181
|
+
if (permissionsParam) {
|
|
28182
|
+
try {
|
|
28183
|
+
permissions = JSON.parse(atob(permissionsParam));
|
|
28184
|
+
}
|
|
28185
|
+
catch {
|
|
28186
|
+
permissions = permissionsParam.split(',');
|
|
28187
|
+
}
|
|
28188
|
+
}
|
|
28189
|
+
// Enviar tokens a la ventana padre
|
|
28190
|
+
const result = {
|
|
28191
|
+
accessToken,
|
|
28192
|
+
refreshToken,
|
|
28193
|
+
expiresIn: expiresIn ? parseInt(expiresIn, 10) : 900,
|
|
28194
|
+
firebaseToken: firebaseToken || undefined,
|
|
28195
|
+
roles,
|
|
28196
|
+
permissions,
|
|
28197
|
+
isNewUser: params.get('is_new_user') === 'true',
|
|
28198
|
+
linked: params.get('linked') === 'true',
|
|
28199
|
+
};
|
|
28200
|
+
this.sendToParent({
|
|
28201
|
+
type: 'oauth-callback',
|
|
28202
|
+
tokens: result,
|
|
28203
|
+
});
|
|
28204
|
+
this.message = 'Autenticación exitosa';
|
|
28205
|
+
this.closeAfterDelay();
|
|
28206
|
+
}
|
|
28207
|
+
sendToParent(data) {
|
|
28208
|
+
if (window.opener) {
|
|
28209
|
+
// Enviar al opener (ventana que abrió el popup)
|
|
28210
|
+
window.opener.postMessage(data, window.location.origin);
|
|
28211
|
+
}
|
|
28212
|
+
else if (window.parent !== window) {
|
|
28213
|
+
// Enviar al parent (si estamos en iframe)
|
|
28214
|
+
window.parent.postMessage(data, window.location.origin);
|
|
28215
|
+
}
|
|
28216
|
+
}
|
|
28217
|
+
closeAfterDelay() {
|
|
28218
|
+
// Dar tiempo para que el mensaje se envíe antes de cerrar
|
|
28219
|
+
setTimeout(() => {
|
|
28220
|
+
if (window.opener) {
|
|
28221
|
+
window.close();
|
|
28222
|
+
}
|
|
28223
|
+
}, 500);
|
|
28224
|
+
}
|
|
28225
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthCallbackComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
28226
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: OAuthCallbackComponent, isStandalone: true, selector: "val-oauth-callback", ngImport: i0, template: `
|
|
28227
|
+
<div class="oauth-callback">
|
|
28228
|
+
<div class="oauth-callback__spinner"></div>
|
|
28229
|
+
<p class="oauth-callback__text">{{ message }}</p>
|
|
28230
|
+
</div>
|
|
28231
|
+
`, isInline: true, styles: [".oauth-callback{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.oauth-callback__spinner{width:40px;height:40px;border:3px solid #f3f3f3;border-top:3px solid #3498db;border-radius:50%;animation:spin 1s linear infinite;margin-bottom:16px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.oauth-callback__text{color:#666;font-size:14px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] }); }
|
|
28232
|
+
}
|
|
28233
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthCallbackComponent, decorators: [{
|
|
28234
|
+
type: Component,
|
|
28235
|
+
args: [{ selector: 'val-oauth-callback', standalone: true, imports: [CommonModule], template: `
|
|
28236
|
+
<div class="oauth-callback">
|
|
28237
|
+
<div class="oauth-callback__spinner"></div>
|
|
28238
|
+
<p class="oauth-callback__text">{{ message }}</p>
|
|
28239
|
+
</div>
|
|
28240
|
+
`, styles: [".oauth-callback{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.oauth-callback__spinner{width:40px;height:40px;border:3px solid #f3f3f3;border-top:3px solid #3498db;border-radius:50%;animation:spin 1s linear infinite;margin-bottom:16px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.oauth-callback__text{color:#666;font-size:14px}\n"] }]
|
|
28241
|
+
}] });
|
|
28242
|
+
|
|
26728
28243
|
/**
|
|
26729
28244
|
* Valtech Auth Service
|
|
26730
28245
|
*
|
|
@@ -26798,152 +28313,1137 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
26798
28313
|
// Tipos
|
|
26799
28314
|
|
|
26800
28315
|
/**
|
|
26801
|
-
*
|
|
28316
|
+
* Ads Types
|
|
26802
28317
|
*
|
|
26803
|
-
*
|
|
26804
|
-
*
|
|
26805
|
-
|
|
28318
|
+
* Tipos e interfaces para el servicio de Google Ad Manager.
|
|
28319
|
+
* Usa Google Publisher Tags (GPT) para renderizar ads.
|
|
28320
|
+
*/
|
|
28321
|
+
/**
|
|
28322
|
+
* Mapeo de tamanos predefinidos a dimensiones.
|
|
28323
|
+
*/
|
|
28324
|
+
const AD_SIZE_MAP = {
|
|
28325
|
+
leaderboard: [728, 90],
|
|
28326
|
+
billboard: [970, 250],
|
|
28327
|
+
'medium-rectangle': [300, 250],
|
|
28328
|
+
'large-rectangle': [336, 280],
|
|
28329
|
+
'half-page': [300, 600],
|
|
28330
|
+
skyscraper: [160, 600],
|
|
28331
|
+
'mobile-banner': [320, 50],
|
|
28332
|
+
'mobile-leaderboard': [320, 100],
|
|
28333
|
+
fluid: 'fluid',
|
|
28334
|
+
native: 'fluid',
|
|
28335
|
+
custom: 'fluid',
|
|
28336
|
+
};
|
|
28337
|
+
|
|
28338
|
+
/**
|
|
28339
|
+
* Ads Consent Service
|
|
26806
28340
|
*
|
|
26807
|
-
*
|
|
26808
|
-
*
|
|
26809
|
-
|
|
28341
|
+
* Integra el servicio de Ads con el Consent Mode v2 existente en AnalyticsService.
|
|
28342
|
+
* Proporciona estado de consent especifico para ads (advertising).
|
|
28343
|
+
*/
|
|
28344
|
+
/**
|
|
28345
|
+
* Servicio que maneja el consent para ads.
|
|
26810
28346
|
*
|
|
26811
|
-
*
|
|
26812
|
-
*
|
|
26813
|
-
* // { size: 'large', color: 'primary', fill: 'solid' }
|
|
28347
|
+
* Se integra con AnalyticsService para reutilizar el estado de consent GDPR.
|
|
28348
|
+
* Si AnalyticsService no esta disponible, usa valores por defecto conservadores.
|
|
26814
28349
|
*/
|
|
26815
|
-
class
|
|
28350
|
+
class AdsConsentService {
|
|
26816
28351
|
constructor() {
|
|
26817
|
-
this.
|
|
28352
|
+
this.platformId = inject(PLATFORM_ID);
|
|
28353
|
+
this.injector = inject(Injector);
|
|
28354
|
+
// Cache del estado de consent
|
|
28355
|
+
this._consentState = signal({
|
|
28356
|
+
adStorage: false,
|
|
28357
|
+
adPersonalization: false,
|
|
28358
|
+
adUserData: false,
|
|
28359
|
+
});
|
|
28360
|
+
/** Estado de consent para ads (readonly) */
|
|
28361
|
+
this.adsConsentState = this._consentState.asReadonly();
|
|
28362
|
+
/** Indica si se pueden mostrar ads (al menos consent basico) */
|
|
28363
|
+
this.canShowAds = computed(() => {
|
|
28364
|
+
// Siempre permitimos mostrar ads (pueden ser no personalizados)
|
|
28365
|
+
// El control real esta en canPersonalize
|
|
28366
|
+
return true;
|
|
28367
|
+
});
|
|
28368
|
+
/** Indica si se pueden mostrar ads personalizados */
|
|
28369
|
+
this.canPersonalize = computed(() => {
|
|
28370
|
+
const state = this._consentState();
|
|
28371
|
+
return state.adStorage && state.adPersonalization && state.adUserData;
|
|
28372
|
+
});
|
|
28373
|
+
this.analyticsService = null;
|
|
28374
|
+
this.consentSyncInterval = null;
|
|
28375
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
28376
|
+
this.initializeConsentSync();
|
|
28377
|
+
}
|
|
26818
28378
|
}
|
|
26819
28379
|
/**
|
|
26820
|
-
*
|
|
28380
|
+
* Inicializa la sincronizacion con AnalyticsService.
|
|
28381
|
+
*/
|
|
28382
|
+
initializeConsentSync() {
|
|
28383
|
+
// Intentar obtener AnalyticsService de forma lazy
|
|
28384
|
+
// Usamos setTimeout para dar tiempo a que se inicialice
|
|
28385
|
+
setTimeout(() => {
|
|
28386
|
+
this.syncWithAnalyticsConsent();
|
|
28387
|
+
}, 100);
|
|
28388
|
+
// Re-sincronizar periodicamente por si el usuario cambia consent
|
|
28389
|
+
this.consentSyncInterval = setInterval(() => {
|
|
28390
|
+
this.syncWithAnalyticsConsent();
|
|
28391
|
+
}, 5000);
|
|
28392
|
+
}
|
|
28393
|
+
/**
|
|
28394
|
+
* Sincroniza el estado de consent desde AnalyticsService.
|
|
28395
|
+
*/
|
|
28396
|
+
async syncWithAnalyticsConsent() {
|
|
28397
|
+
try {
|
|
28398
|
+
// Importar AnalyticsService dinamicamente
|
|
28399
|
+
const { AnalyticsService } = await Promise.resolve().then(function () { return analytics_service; });
|
|
28400
|
+
if (!this.analyticsService) {
|
|
28401
|
+
this.analyticsService = this.injector.get(AnalyticsService, null);
|
|
28402
|
+
}
|
|
28403
|
+
if (this.analyticsService) {
|
|
28404
|
+
// AnalyticsService expone consentState como signal
|
|
28405
|
+
const consentState = this.analyticsService.consentState();
|
|
28406
|
+
this.updateFromAnalyticsConsent(consentState.settings);
|
|
28407
|
+
}
|
|
28408
|
+
}
|
|
28409
|
+
catch {
|
|
28410
|
+
// AnalyticsService no disponible - usar defaults
|
|
28411
|
+
this.setDefaultConsent();
|
|
28412
|
+
}
|
|
28413
|
+
}
|
|
28414
|
+
/**
|
|
28415
|
+
* Actualiza el estado de consent desde AnalyticsService.
|
|
28416
|
+
*/
|
|
28417
|
+
updateFromAnalyticsConsent(settings) {
|
|
28418
|
+
const hasAdvertisingConsent = settings['advertising'] === true;
|
|
28419
|
+
this._consentState.set({
|
|
28420
|
+
adStorage: hasAdvertisingConsent,
|
|
28421
|
+
adPersonalization: hasAdvertisingConsent,
|
|
28422
|
+
adUserData: hasAdvertisingConsent,
|
|
28423
|
+
});
|
|
28424
|
+
}
|
|
28425
|
+
/**
|
|
28426
|
+
* Establece consent por defecto (sin datos personales).
|
|
28427
|
+
*/
|
|
28428
|
+
setDefaultConsent() {
|
|
28429
|
+
this._consentState.set({
|
|
28430
|
+
adStorage: false,
|
|
28431
|
+
adPersonalization: false,
|
|
28432
|
+
adUserData: false,
|
|
28433
|
+
});
|
|
28434
|
+
}
|
|
28435
|
+
/**
|
|
28436
|
+
* Actualiza manualmente el consent para ads.
|
|
28437
|
+
* Usar cuando el usuario actualiza sus preferencias directamente.
|
|
26821
28438
|
*
|
|
26822
|
-
* @param
|
|
26823
|
-
|
|
26824
|
-
|
|
28439
|
+
* @param consent - Nuevo estado de consent parcial
|
|
28440
|
+
*/
|
|
28441
|
+
updateConsent(consent) {
|
|
28442
|
+
this._consentState.update((current) => ({
|
|
28443
|
+
...current,
|
|
28444
|
+
...consent,
|
|
28445
|
+
}));
|
|
28446
|
+
// Notificar a GPT si ya esta cargado
|
|
28447
|
+
this.applyConsentToGPT();
|
|
28448
|
+
}
|
|
28449
|
+
/**
|
|
28450
|
+
* Aplica el estado de consent actual a GPT.
|
|
28451
|
+
*/
|
|
28452
|
+
applyConsentToGPT() {
|
|
28453
|
+
if (!isPlatformBrowser(this.platformId))
|
|
28454
|
+
return;
|
|
28455
|
+
const googletag = window.googletag;
|
|
28456
|
+
if (!googletag)
|
|
28457
|
+
return;
|
|
28458
|
+
googletag.cmd.push(() => {
|
|
28459
|
+
const canPersonalize = this.canPersonalize();
|
|
28460
|
+
// 0 = personalized, 1 = non-personalized
|
|
28461
|
+
googletag.pubads().setRequestNonPersonalizedAds(canPersonalize ? 0 : 1);
|
|
28462
|
+
});
|
|
28463
|
+
}
|
|
28464
|
+
/**
|
|
28465
|
+
* Obtiene el estado actual de consent.
|
|
28466
|
+
*/
|
|
28467
|
+
getConsentState() {
|
|
28468
|
+
return this._consentState();
|
|
28469
|
+
}
|
|
28470
|
+
/**
|
|
28471
|
+
* Destruye el servicio y limpia recursos.
|
|
28472
|
+
*/
|
|
28473
|
+
destroy() {
|
|
28474
|
+
if (this.consentSyncInterval) {
|
|
28475
|
+
clearInterval(this.consentSyncInterval);
|
|
28476
|
+
this.consentSyncInterval = null;
|
|
28477
|
+
}
|
|
28478
|
+
}
|
|
28479
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsConsentService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
28480
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsConsentService, providedIn: 'root' }); }
|
|
28481
|
+
}
|
|
28482
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsConsentService, decorators: [{
|
|
28483
|
+
type: Injectable,
|
|
28484
|
+
args: [{ providedIn: 'root' }]
|
|
28485
|
+
}], ctorParameters: () => [] });
|
|
28486
|
+
|
|
28487
|
+
/**
|
|
28488
|
+
* Ads Loader Service
|
|
28489
|
+
*
|
|
28490
|
+
* Maneja la carga lazy del script GPT (Google Publisher Tag).
|
|
28491
|
+
* Solo carga el script cuando se necesita el primer ad.
|
|
28492
|
+
*/
|
|
28493
|
+
/** URL del script GPT */
|
|
28494
|
+
const GPT_SCRIPT_URL = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js';
|
|
28495
|
+
/**
|
|
28496
|
+
* Servicio para cargar el script de Google Publisher Tags.
|
|
28497
|
+
*
|
|
28498
|
+
* Implementa lazy loading: el script solo se carga cuando
|
|
28499
|
+
* se solicita renderizar el primer ad slot.
|
|
28500
|
+
*/
|
|
28501
|
+
class AdsLoaderService {
|
|
28502
|
+
constructor(config, platformId, consentService) {
|
|
28503
|
+
this.config = config;
|
|
28504
|
+
this.platformId = platformId;
|
|
28505
|
+
this.consentService = consentService;
|
|
28506
|
+
this._isLoading = signal(false);
|
|
28507
|
+
this._isLoaded = signal(false);
|
|
28508
|
+
this._error = signal(null);
|
|
28509
|
+
/** Indica si el script esta cargando */
|
|
28510
|
+
this.isLoading = this._isLoading.asReadonly();
|
|
28511
|
+
/** Indica si el script esta cargado */
|
|
28512
|
+
this.isLoaded = this._isLoaded.asReadonly();
|
|
28513
|
+
/** Error de carga (si existe) */
|
|
28514
|
+
this.error = this._error.asReadonly();
|
|
28515
|
+
this.loadPromise = null;
|
|
28516
|
+
}
|
|
28517
|
+
/**
|
|
28518
|
+
* Carga el script GPT de forma lazy.
|
|
28519
|
+
* Retorna la instancia de googletag o null si falla.
|
|
26825
28520
|
*
|
|
26826
|
-
* @
|
|
26827
|
-
|
|
26828
|
-
|
|
28521
|
+
* @returns Promise con googletag o null
|
|
28522
|
+
*/
|
|
28523
|
+
loadGPT() {
|
|
28524
|
+
// SSR check
|
|
28525
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
28526
|
+
return Promise.resolve(null);
|
|
28527
|
+
}
|
|
28528
|
+
// Ya cargado
|
|
28529
|
+
if (this._isLoaded() && window.googletag) {
|
|
28530
|
+
return Promise.resolve(window.googletag);
|
|
28531
|
+
}
|
|
28532
|
+
// Ya hay una carga en progreso
|
|
28533
|
+
if (this.loadPromise) {
|
|
28534
|
+
return this.loadPromise;
|
|
28535
|
+
}
|
|
28536
|
+
this._isLoading.set(true);
|
|
28537
|
+
this._error.set(null);
|
|
28538
|
+
this.loadPromise = new Promise((resolve) => {
|
|
28539
|
+
// Inicializar cmd queue
|
|
28540
|
+
window.googletag = window.googletag || { cmd: [] };
|
|
28541
|
+
const googletag = window.googletag;
|
|
28542
|
+
// Crear script
|
|
28543
|
+
const script = document.createElement('script');
|
|
28544
|
+
script.async = true;
|
|
28545
|
+
script.src = GPT_SCRIPT_URL;
|
|
28546
|
+
script.onload = () => {
|
|
28547
|
+
googletag.cmd.push(() => {
|
|
28548
|
+
this.configureGPT(googletag);
|
|
28549
|
+
this._isLoaded.set(true);
|
|
28550
|
+
this._isLoading.set(false);
|
|
28551
|
+
if (this.config.debugMode) {
|
|
28552
|
+
console.log('[ValtechAds] Script GPT cargado y configurado');
|
|
28553
|
+
}
|
|
28554
|
+
resolve(googletag);
|
|
28555
|
+
});
|
|
28556
|
+
};
|
|
28557
|
+
script.onerror = (error) => {
|
|
28558
|
+
console.error('[ValtechAds] Error cargando GPT:', error);
|
|
28559
|
+
this._error.set(new Error('Error cargando Google Publisher Tag'));
|
|
28560
|
+
this._isLoading.set(false);
|
|
28561
|
+
this.loadPromise = null;
|
|
28562
|
+
resolve(null);
|
|
28563
|
+
};
|
|
28564
|
+
// Insertar script
|
|
28565
|
+
const firstScript = document.getElementsByTagName('script')[0];
|
|
28566
|
+
firstScript.parentNode?.insertBefore(script, firstScript);
|
|
28567
|
+
});
|
|
28568
|
+
return this.loadPromise;
|
|
28569
|
+
}
|
|
28570
|
+
/**
|
|
28571
|
+
* Configura GPT con las opciones de la aplicacion.
|
|
28572
|
+
*/
|
|
28573
|
+
configureGPT(googletag) {
|
|
28574
|
+
const pubads = googletag.pubads();
|
|
28575
|
+
// Single Request Architecture para mejor performance
|
|
28576
|
+
pubads.enableSingleRequest();
|
|
28577
|
+
// Colapsar divs vacios antes de fetch
|
|
28578
|
+
pubads.collapseEmptyDivs(true);
|
|
28579
|
+
// Lazy loading nativo de GPT
|
|
28580
|
+
if (this.config.lazyLoad && this.config.lazyLoadConfig) {
|
|
28581
|
+
pubads.enableLazyLoad({
|
|
28582
|
+
fetchMarginPercent: this.config.lazyLoadConfig.fetchMarginPercent,
|
|
28583
|
+
renderMarginPercent: this.config.lazyLoadConfig.renderMarginPercent,
|
|
28584
|
+
mobileScaling: 2.0,
|
|
28585
|
+
});
|
|
28586
|
+
}
|
|
28587
|
+
// Targeting global
|
|
28588
|
+
if (this.config.globalTargeting) {
|
|
28589
|
+
for (const [key, value] of Object.entries(this.config.globalTargeting)) {
|
|
28590
|
+
pubads.setTargeting(key, value);
|
|
28591
|
+
}
|
|
28592
|
+
}
|
|
28593
|
+
// Non-personalized ads segun consent
|
|
28594
|
+
if (!this.consentService.canPersonalize()) {
|
|
28595
|
+
pubads.setRequestNonPersonalizedAds(1);
|
|
28596
|
+
}
|
|
28597
|
+
// Event listeners
|
|
28598
|
+
pubads.addEventListener('slotRenderEnded', (event) => {
|
|
28599
|
+
const slotId = event.slot.getSlotElementId();
|
|
28600
|
+
if (this.config.debugMode) {
|
|
28601
|
+
console.log(`[ValtechAds] Slot ${slotId} rendered, empty: ${event.isEmpty}`);
|
|
28602
|
+
}
|
|
28603
|
+
});
|
|
28604
|
+
// Debug mode
|
|
28605
|
+
if (this.config.debugMode) {
|
|
28606
|
+
console.log('[ValtechAds] GPT configurado:', {
|
|
28607
|
+
lazyLoad: this.config.lazyLoad,
|
|
28608
|
+
personalized: this.consentService.canPersonalize(),
|
|
28609
|
+
targeting: this.config.globalTargeting,
|
|
28610
|
+
});
|
|
28611
|
+
}
|
|
28612
|
+
}
|
|
28613
|
+
/**
|
|
28614
|
+
* Verifica si el script GPT esta disponible.
|
|
28615
|
+
*/
|
|
28616
|
+
isGPTAvailable() {
|
|
28617
|
+
return isPlatformBrowser(this.platformId) && !!window.googletag && this._isLoaded();
|
|
28618
|
+
}
|
|
28619
|
+
/**
|
|
28620
|
+
* Obtiene la instancia de googletag si esta cargada.
|
|
28621
|
+
*/
|
|
28622
|
+
getGPT() {
|
|
28623
|
+
if (this.isGPTAvailable()) {
|
|
28624
|
+
return window.googletag;
|
|
28625
|
+
}
|
|
28626
|
+
return null;
|
|
28627
|
+
}
|
|
28628
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsLoaderService, deps: [{ token: VALTECH_ADS_CONFIG }, { token: PLATFORM_ID }, { token: AdsConsentService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
28629
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsLoaderService, providedIn: 'root' }); }
|
|
28630
|
+
}
|
|
28631
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsLoaderService, decorators: [{
|
|
28632
|
+
type: Injectable,
|
|
28633
|
+
args: [{ providedIn: 'root' }]
|
|
28634
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
28635
|
+
type: Inject,
|
|
28636
|
+
args: [VALTECH_ADS_CONFIG]
|
|
28637
|
+
}] }, { type: Object, decorators: [{
|
|
28638
|
+
type: Inject,
|
|
28639
|
+
args: [PLATFORM_ID]
|
|
28640
|
+
}] }, { type: AdsConsentService }] });
|
|
28641
|
+
|
|
28642
|
+
/**
|
|
28643
|
+
* Ads Service
|
|
28644
|
+
*
|
|
28645
|
+
* Servicio principal para Google Ad Manager (GPT).
|
|
28646
|
+
* Integra con el sistema de consent existente y respeta usuarios premium.
|
|
28647
|
+
*/
|
|
28648
|
+
/**
|
|
28649
|
+
* Servicio principal de Google Ad Manager.
|
|
28650
|
+
*
|
|
28651
|
+
* Maneja la creacion, destruccion y refresh de ad slots.
|
|
28652
|
+
* Integra automaticamente con consent mode y detecta usuarios premium.
|
|
28653
|
+
*
|
|
28654
|
+
* @example
|
|
28655
|
+
* ```typescript
|
|
28656
|
+
* @Component({...})
|
|
28657
|
+
* export class MyComponent {
|
|
28658
|
+
* private ads = inject(AdsService);
|
|
28659
|
+
*
|
|
28660
|
+
* // Verificar si mostrar ads
|
|
28661
|
+
* showAds = this.ads.isEnabled;
|
|
28662
|
+
*
|
|
28663
|
+
* // Verificar si usuario es premium
|
|
28664
|
+
* isPremium = this.ads.isPremiumUser;
|
|
28665
|
+
* }
|
|
28666
|
+
* ```
|
|
28667
|
+
*/
|
|
28668
|
+
class AdsService {
|
|
28669
|
+
constructor(injector, config, platformId, router, loaderService, consentService) {
|
|
28670
|
+
this.injector = injector;
|
|
28671
|
+
this.config = config;
|
|
28672
|
+
this.platformId = platformId;
|
|
28673
|
+
this.router = router;
|
|
28674
|
+
this.loaderService = loaderService;
|
|
28675
|
+
this.consentService = consentService;
|
|
28676
|
+
// ===========================================================================
|
|
28677
|
+
// ESTADO (Signals)
|
|
28678
|
+
// ===========================================================================
|
|
28679
|
+
this._isInitialized = signal(false);
|
|
28680
|
+
this._isEnabled = signal(false);
|
|
28681
|
+
this._isPremiumUser = signal(false);
|
|
28682
|
+
this._isDebugMode = signal(false);
|
|
28683
|
+
this._slots = signal(new Map());
|
|
28684
|
+
this._slotStates = signal(new Map());
|
|
28685
|
+
this._events = signal([]);
|
|
28686
|
+
/** Indica si el servicio esta inicializado */
|
|
28687
|
+
this.isInitialized = this._isInitialized.asReadonly();
|
|
28688
|
+
/** Indica si los ads estan habilitados (consent + no premium) */
|
|
28689
|
+
this.isEnabled = computed(() => {
|
|
28690
|
+
return (this._isInitialized() &&
|
|
28691
|
+
this._isEnabled() &&
|
|
28692
|
+
!this._isPremiumUser() &&
|
|
28693
|
+
this.consentService.canShowAds());
|
|
28694
|
+
});
|
|
28695
|
+
/** Indica si el usuario es premium (no ve ads) */
|
|
28696
|
+
this.isPremiumUser = this._isPremiumUser.asReadonly();
|
|
28697
|
+
/** Indica si esta en modo debug */
|
|
28698
|
+
this.isDebugMode = this._isDebugMode.asReadonly();
|
|
28699
|
+
/** Estado de consent para ads */
|
|
28700
|
+
this.consentState = this.consentService.adsConsentState;
|
|
28701
|
+
/** Eventos de ads (historial) */
|
|
28702
|
+
this.events = this._events.asReadonly();
|
|
28703
|
+
// ===========================================================================
|
|
28704
|
+
// INTERNOS
|
|
28705
|
+
// ===========================================================================
|
|
28706
|
+
this.activeSlots = new Map();
|
|
28707
|
+
this.routerSubscription = null;
|
|
28708
|
+
this.refreshTimerId = null;
|
|
28709
|
+
this._isDebugMode.set(config.debugMode ?? false);
|
|
28710
|
+
}
|
|
28711
|
+
// ===========================================================================
|
|
28712
|
+
// INICIALIZACION
|
|
28713
|
+
// ===========================================================================
|
|
28714
|
+
/**
|
|
28715
|
+
* Inicializa el servicio de ads.
|
|
28716
|
+
* Llamado automaticamente por APP_INITIALIZER.
|
|
28717
|
+
* NO carga el script GPT hasta que se necesite el primer ad.
|
|
28718
|
+
*/
|
|
28719
|
+
async initialize() {
|
|
28720
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
28721
|
+
return;
|
|
28722
|
+
}
|
|
28723
|
+
// Verificar si usuario es premium
|
|
28724
|
+
if (this.config.isPremiumUser) {
|
|
28725
|
+
try {
|
|
28726
|
+
this._isPremiumUser.set(this.config.isPremiumUser());
|
|
28727
|
+
}
|
|
28728
|
+
catch {
|
|
28729
|
+
this._isPremiumUser.set(false);
|
|
28730
|
+
}
|
|
28731
|
+
}
|
|
28732
|
+
// Si es premium, no inicializar ads
|
|
28733
|
+
if (this._isPremiumUser()) {
|
|
28734
|
+
if (this._isDebugMode()) {
|
|
28735
|
+
console.log('[ValtechAds] Usuario premium detectado - ads deshabilitados');
|
|
28736
|
+
}
|
|
28737
|
+
this._isInitialized.set(true);
|
|
28738
|
+
return;
|
|
28739
|
+
}
|
|
28740
|
+
// Verificar consent basico
|
|
28741
|
+
if (!this.consentService.canShowAds() && !this.config.showNonPersonalizedAds) {
|
|
28742
|
+
if (this._isDebugMode()) {
|
|
28743
|
+
console.log('[ValtechAds] Sin consent para ads');
|
|
28744
|
+
}
|
|
28745
|
+
this._isInitialized.set(true);
|
|
28746
|
+
return;
|
|
28747
|
+
}
|
|
28748
|
+
// Suscribirse a cambios de ruta para verificar exclusiones
|
|
28749
|
+
this.setupRouteListener();
|
|
28750
|
+
// Configurar auto-refresh si esta habilitado
|
|
28751
|
+
if (this.config.autoRefreshInterval && this.config.autoRefreshInterval > 0) {
|
|
28752
|
+
this.setupAutoRefresh();
|
|
28753
|
+
}
|
|
28754
|
+
this._isEnabled.set(true);
|
|
28755
|
+
this._isInitialized.set(true);
|
|
28756
|
+
if (this._isDebugMode()) {
|
|
28757
|
+
console.log('[ValtechAds] Inicializado en modo debug', {
|
|
28758
|
+
networkId: this.config.networkId,
|
|
28759
|
+
lazyLoad: this.config.lazyLoad,
|
|
28760
|
+
excludeRoutes: this.config.excludeRoutes,
|
|
28761
|
+
});
|
|
28762
|
+
}
|
|
28763
|
+
}
|
|
28764
|
+
/**
|
|
28765
|
+
* Carga el script GPT de forma lazy.
|
|
28766
|
+
* Llamado automaticamente cuando se renderiza el primer ad slot.
|
|
28767
|
+
*/
|
|
28768
|
+
async ensureGPTLoaded() {
|
|
28769
|
+
if (!this.isEnabled()) {
|
|
28770
|
+
return null;
|
|
28771
|
+
}
|
|
28772
|
+
return this.loaderService.loadGPT();
|
|
28773
|
+
}
|
|
28774
|
+
// ===========================================================================
|
|
28775
|
+
// GESTION DE SLOTS
|
|
28776
|
+
// ===========================================================================
|
|
28777
|
+
/**
|
|
28778
|
+
* Define y muestra un ad slot.
|
|
28779
|
+
* Llamado internamente por el componente AdSlot.
|
|
26829
28780
|
*
|
|
26830
|
-
*
|
|
26831
|
-
*
|
|
28781
|
+
* @param config - Configuracion del slot
|
|
28782
|
+
* @returns ID del slot o null si no se puede mostrar
|
|
26832
28783
|
*/
|
|
26833
|
-
|
|
26834
|
-
|
|
26835
|
-
|
|
26836
|
-
|
|
26837
|
-
return {};
|
|
28784
|
+
async defineSlot(config) {
|
|
28785
|
+
if (!this.isEnabled()) {
|
|
28786
|
+
this.updateSlotState(config.slotId, 'hidden');
|
|
28787
|
+
return null;
|
|
26838
28788
|
}
|
|
26839
|
-
|
|
26840
|
-
if (
|
|
26841
|
-
|
|
26842
|
-
return
|
|
28789
|
+
// Verificar si la ruta actual esta excluida
|
|
28790
|
+
if (this.isRouteExcluded()) {
|
|
28791
|
+
this.updateSlotState(config.slotId, 'hidden');
|
|
28792
|
+
return null;
|
|
26843
28793
|
}
|
|
26844
|
-
|
|
28794
|
+
this.updateSlotState(config.slotId, 'loading');
|
|
28795
|
+
const googletag = await this.ensureGPTLoaded();
|
|
28796
|
+
if (!googletag) {
|
|
28797
|
+
this.updateSlotState(config.slotId, 'error');
|
|
28798
|
+
return null;
|
|
28799
|
+
}
|
|
28800
|
+
return new Promise((resolve) => {
|
|
28801
|
+
googletag.cmd.push(() => {
|
|
28802
|
+
try {
|
|
28803
|
+
const adUnitPath = `${this.config.networkId}${config.adUnitPath}`;
|
|
28804
|
+
const sizes = this.resolveSizes(config.size);
|
|
28805
|
+
const slot = googletag.defineSlot(adUnitPath, sizes, config.slotId);
|
|
28806
|
+
if (!slot) {
|
|
28807
|
+
this.updateSlotState(config.slotId, 'error');
|
|
28808
|
+
this.emitEvent({ type: 'error', slotId: config.slotId, error: new Error('No se pudo crear el slot') });
|
|
28809
|
+
resolve(null);
|
|
28810
|
+
return;
|
|
28811
|
+
}
|
|
28812
|
+
// Agregar al servicio pubads
|
|
28813
|
+
slot.addService(googletag.pubads());
|
|
28814
|
+
// Configurar size mapping responsivo
|
|
28815
|
+
if (config.sizeMapping && config.sizeMapping.length > 0) {
|
|
28816
|
+
const mapping = this.buildSizeMapping(googletag, config.sizeMapping);
|
|
28817
|
+
slot.defineSizeMapping(mapping);
|
|
28818
|
+
}
|
|
28819
|
+
// Configurar targeting
|
|
28820
|
+
this.applyTargeting(slot, config.targeting);
|
|
28821
|
+
// Colapsar si esta vacio
|
|
28822
|
+
if (config.collapseEmpty ?? this.config.defaultSlotConfig?.collapseEmpty) {
|
|
28823
|
+
slot.setCollapseEmptyDiv(true, true);
|
|
28824
|
+
}
|
|
28825
|
+
// Guardar referencia
|
|
28826
|
+
this.activeSlots.set(config.slotId, slot);
|
|
28827
|
+
this._slots.update((slots) => new Map(slots).set(config.slotId, slot));
|
|
28828
|
+
// Mostrar el ad
|
|
28829
|
+
googletag.enableServices();
|
|
28830
|
+
googletag.display(config.slotId);
|
|
28831
|
+
this.updateSlotState(config.slotId, 'rendered');
|
|
28832
|
+
this.emitEvent({ type: 'loaded', slotId: config.slotId, isEmpty: false });
|
|
28833
|
+
if (this._isDebugMode()) {
|
|
28834
|
+
console.log(`[ValtechAds] Slot definido: ${config.slotId}`, {
|
|
28835
|
+
adUnitPath,
|
|
28836
|
+
sizes: config.size,
|
|
28837
|
+
});
|
|
28838
|
+
}
|
|
28839
|
+
resolve(config.slotId);
|
|
28840
|
+
}
|
|
28841
|
+
catch (error) {
|
|
28842
|
+
console.error('[ValtechAds] Error definiendo slot:', error);
|
|
28843
|
+
this.updateSlotState(config.slotId, 'error');
|
|
28844
|
+
this.emitEvent({ type: 'error', slotId: config.slotId, error: error });
|
|
28845
|
+
resolve(null);
|
|
28846
|
+
}
|
|
28847
|
+
});
|
|
28848
|
+
});
|
|
26845
28849
|
}
|
|
26846
28850
|
/**
|
|
26847
|
-
*
|
|
28851
|
+
* Destruye un slot y libera recursos.
|
|
28852
|
+
*
|
|
28853
|
+
* @param slotId - ID del slot a destruir
|
|
26848
28854
|
*/
|
|
26849
|
-
|
|
26850
|
-
|
|
28855
|
+
destroySlot(slotId) {
|
|
28856
|
+
const slot = this.activeSlots.get(slotId);
|
|
28857
|
+
if (!slot)
|
|
28858
|
+
return;
|
|
28859
|
+
const googletag = window.googletag;
|
|
28860
|
+
if (googletag) {
|
|
28861
|
+
googletag.cmd.push(() => {
|
|
28862
|
+
googletag.destroySlots([slot]);
|
|
28863
|
+
});
|
|
28864
|
+
}
|
|
28865
|
+
this.activeSlots.delete(slotId);
|
|
28866
|
+
this._slots.update((slots) => {
|
|
28867
|
+
const newSlots = new Map(slots);
|
|
28868
|
+
newSlots.delete(slotId);
|
|
28869
|
+
return newSlots;
|
|
28870
|
+
});
|
|
28871
|
+
this._slotStates.update((states) => {
|
|
28872
|
+
const newStates = new Map(states);
|
|
28873
|
+
newStates.delete(slotId);
|
|
28874
|
+
return newStates;
|
|
28875
|
+
});
|
|
28876
|
+
if (this._isDebugMode()) {
|
|
28877
|
+
console.log(`[ValtechAds] Slot destruido: ${slotId}`);
|
|
28878
|
+
}
|
|
26851
28879
|
}
|
|
26852
28880
|
/**
|
|
26853
|
-
*
|
|
28881
|
+
* Refresca un slot especifico o todos los slots activos.
|
|
26854
28882
|
*
|
|
26855
|
-
* @param
|
|
26856
|
-
*
|
|
26857
|
-
* @example
|
|
26858
|
-
* presets.registerPresets({
|
|
26859
|
-
* button: {
|
|
26860
|
-
* 'primary-action': { size: 'large', color: 'primary' },
|
|
26861
|
-
* },
|
|
26862
|
-
* card: {
|
|
26863
|
-
* 'feature': { variant: 'elevated' },
|
|
26864
|
-
* }
|
|
26865
|
-
* });
|
|
28883
|
+
* @param slotIds - IDs de slots a refrescar (undefined = todos)
|
|
26866
28884
|
*/
|
|
26867
|
-
|
|
26868
|
-
this.
|
|
28885
|
+
refreshSlots(slotIds) {
|
|
28886
|
+
if (!this.isEnabled())
|
|
28887
|
+
return;
|
|
28888
|
+
const googletag = window.googletag;
|
|
28889
|
+
if (!googletag)
|
|
28890
|
+
return;
|
|
28891
|
+
googletag.cmd.push(() => {
|
|
28892
|
+
if (slotIds && slotIds.length > 0) {
|
|
28893
|
+
const slots = slotIds.map((id) => this.activeSlots.get(id)).filter((s) => !!s);
|
|
28894
|
+
if (slots.length > 0) {
|
|
28895
|
+
googletag.pubads().refresh(slots);
|
|
28896
|
+
if (this._isDebugMode()) {
|
|
28897
|
+
console.log(`[ValtechAds] Slots refrescados: ${slotIds.join(', ')}`);
|
|
28898
|
+
}
|
|
28899
|
+
}
|
|
28900
|
+
}
|
|
28901
|
+
else {
|
|
28902
|
+
googletag.pubads().refresh();
|
|
28903
|
+
if (this._isDebugMode()) {
|
|
28904
|
+
console.log('[ValtechAds] Todos los slots refrescados');
|
|
28905
|
+
}
|
|
28906
|
+
}
|
|
28907
|
+
});
|
|
26869
28908
|
}
|
|
26870
28909
|
/**
|
|
26871
|
-
*
|
|
28910
|
+
* Obtiene el estado de un slot.
|
|
28911
|
+
*
|
|
28912
|
+
* @param slotId - ID del slot
|
|
28913
|
+
* @returns Estado actual del slot
|
|
26872
28914
|
*/
|
|
26873
|
-
|
|
26874
|
-
this.
|
|
26875
|
-
...current,
|
|
26876
|
-
[component]: {
|
|
26877
|
-
...current[component],
|
|
26878
|
-
...presets,
|
|
26879
|
-
},
|
|
26880
|
-
}));
|
|
28915
|
+
getSlotState(slotId) {
|
|
28916
|
+
return this._slotStates().get(slotId) ?? 'idle';
|
|
26881
28917
|
}
|
|
28918
|
+
// ===========================================================================
|
|
28919
|
+
// PREMIUM USER
|
|
28920
|
+
// ===========================================================================
|
|
26882
28921
|
/**
|
|
26883
|
-
*
|
|
26884
|
-
|
|
26885
|
-
|
|
26886
|
-
|
|
28922
|
+
* Actualiza el estado premium del usuario.
|
|
28923
|
+
* Llamar cuando cambie el estado de suscripcion.
|
|
28924
|
+
*
|
|
28925
|
+
* @param isPremium - Nuevo estado premium
|
|
28926
|
+
*/
|
|
28927
|
+
updatePremiumStatus(isPremium) {
|
|
28928
|
+
const wasEnabled = this.isEnabled();
|
|
28929
|
+
this._isPremiumUser.set(isPremium);
|
|
28930
|
+
if (isPremium && wasEnabled) {
|
|
28931
|
+
// Destruir todos los slots activos
|
|
28932
|
+
this.destroyAllSlots();
|
|
28933
|
+
if (this._isDebugMode()) {
|
|
28934
|
+
console.log('[ValtechAds] Usuario ahora es premium - ads deshabilitados');
|
|
28935
|
+
}
|
|
28936
|
+
}
|
|
28937
|
+
else if (!isPremium && !wasEnabled && this._isInitialized()) {
|
|
28938
|
+
if (this._isDebugMode()) {
|
|
28939
|
+
console.log('[ValtechAds] Usuario ya no es premium - ads habilitados');
|
|
28940
|
+
}
|
|
28941
|
+
}
|
|
26887
28942
|
}
|
|
26888
28943
|
/**
|
|
26889
|
-
*
|
|
28944
|
+
* Destruye todos los slots activos.
|
|
26890
28945
|
*/
|
|
26891
|
-
|
|
26892
|
-
|
|
28946
|
+
destroyAllSlots() {
|
|
28947
|
+
const googletag = window.googletag;
|
|
28948
|
+
if (googletag) {
|
|
28949
|
+
googletag.cmd.push(() => {
|
|
28950
|
+
googletag.destroySlots();
|
|
28951
|
+
});
|
|
28952
|
+
}
|
|
28953
|
+
this.activeSlots.clear();
|
|
28954
|
+
this._slots.set(new Map());
|
|
28955
|
+
this._slotStates.set(new Map());
|
|
28956
|
+
if (this._isDebugMode()) {
|
|
28957
|
+
console.log('[ValtechAds] Todos los slots destruidos');
|
|
28958
|
+
}
|
|
26893
28959
|
}
|
|
26894
|
-
|
|
26895
|
-
|
|
28960
|
+
// ===========================================================================
|
|
28961
|
+
// PRIVATE METHODS
|
|
28962
|
+
// ===========================================================================
|
|
28963
|
+
resolveSizes(size) {
|
|
28964
|
+
if (typeof size === 'string') {
|
|
28965
|
+
// Single preset size
|
|
28966
|
+
const mapped = AD_SIZE_MAP[size];
|
|
28967
|
+
return mapped === 'fluid' ? 'fluid' : [mapped];
|
|
28968
|
+
}
|
|
28969
|
+
if (Array.isArray(size)) {
|
|
28970
|
+
if (typeof size[0] === 'string') {
|
|
28971
|
+
// Array of preset sizes
|
|
28972
|
+
return size.map((s) => {
|
|
28973
|
+
const mapped = AD_SIZE_MAP[s];
|
|
28974
|
+
return mapped === 'fluid' ? 'fluid' : mapped;
|
|
28975
|
+
});
|
|
28976
|
+
}
|
|
28977
|
+
// Direct size array
|
|
28978
|
+
return size;
|
|
28979
|
+
}
|
|
28980
|
+
return 'fluid';
|
|
28981
|
+
}
|
|
28982
|
+
buildSizeMapping(googletag, mapping) {
|
|
28983
|
+
const builder = googletag.sizeMapping();
|
|
28984
|
+
// Ordenar por viewport descendente
|
|
28985
|
+
const sorted = [...mapping].sort((a, b) => b.viewportWidth - a.viewportWidth);
|
|
28986
|
+
for (const m of sorted) {
|
|
28987
|
+
builder.addSize([m.viewportWidth, 0], m.sizes);
|
|
28988
|
+
}
|
|
28989
|
+
return builder.build();
|
|
28990
|
+
}
|
|
28991
|
+
applyTargeting(slot, targeting) {
|
|
28992
|
+
// Aplicar targeting global
|
|
28993
|
+
if (this.config.globalTargeting) {
|
|
28994
|
+
for (const [key, value] of Object.entries(this.config.globalTargeting)) {
|
|
28995
|
+
slot.setTargeting(key, value);
|
|
28996
|
+
}
|
|
28997
|
+
}
|
|
28998
|
+
// Aplicar targeting del slot
|
|
28999
|
+
if (targeting) {
|
|
29000
|
+
for (const [key, value] of Object.entries(targeting)) {
|
|
29001
|
+
slot.setTargeting(key, value);
|
|
29002
|
+
}
|
|
29003
|
+
}
|
|
29004
|
+
// Agregar targeting de debug si esta habilitado
|
|
29005
|
+
if (this._isDebugMode()) {
|
|
29006
|
+
slot.setTargeting('test', 'true');
|
|
29007
|
+
}
|
|
29008
|
+
}
|
|
29009
|
+
updateSlotState(slotId, state) {
|
|
29010
|
+
this._slotStates.update((states) => new Map(states).set(slotId, state));
|
|
29011
|
+
}
|
|
29012
|
+
emitEvent(event) {
|
|
29013
|
+
const fullEvent = { ...event, timestamp: new Date() };
|
|
29014
|
+
this._events.update((events) => [...events.slice(-99), fullEvent]);
|
|
29015
|
+
// Callbacks de configuracion
|
|
29016
|
+
if (event.type === 'loaded' || event.type === 'empty') {
|
|
29017
|
+
this.config.onAdLoaded?.(event.slotId, event.isEmpty ?? false);
|
|
29018
|
+
}
|
|
29019
|
+
else if (event.type === 'error' && event.error) {
|
|
29020
|
+
this.config.onAdError?.(event.slotId, event.error);
|
|
29021
|
+
}
|
|
29022
|
+
}
|
|
29023
|
+
setupRouteListener() {
|
|
29024
|
+
if (!this.config.excludeRoutes || this.config.excludeRoutes.length === 0) {
|
|
29025
|
+
return;
|
|
29026
|
+
}
|
|
29027
|
+
this.routerSubscription = this.router.events
|
|
29028
|
+
.pipe(filter$1((event) => event instanceof NavigationEnd))
|
|
29029
|
+
.subscribe((event) => {
|
|
29030
|
+
const shouldHide = this.isRouteExcluded(event.urlAfterRedirects);
|
|
29031
|
+
if (shouldHide) {
|
|
29032
|
+
this.destroyAllSlots();
|
|
29033
|
+
}
|
|
29034
|
+
});
|
|
29035
|
+
}
|
|
29036
|
+
setupAutoRefresh() {
|
|
29037
|
+
const intervalMs = (this.config.autoRefreshInterval ?? 0) * 1000;
|
|
29038
|
+
if (intervalMs <= 0)
|
|
29039
|
+
return;
|
|
29040
|
+
this.refreshTimerId = setInterval(() => {
|
|
29041
|
+
if (this.isEnabled() && this.activeSlots.size > 0) {
|
|
29042
|
+
this.refreshSlots();
|
|
29043
|
+
}
|
|
29044
|
+
}, intervalMs);
|
|
29045
|
+
}
|
|
29046
|
+
isRouteExcluded(url) {
|
|
29047
|
+
if (!this.config.excludeRoutes || this.config.excludeRoutes.length === 0) {
|
|
29048
|
+
return false;
|
|
29049
|
+
}
|
|
29050
|
+
const currentUrl = url ?? this.router.url;
|
|
29051
|
+
return this.config.excludeRoutes.some((pattern) => {
|
|
29052
|
+
const regex = new RegExp(pattern);
|
|
29053
|
+
return regex.test(currentUrl);
|
|
29054
|
+
});
|
|
29055
|
+
}
|
|
29056
|
+
// ===========================================================================
|
|
29057
|
+
// LIFECYCLE
|
|
29058
|
+
// ===========================================================================
|
|
29059
|
+
ngOnDestroy() {
|
|
29060
|
+
this.routerSubscription?.unsubscribe();
|
|
29061
|
+
if (this.refreshTimerId) {
|
|
29062
|
+
clearInterval(this.refreshTimerId);
|
|
29063
|
+
}
|
|
29064
|
+
this.destroyAllSlots();
|
|
29065
|
+
this.consentService.destroy();
|
|
29066
|
+
}
|
|
29067
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsService, deps: [{ token: i0.Injector }, { token: VALTECH_ADS_CONFIG }, { token: PLATFORM_ID }, { token: i1$1.Router }, { token: AdsLoaderService }, { token: AdsConsentService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
29068
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsService, providedIn: 'root' }); }
|
|
26896
29069
|
}
|
|
26897
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type:
|
|
29070
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsService, decorators: [{
|
|
26898
29071
|
type: Injectable,
|
|
26899
29072
|
args: [{ providedIn: 'root' }]
|
|
26900
|
-
}] }
|
|
29073
|
+
}], ctorParameters: () => [{ type: i0.Injector }, { type: undefined, decorators: [{
|
|
29074
|
+
type: Inject,
|
|
29075
|
+
args: [VALTECH_ADS_CONFIG]
|
|
29076
|
+
}] }, { type: Object, decorators: [{
|
|
29077
|
+
type: Inject,
|
|
29078
|
+
args: [PLATFORM_ID]
|
|
29079
|
+
}] }, { type: i1$1.Router }, { type: AdsLoaderService }, { type: AdsConsentService }] });
|
|
26901
29080
|
|
|
26902
29081
|
/**
|
|
26903
|
-
*
|
|
29082
|
+
* Ads Configuration
|
|
26904
29083
|
*
|
|
26905
|
-
*
|
|
26906
|
-
*
|
|
29084
|
+
* Configuracion e inicializacion de Google Ad Manager para Angular.
|
|
29085
|
+
* Usa provideValtechAds() en el bootstrap de tu aplicacion.
|
|
29086
|
+
*/
|
|
29087
|
+
/**
|
|
29088
|
+
* Token de inyeccion para la configuracion de Ads.
|
|
29089
|
+
* Usado internamente por los servicios de ads.
|
|
29090
|
+
*/
|
|
29091
|
+
const VALTECH_ADS_CONFIG = new InjectionToken('ValtechAdsConfig');
|
|
29092
|
+
/**
|
|
29093
|
+
* Configuracion por defecto de lazy loading.
|
|
29094
|
+
*/
|
|
29095
|
+
const DEFAULT_LAZY_LOAD_CONFIG = {
|
|
29096
|
+
rootMargin: '200px',
|
|
29097
|
+
threshold: 0,
|
|
29098
|
+
fetchMarginPercent: 200,
|
|
29099
|
+
renderMarginPercent: 100,
|
|
29100
|
+
};
|
|
29101
|
+
/**
|
|
29102
|
+
* Configuracion por defecto del servicio de ads.
|
|
29103
|
+
*/
|
|
29104
|
+
const DEFAULT_ADS_CONFIG = {
|
|
29105
|
+
debugMode: false,
|
|
29106
|
+
lazyLoad: true,
|
|
29107
|
+
lazyLoadConfig: DEFAULT_LAZY_LOAD_CONFIG,
|
|
29108
|
+
enablePersonalization: true,
|
|
29109
|
+
showNonPersonalizedAds: true,
|
|
29110
|
+
autoRefreshInterval: 0, // Deshabilitado por defecto
|
|
29111
|
+
defaultSlotConfig: {
|
|
29112
|
+
collapseEmpty: true,
|
|
29113
|
+
showSkeleton: true,
|
|
29114
|
+
minHeight: '90px',
|
|
29115
|
+
},
|
|
29116
|
+
};
|
|
29117
|
+
/**
|
|
29118
|
+
* Factory para inicializar AdsService.
|
|
29119
|
+
* Se ejecuta durante el bootstrap de la aplicacion.
|
|
29120
|
+
*/
|
|
29121
|
+
function initializeAds(adsService) {
|
|
29122
|
+
return () => adsService.initialize();
|
|
29123
|
+
}
|
|
29124
|
+
/**
|
|
29125
|
+
* Provee el servicio de Ads a la aplicacion Angular.
|
|
29126
|
+
*
|
|
29127
|
+
* @param config - Configuracion de Google Ad Manager
|
|
29128
|
+
* @returns EnvironmentProviders para usar en bootstrapApplication
|
|
26907
29129
|
*
|
|
26908
29130
|
* @example
|
|
26909
|
-
*
|
|
26910
|
-
*
|
|
29131
|
+
* ```typescript
|
|
29132
|
+
* // main.ts
|
|
29133
|
+
* import { bootstrapApplication } from '@angular/platform-browser';
|
|
29134
|
+
* import { provideValtechAds, provideValtechFirebase } from 'valtech-components';
|
|
29135
|
+
* import { environment } from './environments/environment';
|
|
26911
29136
|
*
|
|
26912
|
-
*
|
|
29137
|
+
* bootstrapApplication(AppComponent, {
|
|
26913
29138
|
* providers: [
|
|
26914
|
-
*
|
|
26915
|
-
*
|
|
26916
|
-
*
|
|
26917
|
-
*
|
|
26918
|
-
* 'danger': { size: 'medium', color: 'danger', fill: 'solid' },
|
|
26919
|
-
* },
|
|
26920
|
-
* card: {
|
|
26921
|
-
* 'feature': { variant: 'elevated', padding: 'large' },
|
|
26922
|
-
* 'compact': { variant: 'flat', padding: 'small' },
|
|
26923
|
-
* },
|
|
26924
|
-
* input: {
|
|
26925
|
-
* 'form-field': { size: 'medium', fill: 'outline', labelPosition: 'floating' },
|
|
26926
|
-
* }
|
|
29139
|
+
* // Firebase primero (para consent mode)
|
|
29140
|
+
* provideValtechFirebase({
|
|
29141
|
+
* firebase: environment.firebase,
|
|
29142
|
+
* enableAnalytics: true,
|
|
26927
29143
|
* }),
|
|
26928
|
-
*
|
|
26929
|
-
*
|
|
29144
|
+
*
|
|
29145
|
+
* // Luego Ads
|
|
29146
|
+
* provideValtechAds({
|
|
29147
|
+
* networkId: '/12345678',
|
|
29148
|
+
* debugMode: !environment.production,
|
|
29149
|
+
* isPremiumUser: () => inject(AuthService).hasRole('premium'),
|
|
29150
|
+
* }),
|
|
29151
|
+
* ],
|
|
29152
|
+
* });
|
|
29153
|
+
* ```
|
|
29154
|
+
*
|
|
29155
|
+
* @example Con targeting global y rutas excluidas
|
|
29156
|
+
* ```typescript
|
|
29157
|
+
* provideValtechAds({
|
|
29158
|
+
* networkId: '/12345678',
|
|
29159
|
+
* globalTargeting: {
|
|
29160
|
+
* site: 'myvaltech',
|
|
29161
|
+
* section: 'app',
|
|
29162
|
+
* },
|
|
29163
|
+
* excludeRoutes: [
|
|
29164
|
+
* '^/checkout',
|
|
29165
|
+
* '^/payment',
|
|
29166
|
+
* '^/premium',
|
|
29167
|
+
* ],
|
|
29168
|
+
* })
|
|
29169
|
+
* ```
|
|
26930
29170
|
*/
|
|
26931
|
-
function
|
|
29171
|
+
function provideValtechAds(config) {
|
|
29172
|
+
// Merge config con defaults
|
|
29173
|
+
const mergedConfig = {
|
|
29174
|
+
...DEFAULT_ADS_CONFIG,
|
|
29175
|
+
...config,
|
|
29176
|
+
lazyLoadConfig: {
|
|
29177
|
+
...DEFAULT_LAZY_LOAD_CONFIG,
|
|
29178
|
+
...config.lazyLoadConfig,
|
|
29179
|
+
},
|
|
29180
|
+
defaultSlotConfig: {
|
|
29181
|
+
...DEFAULT_ADS_CONFIG.defaultSlotConfig,
|
|
29182
|
+
...config.defaultSlotConfig,
|
|
29183
|
+
},
|
|
29184
|
+
};
|
|
26932
29185
|
return makeEnvironmentProviders([
|
|
29186
|
+
{ provide: VALTECH_ADS_CONFIG, useValue: mergedConfig },
|
|
29187
|
+
// Inicializar AdsService al arrancar (pero NO cargar script GPT aun)
|
|
26933
29188
|
{
|
|
26934
29189
|
provide: APP_INITIALIZER,
|
|
26935
|
-
useFactory:
|
|
26936
|
-
|
|
26937
|
-
presetService.registerPresets(presets);
|
|
26938
|
-
};
|
|
26939
|
-
},
|
|
26940
|
-
deps: [PresetService],
|
|
29190
|
+
useFactory: initializeAds,
|
|
29191
|
+
deps: [AdsService],
|
|
26941
29192
|
multi: true,
|
|
26942
29193
|
},
|
|
26943
29194
|
]);
|
|
26944
29195
|
}
|
|
26945
29196
|
|
|
26946
|
-
|
|
29197
|
+
/**
|
|
29198
|
+
* Ads Module
|
|
29199
|
+
*
|
|
29200
|
+
* Exports publicos para el servicio de Google Ad Manager.
|
|
29201
|
+
*/
|
|
29202
|
+
// Configuration
|
|
29203
|
+
|
|
29204
|
+
/**
|
|
29205
|
+
* Ad Slot Component
|
|
29206
|
+
*
|
|
29207
|
+
* Componente standalone para mostrar ads de Google Ad Manager.
|
|
29208
|
+
* Se integra automaticamente con el servicio de Ads y respeta consent + premium.
|
|
29209
|
+
*
|
|
29210
|
+
* @example
|
|
29211
|
+
* ```html
|
|
29212
|
+
* <!-- Banner basico -->
|
|
29213
|
+
* <val-ad-slot
|
|
29214
|
+
* slotId="homepage-top"
|
|
29215
|
+
* adUnitPath="/homepage/top"
|
|
29216
|
+
* size="leaderboard"
|
|
29217
|
+
* />
|
|
29218
|
+
*
|
|
29219
|
+
* <!-- Con responsivo -->
|
|
29220
|
+
* <val-ad-slot
|
|
29221
|
+
* slotId="sidebar-ad"
|
|
29222
|
+
* adUnitPath="/sidebar"
|
|
29223
|
+
* [size]="['medium-rectangle', 'half-page']"
|
|
29224
|
+
* [sizeMapping]="[
|
|
29225
|
+
* { viewportWidth: 1024, sizes: [[300, 600]] },
|
|
29226
|
+
* { viewportWidth: 768, sizes: [[300, 250]] },
|
|
29227
|
+
* { viewportWidth: 0, sizes: [[320, 50]] }
|
|
29228
|
+
* ]"
|
|
29229
|
+
* />
|
|
29230
|
+
*
|
|
29231
|
+
* <!-- Native ad -->
|
|
29232
|
+
* <val-ad-slot
|
|
29233
|
+
* slotId="article-native"
|
|
29234
|
+
* adUnitPath="/article/native"
|
|
29235
|
+
* size="native"
|
|
29236
|
+
* [isNative]="true"
|
|
29237
|
+
* />
|
|
29238
|
+
* ```
|
|
29239
|
+
*/
|
|
29240
|
+
class AdSlotComponent {
|
|
29241
|
+
constructor() {
|
|
29242
|
+
this.adsService = inject(AdsService);
|
|
29243
|
+
this.platformId = inject(PLATFORM_ID);
|
|
29244
|
+
/** Tamano del ad */
|
|
29245
|
+
this.size = 'medium-rectangle';
|
|
29246
|
+
/** Colapsar si vacio */
|
|
29247
|
+
this.collapseEmpty = true;
|
|
29248
|
+
/** Es native ad */
|
|
29249
|
+
this.isNative = false;
|
|
29250
|
+
/** CSS class adicional */
|
|
29251
|
+
this.cssClass = '';
|
|
29252
|
+
/** Altura minima */
|
|
29253
|
+
this.minHeight = '90px';
|
|
29254
|
+
/** Mostrar skeleton */
|
|
29255
|
+
this.showSkeleton = true;
|
|
29256
|
+
// ===========================================================================
|
|
29257
|
+
// ESTADO
|
|
29258
|
+
// ===========================================================================
|
|
29259
|
+
this._state = signal('idle');
|
|
29260
|
+
this.state = this._state.asReadonly();
|
|
29261
|
+
/** Indica si el componente debe renderizarse */
|
|
29262
|
+
this.shouldRender = computed(() => {
|
|
29263
|
+
// No renderizar en SSR
|
|
29264
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
29265
|
+
return false;
|
|
29266
|
+
}
|
|
29267
|
+
// No renderizar si usuario es premium
|
|
29268
|
+
if (this.adsService.isPremiumUser()) {
|
|
29269
|
+
return false;
|
|
29270
|
+
}
|
|
29271
|
+
// No renderizar si ads estan deshabilitados y aun no inicializado
|
|
29272
|
+
if (!this.adsService.isInitialized()) {
|
|
29273
|
+
return false;
|
|
29274
|
+
}
|
|
29275
|
+
return true;
|
|
29276
|
+
});
|
|
29277
|
+
/** Ancho del skeleton */
|
|
29278
|
+
this.skeletonWidth = computed(() => {
|
|
29279
|
+
const firstSize = this.getFirstSize();
|
|
29280
|
+
return firstSize ? `${firstSize[0]}px` : '100%';
|
|
29281
|
+
});
|
|
29282
|
+
/** Altura del skeleton */
|
|
29283
|
+
this.skeletonHeight = computed(() => {
|
|
29284
|
+
const firstSize = this.getFirstSize();
|
|
29285
|
+
return firstSize ? `${firstSize[1]}px` : this.minHeight;
|
|
29286
|
+
});
|
|
29287
|
+
}
|
|
29288
|
+
// ===========================================================================
|
|
29289
|
+
// LIFECYCLE
|
|
29290
|
+
// ===========================================================================
|
|
29291
|
+
async ngOnInit() {
|
|
29292
|
+
if (!this.shouldRender()) {
|
|
29293
|
+
return;
|
|
29294
|
+
}
|
|
29295
|
+
await this.initializeSlot();
|
|
29296
|
+
}
|
|
29297
|
+
ngOnDestroy() {
|
|
29298
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
29299
|
+
this.adsService.destroySlot(this.slotId);
|
|
29300
|
+
}
|
|
29301
|
+
}
|
|
29302
|
+
// ===========================================================================
|
|
29303
|
+
// PRIVATE
|
|
29304
|
+
// ===========================================================================
|
|
29305
|
+
async initializeSlot() {
|
|
29306
|
+
this._state.set('loading');
|
|
29307
|
+
const config = {
|
|
29308
|
+
slotId: this.slotId,
|
|
29309
|
+
adUnitPath: this.adUnitPath,
|
|
29310
|
+
size: this.size,
|
|
29311
|
+
sizeMapping: this.sizeMapping,
|
|
29312
|
+
targeting: this.targeting,
|
|
29313
|
+
collapseEmpty: this.collapseEmpty,
|
|
29314
|
+
isNative: this.isNative,
|
|
29315
|
+
cssClass: this.cssClass,
|
|
29316
|
+
minHeight: this.minHeight,
|
|
29317
|
+
showSkeleton: this.showSkeleton,
|
|
29318
|
+
};
|
|
29319
|
+
const result = await this.adsService.defineSlot(config);
|
|
29320
|
+
if (result) {
|
|
29321
|
+
this._state.set('rendered');
|
|
29322
|
+
}
|
|
29323
|
+
else {
|
|
29324
|
+
this._state.set(this.adsService.getSlotState(this.slotId));
|
|
29325
|
+
}
|
|
29326
|
+
}
|
|
29327
|
+
getFirstSize() {
|
|
29328
|
+
if (typeof this.size === 'string') {
|
|
29329
|
+
const mapped = AD_SIZE_MAP[this.size];
|
|
29330
|
+
return mapped === 'fluid' ? null : mapped;
|
|
29331
|
+
}
|
|
29332
|
+
if (Array.isArray(this.size)) {
|
|
29333
|
+
if (typeof this.size[0] === 'number') {
|
|
29334
|
+
return this.size;
|
|
29335
|
+
}
|
|
29336
|
+
if (typeof this.size[0] === 'string') {
|
|
29337
|
+
const first = AD_SIZE_MAP[this.size[0]];
|
|
29338
|
+
return first === 'fluid' ? null : first;
|
|
29339
|
+
}
|
|
29340
|
+
if (Array.isArray(this.size[0])) {
|
|
29341
|
+
return this.size[0];
|
|
29342
|
+
}
|
|
29343
|
+
}
|
|
29344
|
+
return null;
|
|
29345
|
+
}
|
|
29346
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
29347
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AdSlotComponent, isStandalone: true, selector: "val-ad-slot", inputs: { slotId: "slotId", adUnitPath: "adUnitPath", size: "size", sizeMapping: "sizeMapping", targeting: "targeting", collapseEmpty: "collapseEmpty", isNative: "isNative", cssClass: "cssClass", minHeight: "minHeight", showSkeleton: "showSkeleton" }, ngImport: i0, template: `
|
|
29348
|
+
@if (shouldRender()) {
|
|
29349
|
+
<div
|
|
29350
|
+
class="val-ad-slot"
|
|
29351
|
+
[class.val-ad-slot--loading]="state() === 'loading'"
|
|
29352
|
+
[class.val-ad-slot--rendered]="state() === 'rendered'"
|
|
29353
|
+
[class.val-ad-slot--empty]="state() === 'empty'"
|
|
29354
|
+
[class.val-ad-slot--hidden]="state() === 'hidden'"
|
|
29355
|
+
[class.val-ad-slot--native]="isNative"
|
|
29356
|
+
[class]="cssClass"
|
|
29357
|
+
[style.min-height]="minHeight"
|
|
29358
|
+
>
|
|
29359
|
+
<!-- Skeleton mientras carga -->
|
|
29360
|
+
@if (showSkeleton && state() === 'loading') {
|
|
29361
|
+
<div
|
|
29362
|
+
class="val-ad-slot__skeleton"
|
|
29363
|
+
[style.width]="skeletonWidth()"
|
|
29364
|
+
[style.height]="skeletonHeight()"
|
|
29365
|
+
></div>
|
|
29366
|
+
}
|
|
29367
|
+
|
|
29368
|
+
<!-- Container del ad -->
|
|
29369
|
+
<div
|
|
29370
|
+
[id]="slotId"
|
|
29371
|
+
class="val-ad-slot__container"
|
|
29372
|
+
[class.val-ad-slot__container--hidden]="state() === 'loading' && showSkeleton"
|
|
29373
|
+
></div>
|
|
29374
|
+
|
|
29375
|
+
<!-- Debug info -->
|
|
29376
|
+
@if (adsService.isDebugMode()) {
|
|
29377
|
+
<div class="val-ad-slot__debug">
|
|
29378
|
+
<small>{{ slotId }} | {{ adUnitPath }} | {{ state() }}</small>
|
|
29379
|
+
</div>
|
|
29380
|
+
}
|
|
29381
|
+
</div>
|
|
29382
|
+
}
|
|
29383
|
+
`, isInline: true, styles: [".val-ad-slot{position:relative;display:flex;justify-content:center;align-items:center;overflow:hidden}.val-ad-slot--loading{background-color:var(--ion-color-light, #f4f4f4)}.val-ad-slot--empty,.val-ad-slot--hidden{display:none!important}.val-ad-slot--native{width:100%}.val-ad-slot__skeleton{background:linear-gradient(90deg,var(--ion-color-light, #f4f4f4) 25%,var(--ion-color-light-shade, #e0e0e0) 50%,var(--ion-color-light, #f4f4f4) 75%);background-size:200% 100%;animation:skeleton-loading 1.5s infinite;border-radius:4px}.val-ad-slot__container--hidden{visibility:hidden;position:absolute}.val-ad-slot__debug{position:absolute;bottom:0;left:0;background:#000000b3;color:#fff;padding:2px 6px;font-size:10px;z-index:1000;font-family:monospace}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
29384
|
+
}
|
|
29385
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdSlotComponent, decorators: [{
|
|
29386
|
+
type: Component,
|
|
29387
|
+
args: [{ selector: 'val-ad-slot', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
29388
|
+
@if (shouldRender()) {
|
|
29389
|
+
<div
|
|
29390
|
+
class="val-ad-slot"
|
|
29391
|
+
[class.val-ad-slot--loading]="state() === 'loading'"
|
|
29392
|
+
[class.val-ad-slot--rendered]="state() === 'rendered'"
|
|
29393
|
+
[class.val-ad-slot--empty]="state() === 'empty'"
|
|
29394
|
+
[class.val-ad-slot--hidden]="state() === 'hidden'"
|
|
29395
|
+
[class.val-ad-slot--native]="isNative"
|
|
29396
|
+
[class]="cssClass"
|
|
29397
|
+
[style.min-height]="minHeight"
|
|
29398
|
+
>
|
|
29399
|
+
<!-- Skeleton mientras carga -->
|
|
29400
|
+
@if (showSkeleton && state() === 'loading') {
|
|
29401
|
+
<div
|
|
29402
|
+
class="val-ad-slot__skeleton"
|
|
29403
|
+
[style.width]="skeletonWidth()"
|
|
29404
|
+
[style.height]="skeletonHeight()"
|
|
29405
|
+
></div>
|
|
29406
|
+
}
|
|
29407
|
+
|
|
29408
|
+
<!-- Container del ad -->
|
|
29409
|
+
<div
|
|
29410
|
+
[id]="slotId"
|
|
29411
|
+
class="val-ad-slot__container"
|
|
29412
|
+
[class.val-ad-slot__container--hidden]="state() === 'loading' && showSkeleton"
|
|
29413
|
+
></div>
|
|
29414
|
+
|
|
29415
|
+
<!-- Debug info -->
|
|
29416
|
+
@if (adsService.isDebugMode()) {
|
|
29417
|
+
<div class="val-ad-slot__debug">
|
|
29418
|
+
<small>{{ slotId }} | {{ adUnitPath }} | {{ state() }}</small>
|
|
29419
|
+
</div>
|
|
29420
|
+
}
|
|
29421
|
+
</div>
|
|
29422
|
+
}
|
|
29423
|
+
`, styles: [".val-ad-slot{position:relative;display:flex;justify-content:center;align-items:center;overflow:hidden}.val-ad-slot--loading{background-color:var(--ion-color-light, #f4f4f4)}.val-ad-slot--empty,.val-ad-slot--hidden{display:none!important}.val-ad-slot--native{width:100%}.val-ad-slot__skeleton{background:linear-gradient(90deg,var(--ion-color-light, #f4f4f4) 25%,var(--ion-color-light-shade, #e0e0e0) 50%,var(--ion-color-light, #f4f4f4) 75%);background-size:200% 100%;animation:skeleton-loading 1.5s infinite;border-radius:4px}.val-ad-slot__container--hidden{visibility:hidden;position:absolute}.val-ad-slot__debug{position:absolute;bottom:0;left:0;background:#000000b3;color:#fff;padding:2px 6px;font-size:10px;z-index:1000;font-family:monospace}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}\n"] }]
|
|
29424
|
+
}], propDecorators: { slotId: [{
|
|
29425
|
+
type: Input,
|
|
29426
|
+
args: [{ required: true }]
|
|
29427
|
+
}], adUnitPath: [{
|
|
29428
|
+
type: Input,
|
|
29429
|
+
args: [{ required: true }]
|
|
29430
|
+
}], size: [{
|
|
29431
|
+
type: Input
|
|
29432
|
+
}], sizeMapping: [{
|
|
29433
|
+
type: Input
|
|
29434
|
+
}], targeting: [{
|
|
29435
|
+
type: Input
|
|
29436
|
+
}], collapseEmpty: [{
|
|
29437
|
+
type: Input
|
|
29438
|
+
}], isNative: [{
|
|
29439
|
+
type: Input
|
|
29440
|
+
}], cssClass: [{
|
|
29441
|
+
type: Input
|
|
29442
|
+
}], minHeight: [{
|
|
29443
|
+
type: Input
|
|
29444
|
+
}], showSkeleton: [{
|
|
29445
|
+
type: Input
|
|
29446
|
+
}] } });
|
|
26947
29447
|
|
|
26948
29448
|
/*
|
|
26949
29449
|
* Public API Surface of valtech-components
|
|
@@ -26953,5 +29453,5 @@ function provideValtechPresets(presets) {
|
|
|
26953
29453
|
* Generated bundle index. Do not edit.
|
|
26954
29454
|
*/
|
|
26955
29455
|
|
|
26956
|
-
export { ARTICLE_SPACING, AccordionComponent, ActionHeaderComponent, ActionType, AlertBoxComponent, ArticleBuilder, ArticleComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, BannerComponent, BaseDefault, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, CheckInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContentLoaderComponent, CountdownComponent, CurrencyInputComponent, DEFAULT_AUTH_CONFIG, DEFAULT_CANCEL_BUTTON, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_EMPTY_STATE, DEFAULT_LEGEND_LABELS, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PAYMENT_STATUS_COLORS, DEFAULT_PAYMENT_STATUS_LABELS, DEFAULT_PLATFORMS, DEFAULT_STATUS_COLORS, DEFAULT_STATUS_LABELS, DEFAULT_WINNER_LABELS, DataTableComponent, DateInputComponent, DateRangeInputComponent, DeviceService, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FabComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FunHeaderComponent, GlowCardComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfoComponent, InputType, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinksAccordionComponent, LinksCakeComponent, LocalStorageService, LocaleService, MODAL_SIZES, MOTION, MenuComponent, MessagingService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, RadioInputComponent, RaffleStatusCardComponent, RangeInputComponent, RatingComponent, RecapCardComponent, RightsFooterComponent, SKELETON_PRESETS, 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, SimpleComponent, SkeletonComponent, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TabbedContentComponent, TabsComponent, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, VALTECH_AUTH_CONFIG, VALTECH_FIREBASE_CONFIG, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, buildPath, collections, createFirebaseConfig, createGlowCardProps, createNumberFromToField, createTitleProps, extractPathParams, getCollectionPath, getDocumentId, goToTop, guestGuard, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, permissionGuard, permissionGuardFromRoute, provideValtechAuth, provideValtechAuthInterceptor, provideValtechFirebase, provideValtechI18n, provideValtechPresets, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, storagePaths, superAdminGuard };
|
|
29456
|
+
export { AD_SIZE_MAP, ARTICLE_SPACING, AccordionComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsConsentService, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, ArticleBuilder, ArticleComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, BannerComponent, BaseDefault, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, CheckInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContentLoaderComponent, CountdownComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CANCEL_BUTTON, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_EMPTY_STATE, DEFAULT_LAZY_LOAD_CONFIG, DEFAULT_LEGEND_LABELS, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PAYMENT_STATUS_COLORS, DEFAULT_PAYMENT_STATUS_LABELS, DEFAULT_PLATFORMS, DEFAULT_STATUS_COLORS, DEFAULT_STATUS_LABELS, DEFAULT_WINNER_LABELS, DataTableComponent, DateInputComponent, DateRangeInputComponent, DeviceService, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FabComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FunHeaderComponent, GlowCardComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfoComponent, InputType, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinksAccordionComponent, LinksCakeComponent, LocalStorageService, LocaleService, MODAL_SIZES, MOTION, MenuComponent, MessagingService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAuthCallbackComponent, OAuthService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, RadioInputComponent, RaffleStatusCardComponent, RangeInputComponent, RatingComponent, RecapCardComponent, RightsFooterComponent, SKELETON_PRESETS, 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, SimpleComponent, SkeletonComponent, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TabbedContentComponent, TabsComponent, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, VALTECH_ADS_CONFIG, VALTECH_AUTH_CONFIG, VALTECH_FIREBASE_CONFIG, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, buildPath, collections, createFirebaseConfig, createGlowCardProps, createNumberFromToField, createTitleProps, extractPathParams, getCollectionPath, getDocumentId, goToTop, guestGuard, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, permissionGuard, permissionGuardFromRoute, provideValtechAds, provideValtechAuth, provideValtechAuthInterceptor, provideValtechFirebase, provideValtechI18n, provideValtechPresets, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, storagePaths, superAdminGuard };
|
|
26957
29457
|
//# sourceMappingURL=valtech-components.mjs.map
|