valtech-components 2.0.429 → 2.0.430
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/organisms/data-table/data-table.component.mjs +17 -3
- package/esm2022/lib/components/organisms/data-table/types.mjs +1 -1
- package/esm2022/lib/services/auth/auth-state.service.mjs +173 -0
- package/esm2022/lib/services/auth/auth.service.mjs +432 -0
- package/esm2022/lib/services/auth/config.mjs +76 -0
- package/esm2022/lib/services/auth/guards.mjs +194 -0
- package/esm2022/lib/services/auth/index.mjs +70 -0
- package/esm2022/lib/services/auth/interceptor.mjs +98 -0
- package/esm2022/lib/services/auth/storage.service.mjs +138 -0
- package/esm2022/lib/services/auth/sync.service.mjs +146 -0
- package/esm2022/lib/services/auth/token.service.mjs +113 -0
- package/esm2022/lib/services/auth/types.mjs +29 -0
- package/esm2022/public-api.mjs +4 -1
- package/fesm2022/valtech-components.mjs +1465 -8
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/organisms/data-table/types.d.ts +8 -0
- package/lib/services/auth/auth-state.service.d.ts +85 -0
- package/lib/services/auth/auth.service.d.ts +123 -0
- package/lib/services/auth/config.d.ts +38 -0
- package/lib/services/auth/guards.d.ts +123 -0
- package/lib/services/auth/index.d.ts +63 -0
- package/lib/services/auth/interceptor.d.ts +22 -0
- package/lib/services/auth/storage.service.d.ts +48 -0
- package/lib/services/auth/sync.service.d.ts +49 -0
- package/lib/services/auth/token.service.d.ts +51 -0
- package/lib/services/auth/types.d.ts +264 -0
- package/package.json +1 -1
- package/public-api.d.ts +1 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { EventEmitter, Component, Input, Output, Injectable, inject, HostListener, Pipe, ChangeDetectionStrategy, ViewChild, ChangeDetectorRef, ElementRef, InjectionToken, makeEnvironmentProviders, PLATFORM_ID, NgZone } from '@angular/core';
|
|
2
|
+
import { EventEmitter, Component, Input, Output, Injectable, inject, HostListener, Pipe, ChangeDetectionStrategy, ViewChild, ChangeDetectorRef, ElementRef, InjectionToken, makeEnvironmentProviders, PLATFORM_ID, NgZone, signal, computed, APP_INITIALIZER } 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, ModalController } from '@ionic/angular/standalone';
|
|
5
5
|
import * as i1 from '@angular/common';
|
|
@@ -13,7 +13,7 @@ import * as i1$1 from '@angular/platform-browser';
|
|
|
13
13
|
import QRCodeStyling from 'qr-code-styling';
|
|
14
14
|
import * as i1$2 from '@angular/forms';
|
|
15
15
|
import { ReactiveFormsModule, FormsModule, FormControl, Validators } from '@angular/forms';
|
|
16
|
-
import { BehaviorSubject, filter, map, distinctUntilChanged, Subject } from 'rxjs';
|
|
16
|
+
import { BehaviorSubject, filter, map, distinctUntilChanged, Subject, firstValueFrom, throwError, of } from 'rxjs';
|
|
17
17
|
import * as i1$3 from 'ng-otp-input';
|
|
18
18
|
import { NgOtpInputComponent, NgOtpInputModule } from 'ng-otp-input';
|
|
19
19
|
import * as i2 from '@ionic/angular';
|
|
@@ -32,6 +32,8 @@ import { provideAuth, getAuth, connectAuthEmulator, Auth, authState, signInWithC
|
|
|
32
32
|
import { provideFirestore, getFirestore, connectFirestoreEmulator, enableIndexedDbPersistence, Firestore, doc, getDoc, collection, query as query$1, getDocs, limit, docData, collectionData, serverTimestamp, addDoc, setDoc, updateDoc, deleteDoc, writeBatch, arrayUnion, arrayRemove, increment, where, orderBy, startAfter, startAt, endBefore, endAt, Timestamp } from '@angular/fire/firestore';
|
|
33
33
|
import { provideMessaging, getMessaging, Messaging, getToken, deleteToken, onMessage } from '@angular/fire/messaging';
|
|
34
34
|
import { provideStorage, getStorage, connectStorageEmulator, Storage, ref, uploadBytesResumable, getDownloadURL, getMetadata, deleteObject, listAll } from '@angular/fire/storage';
|
|
35
|
+
import { HttpClient, provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
36
|
+
import { tap, catchError, switchMap, finalize, filter as filter$1, take } from 'rxjs/operators';
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
39
|
* val-avatar
|
|
@@ -18280,6 +18282,13 @@ class DataTableComponent {
|
|
|
18280
18282
|
[class.hoverable]="props.hoverable !== false"
|
|
18281
18283
|
[class.sticky-header]="props.stickyHeader"
|
|
18282
18284
|
[class.responsive-cards]="props.responsiveMode === 'cards'"
|
|
18285
|
+
[class.elevation-none]="props.elevation === 'none'"
|
|
18286
|
+
[class.elevation-low]="props.elevation === 'low'"
|
|
18287
|
+
[class.elevation-high]="props.elevation === 'high'"
|
|
18288
|
+
[class.header-gradient]="props.headerGradient"
|
|
18289
|
+
[class.checkbox-circular]="props.checkboxStyle === 'circular'"
|
|
18290
|
+
[class.row-highlight-border]="props.rowHighlightStyle === 'border-left'"
|
|
18291
|
+
[class.row-highlight-both]="props.rowHighlightStyle === 'both'"
|
|
18283
18292
|
[style.--max-height]="props.maxHeight"
|
|
18284
18293
|
>
|
|
18285
18294
|
<!-- Toolbar slot -->
|
|
@@ -18602,7 +18611,7 @@ class DataTableComponent {
|
|
|
18602
18611
|
</div>
|
|
18603
18612
|
}
|
|
18604
18613
|
</div>
|
|
18605
|
-
`, isInline: true, styles: [":host{display:block}.data-table-container{position:relative;background:var(--ion-background-color);border-radius:12px;box-shadow:0 1px 3px #00000014,0 1px 2px #0000000f;border:1px solid var(--ion-color-light-shade);overflow:hidden}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;z-index:10}.loading-message{font-size:14px;color:var(--ion-color-medium-shade)}.table-wrapper{overflow-x:auto;max-height:var(--max-height)}.table-wrapper.is-loading{opacity:.6;pointer-events:none}table{width:100%;border-collapse:collapse;font-size:14px}th,td{padding:12px 16px;text-align:left;vertical-align:middle}thead{background:var(--ion-color-light)}thead th{font-weight:500;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--ion-color-medium-shade);white-space:nowrap;position:relative;border-bottom:2px solid var(--ion-color-light-shade)}thead th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s ease}thead th.sortable:hover{background:var(--ion-color-light-tint)}thead th.sorted{color:var(--ion-color-primary);background:transparent}.header-content{display:flex;align-items:center;gap:8px}.sort-icons{display:flex;flex-direction:column;gap:0}.sort-icons ion-icon{font-size:12px;color:var(--ion-color-medium);margin:-2px 0}.sort-icons ion-icon.active{color:var(--ion-color-primary)}tbody tr{border-bottom:1px solid var(--ion-color-light-tint);transition:background .15s ease}tbody tr:last-child{border-bottom:none}tbody tr.clickable{cursor:pointer}tbody tr.selected{background:rgba(var(--ion-color-primary-rgb),.08)}tbody td{color:var(--ion-color-dark);font-size:14px}.align-left{text-align:left}.align-center{text-align:center}.align-right{text-align:right}.selection-cell{width:48px;text-align:center;padding:8px}.selection-cell ion-checkbox{margin:0}.selection-cell input[type=radio]{width:18px;height:18px;cursor:pointer}.row-number-cell{width:60px;text-align:center;color:var(--ion-color-medium);font-size:12px}.actions-cell{text-align:center;white-space:nowrap}.sticky-start{position:sticky;left:0;background:inherit;z-index:1}.sticky-end{position:sticky;right:0;background:inherit;z-index:1}.empty-row td{padding:48px 16px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:var(--ion-color-medium)}.empty-icon{font-size:48px;margin-bottom:16px;opacity:.5}.empty-title{font-size:16px;font-weight:600;margin:0 0 8px;color:var(--ion-color-dark)}.empty-description{font-size:14px;margin:0;max-width:300px}.pagination-container{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-top:1px solid var(--ion-color-light-shade);background:var(--ion-color-light);flex-wrap:wrap;gap:12px}.pagination-info{display:flex;align-items:center;gap:16px;font-size:13px;color:var(--ion-color-medium-shade)}.page-size-select{--padding-start: 8px;--padding-end: 8px;font-size:13px;min-width:120px}.pagination-controls{display:flex;align-items:center;gap:4px}.pagination-controls ion-button{--padding-start: 8px;--padding-end: 8px}.page-indicator{padding:0 12px;font-size:13px;color:var(--ion-color-dark);font-weight:500}.bordered table,.bordered th,.bordered td{border:1px solid var(--ion-color-medium-tint)}.striped tbody tr:nth-child(2n){background:var(--ion-color-light-shade)}.striped tbody tr:nth-child(2n).selected{background:var(--ion-color-primary-tint)}.hoverable tbody tr:hover:not(.empty-row){background:var(--ion-color-light-tint)}.hoverable tbody tr:hover:not(.empty-row).selected{background:rgba(var(--ion-color-primary-rgb),.12)}.sticky-header .table-wrapper{overflow-y:auto}.sticky-header thead{position:sticky;top:0;z-index:5}.size-small th,.size-small td{padding:8px 12px;font-size:12px}.size-small .selection-cell{width:40px;padding:6px}.size-small .row-number-cell{width:48px;font-size:11px}.size-small .pagination-container{padding:8px 12px}.size-small .pagination-info,.size-small .page-indicator{font-size:12px}.size-large th,.size-large td{padding:16px 20px;font-size:15px}.size-large .selection-cell{width:56px;padding:12px}.size-large .row-number-cell{width:72px;font-size:14px}.size-large .pagination-container{padding:16px 20px}.size-large .pagination-info,.size-large .page-indicator{font-size:14px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media (max-width: 768px){.pagination-container{flex-direction:column;align-items:stretch}.pagination-info{justify-content:space-between}.pagination-controls{justify-content:center}}.table-toolbar{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px;border-bottom:1px solid var(--ion-color-light-shade);background:var(--ion-background-color);flex-wrap:wrap}.table-toolbar:empty{display:none}@media (max-width: 576px){.table-toolbar{flex-direction:column;align-items:stretch}}.status-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:9999px;font-size:12px;font-weight:500;white-space:nowrap}.status-badge--success{background:rgba(var(--ion-color-success-rgb),.15);color:var(--ion-color-success-shade)}.status-badge--warning{background:rgba(var(--ion-color-warning-rgb),.15);color:var(--ion-color-warning-shade)}.status-badge--danger{background:rgba(var(--ion-color-danger-rgb),.15);color:var(--ion-color-danger-shade)}.status-badge--primary{background:rgba(var(--ion-color-primary-rgb),.15);color:var(--ion-color-primary-shade)}.status-badge--secondary{background:rgba(var(--ion-color-secondary-rgb),.15);color:var(--ion-color-secondary-shade)}.status-badge--medium{background:rgba(var(--ion-color-medium-rgb),.15);color:var(--ion-color-medium-shade)}.status-badge--tertiary{background:rgba(var(--ion-color-tertiary-rgb),.15);color:var(--ion-color-tertiary-shade)}.action-buttons{display:flex;gap:4px;justify-content:center}.action-buttons ion-button{--padding-start: 6px;--padding-end: 6px;margin:0}.action-buttons ion-button ion-icon{font-size:18px}.card-list{display:none;padding:8px;gap:12px}.data-card{background:var(--ion-background-color);border:1px solid var(--ion-color-light-shade);border-radius:12px;padding:16px;margin-bottom:12px}.data-card.selected{border-color:var(--ion-color-primary);background:rgba(var(--ion-color-primary-rgb),.04)}.data-card.clickable{cursor:pointer;transition:box-shadow .15s ease,transform .15s ease}.data-card.clickable:hover{box-shadow:0 4px 12px #0000001a;transform:translateY(-2px)}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--ion-color-light-shade)}.card-selection{display:flex;align-items:center}.card-body{display:flex;flex-direction:column;gap:8px}.card-field{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.card-field__label{font-size:12px;font-weight:500;text-transform:uppercase;letter-spacing:.5px;color:var(--ion-color-medium-shade);flex-shrink:0}.card-field__value{font-size:14px;color:var(--ion-color-dark);text-align:right;word-break:break-word}.card-actions{display:flex;justify-content:flex-end;margin-top:12px;padding-top:12px;border-top:1px solid var(--ion-color-light-shade)}@media (max-width: 768px){.responsive-cards .table-wrapper{display:none}.responsive-cards .card-list{display:block}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { 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: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
18614
|
+
`, isInline: true, styles: [":host{display:block}.data-table-container{--dt-elevation-none: none;--dt-elevation-low: 0 1px 3px rgba(0, 0, 0, .08), 0 1px 2px rgba(0, 0, 0, .04);--dt-elevation-medium: 0 4px 6px -1px rgba(0, 0, 0, .1), 0 2px 4px -1px rgba(0, 0, 0, .06);--dt-elevation-high: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);--dt-primary: var(--ion-color-primary, #4f46e5);--dt-primary-rgb: var(--ion-color-primary-rgb, 79, 70, 229);--dt-surface: var(--ion-background-color, #ffffff);--dt-surface-variant: #f8fafc;--dt-border: #e2e8f0;--dt-text-primary: #1e293b;--dt-text-secondary: #64748b;--dt-text-muted: #94a3b8;--dt-gradient-start: #6366f1;--dt-gradient-end: #8b5cf6;--dt-border-radius: 16px;--dt-cell-padding-x: 16px;--dt-cell-padding-y: 14px;--dt-header-padding-y: 12px}.data-table-container{position:relative;background:var(--dt-surface);border-radius:var(--dt-border-radius);box-shadow:var(--dt-elevation-medium);border:1px solid var(--dt-border);overflow:hidden}.data-table-container.elevation-none{box-shadow:var(--dt-elevation-none)}.data-table-container.elevation-low{box-shadow:var(--dt-elevation-low)}.data-table-container.elevation-high{box-shadow:var(--dt-elevation-high)}.data-table-container.header-gradient:before{content:\"\";position:absolute;top:0;left:0;right:0;height:4px;background:linear-gradient(90deg,var(--dt-gradient-start),var(--dt-gradient-end));border-radius:var(--dt-border-radius) var(--dt-border-radius) 0 0;z-index:2}.loading-overlay{position:absolute;inset:0;background:#ffffffe6;backdrop-filter:blur(2px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;z-index:10}.loading-overlay ion-spinner{--color: var(--dt-primary);width:32px;height:32px}.loading-message{font-size:14px;font-weight:500;color:var(--dt-text-secondary)}.table-wrapper{overflow-x:auto;max-height:var(--max-height)}.table-wrapper.is-loading{opacity:.5;pointer-events:none}.table-wrapper::-webkit-scrollbar{height:8px;width:8px}.table-wrapper::-webkit-scrollbar-track{background:var(--dt-surface-variant);border-radius:4px}.table-wrapper::-webkit-scrollbar-thumb{background:var(--dt-border);border-radius:4px}.table-wrapper::-webkit-scrollbar-thumb:hover{background:var(--dt-text-muted)}table{width:100%;border-collapse:collapse;font-size:14px}th,td{padding:var(--dt-cell-padding-y) var(--dt-cell-padding-x);text-align:left;vertical-align:middle}thead{background:var(--dt-surface-variant)}thead th{font-weight:600;font-size:13px;color:var(--dt-text-secondary);white-space:nowrap;position:relative;border-bottom:1px solid var(--dt-border);padding:var(--dt-header-padding-y) var(--dt-cell-padding-x);transition:all .2s ease}thead th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}thead th.sortable:hover{background:#00000008;color:var(--dt-text-primary)}thead th.sorted{color:var(--dt-primary);background:rgba(var(--dt-primary-rgb),.04)}.header-content{display:flex;align-items:center;gap:8px}.sort-icons{display:flex;flex-direction:column;gap:0;opacity:.4;transition:opacity .2s ease}.sortable:hover .sort-icons{opacity:.7}.sorted .sort-icons{opacity:1}.sort-icons ion-icon{font-size:12px;color:var(--dt-text-muted);margin:-3px 0;transition:all .2s ease}.sort-icons ion-icon.active{color:var(--dt-primary);transform:scale(1.2)}tbody tr{border-bottom:1px solid var(--dt-border);transition:all .2s ease;position:relative}tbody tr:last-child{border-bottom:none}tbody tr.clickable{cursor:pointer}tbody tr.selected{background:rgba(var(--dt-primary-rgb),.06)}tbody tr.selected:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--dt-primary)}tbody td{color:var(--dt-text-primary);font-size:14px}.row-highlight-border tbody tr.selected{background:transparent}.row-highlight-border tbody tr.selected:before{width:4px}.row-highlight-both tbody tr.selected:before{width:4px}.hoverable tbody tr:hover:not(.empty-row){background:var(--dt-surface-variant)}.hoverable tbody tr:hover:not(.empty-row).selected{background:rgba(var(--dt-primary-rgb),.08)}.align-left{text-align:left}.align-center{text-align:center}.align-right{text-align:right}.selection-cell{width:52px;text-align:center;padding:8px 12px}.selection-cell ion-checkbox{--size: 20px;--border-radius: 50%;--border-width: 2px;--border-color: var(--dt-border);--border-color-checked: var(--dt-primary);--background: transparent;--background-checked: var(--dt-primary);--checkmark-color: white;--checkmark-width: 2px;margin:0;transition:transform .15s ease}.selection-cell ion-checkbox:hover{transform:scale(1.1)}.selection-cell ion-checkbox::part(container){border-radius:50%}.selection-cell input[type=radio]{width:20px;height:20px;cursor:pointer;accent-color:var(--dt-primary)}.checkbox-circular .selection-cell ion-checkbox{--border-radius: 50%}.checkbox-circular .selection-cell ion-checkbox::part(container){border-radius:50%}.row-number-cell{width:60px;text-align:center;color:var(--dt-text-muted);font-size:12px;font-weight:500}.actions-cell{text-align:center;white-space:nowrap}.sticky-start{position:sticky;left:0;background:inherit;z-index:1;box-shadow:2px 0 4px #0000000d}.sticky-end{position:sticky;right:0;background:inherit;z-index:1;box-shadow:-2px 0 4px #0000000d}.empty-row td{padding:64px 16px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:var(--dt-text-secondary)}.empty-icon{font-size:56px;margin-bottom:20px;opacity:.4;color:var(--dt-text-muted)}.empty-title{font-size:18px;font-weight:600;margin:0 0 8px;color:var(--dt-text-primary)}.empty-description{font-size:14px;margin:0;max-width:320px;color:var(--dt-text-secondary);line-height:1.5}.pagination-container{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-top:1px solid var(--dt-border);background:var(--dt-surface);flex-wrap:wrap;gap:16px}.pagination-info{display:flex;align-items:center;gap:20px;font-size:14px;color:var(--dt-text-secondary)}.pagination-info span{font-weight:500}.page-size-select{--padding-start: 12px;--padding-end: 12px;font-size:14px;min-width:140px;--background: var(--dt-surface-variant);--border-radius: 8px}.pagination-controls{display:flex;align-items:center;gap:8px}.pagination-controls ion-button{--padding-start: 10px;--padding-end: 10px;--border-radius: 8px;--background: var(--dt-surface-variant);--background-hover: var(--dt-border);--color: var(--dt-text-secondary);--color-hover: var(--dt-text-primary);margin:0;height:36px;min-width:36px}.pagination-controls ion-button:disabled{opacity:.4}.pagination-controls ion-button ion-icon{font-size:16px}.page-indicator{padding:0 16px;font-size:14px;color:var(--dt-text-primary);font-weight:600;background:var(--dt-surface-variant);border-radius:8px;height:36px;display:flex;align-items:center;min-width:80px;justify-content:center}.bordered th,.bordered td{border:1px solid var(--dt-border)}.striped tbody tr:nth-child(2n){background:var(--dt-surface-variant)}.striped tbody tr:nth-child(2n).selected{background:rgba(var(--dt-primary-rgb),.08)}.sticky-header .table-wrapper{overflow-y:auto}.sticky-header thead{position:sticky;top:0;z-index:5;box-shadow:0 1px 0 var(--dt-border)}.size-small{--dt-cell-padding-x: 12px;--dt-cell-padding-y: 10px;--dt-header-padding-y: 10px}.size-small th,.size-small td{font-size:13px}.size-small thead th{font-size:12px}.size-small .selection-cell{width:44px;padding:6px 8px}.size-small .selection-cell ion-checkbox{--size: 18px}.size-small .row-number-cell{width:48px;font-size:11px}.size-small .pagination-container{padding:12px 16px}.size-small .pagination-info,.size-small .page-indicator{font-size:13px}.size-small .pagination-controls ion-button{height:32px;min-width:32px}.size-small .page-indicator{height:32px;min-width:70px}.size-large{--dt-cell-padding-x: 20px;--dt-cell-padding-y: 18px;--dt-header-padding-y: 16px}.size-large th,.size-large td{font-size:15px}.size-large thead th{font-size:14px}.size-large .selection-cell{width:60px;padding:12px 16px}.size-large .selection-cell ion-checkbox{--size: 24px}.size-large .row-number-cell{width:72px;font-size:14px}.size-large .pagination-container{padding:20px 24px}.size-large .pagination-info,.size-large .page-indicator{font-size:15px}.size-large .pagination-controls ion-button{height:40px;min-width:40px}.size-large .page-indicator{height:40px;min-width:90px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media (max-width: 768px){.data-table-container{--dt-border-radius: 12px}.pagination-container{flex-direction:column;align-items:stretch;gap:12px}.pagination-info{justify-content:space-between}.pagination-controls{justify-content:center}}.table-toolbar{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:20px;border-bottom:1px solid var(--dt-border);background:var(--dt-surface);flex-wrap:wrap}.table-toolbar:empty{display:none}@media (max-width: 576px){.table-toolbar{flex-direction:column;align-items:stretch;padding:16px}}.status-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:9999px;font-size:12px;font-weight:600;white-space:nowrap;letter-spacing:.025em}.status-badge--success{background:#dcfce7;color:#15803d}.status-badge--warning{background:#fef3c7;color:#b45309}.status-badge--danger{background:#fee2e2;color:#dc2626}.status-badge--primary{background:#e0e7ff;color:#4338ca}.status-badge--secondary{background:#f1f5f9;color:#475569}.status-badge--medium{background:#f1f5f9;color:#64748b}.status-badge--tertiary{background:#f3e8ff;color:#7c3aed}.status-badge--filled.status-badge--success{background:#22c55e;color:#fff}.status-badge--filled.status-badge--warning{background:#f59e0b;color:#fff}.status-badge--filled.status-badge--danger{background:#ef4444;color:#fff}.status-badge--filled.status-badge--primary{background:var(--dt-primary);color:#fff}.status-badge--outlined{background:transparent;border:1.5px solid currentColor}.status-badge--outlined.status-badge--success{color:#16a34a}.status-badge--outlined.status-badge--warning{color:#d97706}.status-badge--outlined.status-badge--danger{color:#dc2626}.status-badge--outlined.status-badge--primary{color:var(--dt-primary)}.action-buttons{display:flex;gap:6px;justify-content:center}.action-buttons ion-button{--padding-start: 8px;--padding-end: 8px;--border-radius: 8px;margin:0}.action-buttons ion-button ion-icon{font-size:18px}.card-list{display:none;padding:12px;gap:12px}.data-card{background:var(--dt-surface);border:1px solid var(--dt-border);border-radius:12px;padding:16px;margin-bottom:12px;transition:all .2s ease}.data-card.selected{border-color:var(--dt-primary);background:rgba(var(--dt-primary-rgb),.04);box-shadow:0 0 0 1px var(--dt-primary)}.data-card.clickable{cursor:pointer}.data-card.clickable:hover{box-shadow:var(--dt-elevation-medium);transform:translateY(-2px)}.data-card.clickable:active{transform:translateY(0)}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid var(--dt-border)}.card-selection{display:flex;align-items:center}.card-body{display:flex;flex-direction:column;gap:12px}.card-field{display:flex;justify-content:space-between;align-items:flex-start;gap:16px}.card-field__label{font-size:12px;font-weight:600;color:var(--dt-text-muted);flex-shrink:0}.card-field__value{font-size:14px;color:var(--dt-text-primary);text-align:right;word-break:break-word;font-weight:500}.card-actions{display:flex;justify-content:flex-end;margin-top:16px;padding-top:12px;border-top:1px solid var(--dt-border)}@media (max-width: 768px){.responsive-cards .table-wrapper{display:none}.responsive-cards .card-list{display:block}}@media (prefers-color-scheme: dark){.data-table-container{--dt-surface: #1e293b;--dt-surface-variant: #334155;--dt-border: #475569;--dt-text-primary: #f1f5f9;--dt-text-secondary: #94a3b8;--dt-text-muted: #64748b}.loading-overlay{background:#1e293be6}.status-badge--success{background:#22c55e33;color:#4ade80}.status-badge--warning{background:#f59e0b33;color:#fbbf24}.status-badge--danger{background:#ef444433;color:#f87171}.status-badge--primary{background:#6366f133;color:#a5b4fc}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { 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: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
18606
18615
|
}
|
|
18607
18616
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DataTableComponent, decorators: [{
|
|
18608
18617
|
type: Component,
|
|
@@ -18627,6 +18636,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
18627
18636
|
[class.hoverable]="props.hoverable !== false"
|
|
18628
18637
|
[class.sticky-header]="props.stickyHeader"
|
|
18629
18638
|
[class.responsive-cards]="props.responsiveMode === 'cards'"
|
|
18639
|
+
[class.elevation-none]="props.elevation === 'none'"
|
|
18640
|
+
[class.elevation-low]="props.elevation === 'low'"
|
|
18641
|
+
[class.elevation-high]="props.elevation === 'high'"
|
|
18642
|
+
[class.header-gradient]="props.headerGradient"
|
|
18643
|
+
[class.checkbox-circular]="props.checkboxStyle === 'circular'"
|
|
18644
|
+
[class.row-highlight-border]="props.rowHighlightStyle === 'border-left'"
|
|
18645
|
+
[class.row-highlight-both]="props.rowHighlightStyle === 'both'"
|
|
18630
18646
|
[style.--max-height]="props.maxHeight"
|
|
18631
18647
|
>
|
|
18632
18648
|
<!-- Toolbar slot -->
|
|
@@ -18949,7 +18965,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
18949
18965
|
</div>
|
|
18950
18966
|
}
|
|
18951
18967
|
</div>
|
|
18952
|
-
`, styles: [":host{display:block}.data-table-container{position:relative;background:var(--ion-background-color);border-radius:12px;box-shadow:0 1px 3px #00000014,0 1px 2px #0000000f;border:1px solid var(--ion-color-light-shade);overflow:hidden}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;z-index:10}.loading-message{font-size:14px;color:var(--ion-color-medium-shade)}.table-wrapper{overflow-x:auto;max-height:var(--max-height)}.table-wrapper.is-loading{opacity:.6;pointer-events:none}table{width:100%;border-collapse:collapse;font-size:14px}th,td{padding:12px 16px;text-align:left;vertical-align:middle}thead{background:var(--ion-color-light)}thead th{font-weight:500;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--ion-color-medium-shade);white-space:nowrap;position:relative;border-bottom:2px solid var(--ion-color-light-shade)}thead th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s ease}thead th.sortable:hover{background:var(--ion-color-light-tint)}thead th.sorted{color:var(--ion-color-primary);background:transparent}.header-content{display:flex;align-items:center;gap:8px}.sort-icons{display:flex;flex-direction:column;gap:0}.sort-icons ion-icon{font-size:12px;color:var(--ion-color-medium);margin:-2px 0}.sort-icons ion-icon.active{color:var(--ion-color-primary)}tbody tr{border-bottom:1px solid var(--ion-color-light-tint);transition:background .15s ease}tbody tr:last-child{border-bottom:none}tbody tr.clickable{cursor:pointer}tbody tr.selected{background:rgba(var(--ion-color-primary-rgb),.08)}tbody td{color:var(--ion-color-dark);font-size:14px}.align-left{text-align:left}.align-center{text-align:center}.align-right{text-align:right}.selection-cell{width:48px;text-align:center;padding:8px}.selection-cell ion-checkbox{margin:0}.selection-cell input[type=radio]{width:18px;height:18px;cursor:pointer}.row-number-cell{width:60px;text-align:center;color:var(--ion-color-medium);font-size:12px}.actions-cell{text-align:center;white-space:nowrap}.sticky-start{position:sticky;left:0;background:inherit;z-index:1}.sticky-end{position:sticky;right:0;background:inherit;z-index:1}.empty-row td{padding:48px 16px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:var(--ion-color-medium)}.empty-icon{font-size:48px;margin-bottom:16px;opacity:.5}.empty-title{font-size:16px;font-weight:600;margin:0 0 8px;color:var(--ion-color-dark)}.empty-description{font-size:14px;margin:0;max-width:300px}.pagination-container{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-top:1px solid var(--ion-color-light-shade);background:var(--ion-color-light);flex-wrap:wrap;gap:12px}.pagination-info{display:flex;align-items:center;gap:16px;font-size:13px;color:var(--ion-color-medium-shade)}.page-size-select{--padding-start: 8px;--padding-end: 8px;font-size:13px;min-width:120px}.pagination-controls{display:flex;align-items:center;gap:4px}.pagination-controls ion-button{--padding-start: 8px;--padding-end: 8px}.page-indicator{padding:0 12px;font-size:13px;color:var(--ion-color-dark);font-weight:500}.bordered table,.bordered th,.bordered td{border:1px solid var(--ion-color-medium-tint)}.striped tbody tr:nth-child(2n){background:var(--ion-color-light-shade)}.striped tbody tr:nth-child(2n).selected{background:var(--ion-color-primary-tint)}.hoverable tbody tr:hover:not(.empty-row){background:var(--ion-color-light-tint)}.hoverable tbody tr:hover:not(.empty-row).selected{background:rgba(var(--ion-color-primary-rgb),.12)}.sticky-header .table-wrapper{overflow-y:auto}.sticky-header thead{position:sticky;top:0;z-index:5}.size-small th,.size-small td{padding:8px 12px;font-size:12px}.size-small .selection-cell{width:40px;padding:6px}.size-small .row-number-cell{width:48px;font-size:11px}.size-small .pagination-container{padding:8px 12px}.size-small .pagination-info,.size-small .page-indicator{font-size:12px}.size-large th,.size-large td{padding:16px 20px;font-size:15px}.size-large .selection-cell{width:56px;padding:12px}.size-large .row-number-cell{width:72px;font-size:14px}.size-large .pagination-container{padding:16px 20px}.size-large .pagination-info,.size-large .page-indicator{font-size:14px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media (max-width: 768px){.pagination-container{flex-direction:column;align-items:stretch}.pagination-info{justify-content:space-between}.pagination-controls{justify-content:center}}.table-toolbar{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px;border-bottom:1px solid var(--ion-color-light-shade);background:var(--ion-background-color);flex-wrap:wrap}.table-toolbar:empty{display:none}@media (max-width: 576px){.table-toolbar{flex-direction:column;align-items:stretch}}.status-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:9999px;font-size:12px;font-weight:500;white-space:nowrap}.status-badge--success{background:rgba(var(--ion-color-success-rgb),.15);color:var(--ion-color-success-shade)}.status-badge--warning{background:rgba(var(--ion-color-warning-rgb),.15);color:var(--ion-color-warning-shade)}.status-badge--danger{background:rgba(var(--ion-color-danger-rgb),.15);color:var(--ion-color-danger-shade)}.status-badge--primary{background:rgba(var(--ion-color-primary-rgb),.15);color:var(--ion-color-primary-shade)}.status-badge--secondary{background:rgba(var(--ion-color-secondary-rgb),.15);color:var(--ion-color-secondary-shade)}.status-badge--medium{background:rgba(var(--ion-color-medium-rgb),.15);color:var(--ion-color-medium-shade)}.status-badge--tertiary{background:rgba(var(--ion-color-tertiary-rgb),.15);color:var(--ion-color-tertiary-shade)}.action-buttons{display:flex;gap:4px;justify-content:center}.action-buttons ion-button{--padding-start: 6px;--padding-end: 6px;margin:0}.action-buttons ion-button ion-icon{font-size:18px}.card-list{display:none;padding:8px;gap:12px}.data-card{background:var(--ion-background-color);border:1px solid var(--ion-color-light-shade);border-radius:12px;padding:16px;margin-bottom:12px}.data-card.selected{border-color:var(--ion-color-primary);background:rgba(var(--ion-color-primary-rgb),.04)}.data-card.clickable{cursor:pointer;transition:box-shadow .15s ease,transform .15s ease}.data-card.clickable:hover{box-shadow:0 4px 12px #0000001a;transform:translateY(-2px)}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--ion-color-light-shade)}.card-selection{display:flex;align-items:center}.card-body{display:flex;flex-direction:column;gap:8px}.card-field{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.card-field__label{font-size:12px;font-weight:500;text-transform:uppercase;letter-spacing:.5px;color:var(--ion-color-medium-shade);flex-shrink:0}.card-field__value{font-size:14px;color:var(--ion-color-dark);text-align:right;word-break:break-word}.card-actions{display:flex;justify-content:flex-end;margin-top:12px;padding-top:12px;border-top:1px solid var(--ion-color-light-shade)}@media (max-width: 768px){.responsive-cards .table-wrapper{display:none}.responsive-cards .card-list{display:block}}\n"] }]
|
|
18968
|
+
`, styles: [":host{display:block}.data-table-container{--dt-elevation-none: none;--dt-elevation-low: 0 1px 3px rgba(0, 0, 0, .08), 0 1px 2px rgba(0, 0, 0, .04);--dt-elevation-medium: 0 4px 6px -1px rgba(0, 0, 0, .1), 0 2px 4px -1px rgba(0, 0, 0, .06);--dt-elevation-high: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);--dt-primary: var(--ion-color-primary, #4f46e5);--dt-primary-rgb: var(--ion-color-primary-rgb, 79, 70, 229);--dt-surface: var(--ion-background-color, #ffffff);--dt-surface-variant: #f8fafc;--dt-border: #e2e8f0;--dt-text-primary: #1e293b;--dt-text-secondary: #64748b;--dt-text-muted: #94a3b8;--dt-gradient-start: #6366f1;--dt-gradient-end: #8b5cf6;--dt-border-radius: 16px;--dt-cell-padding-x: 16px;--dt-cell-padding-y: 14px;--dt-header-padding-y: 12px}.data-table-container{position:relative;background:var(--dt-surface);border-radius:var(--dt-border-radius);box-shadow:var(--dt-elevation-medium);border:1px solid var(--dt-border);overflow:hidden}.data-table-container.elevation-none{box-shadow:var(--dt-elevation-none)}.data-table-container.elevation-low{box-shadow:var(--dt-elevation-low)}.data-table-container.elevation-high{box-shadow:var(--dt-elevation-high)}.data-table-container.header-gradient:before{content:\"\";position:absolute;top:0;left:0;right:0;height:4px;background:linear-gradient(90deg,var(--dt-gradient-start),var(--dt-gradient-end));border-radius:var(--dt-border-radius) var(--dt-border-radius) 0 0;z-index:2}.loading-overlay{position:absolute;inset:0;background:#ffffffe6;backdrop-filter:blur(2px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;z-index:10}.loading-overlay ion-spinner{--color: var(--dt-primary);width:32px;height:32px}.loading-message{font-size:14px;font-weight:500;color:var(--dt-text-secondary)}.table-wrapper{overflow-x:auto;max-height:var(--max-height)}.table-wrapper.is-loading{opacity:.5;pointer-events:none}.table-wrapper::-webkit-scrollbar{height:8px;width:8px}.table-wrapper::-webkit-scrollbar-track{background:var(--dt-surface-variant);border-radius:4px}.table-wrapper::-webkit-scrollbar-thumb{background:var(--dt-border);border-radius:4px}.table-wrapper::-webkit-scrollbar-thumb:hover{background:var(--dt-text-muted)}table{width:100%;border-collapse:collapse;font-size:14px}th,td{padding:var(--dt-cell-padding-y) var(--dt-cell-padding-x);text-align:left;vertical-align:middle}thead{background:var(--dt-surface-variant)}thead th{font-weight:600;font-size:13px;color:var(--dt-text-secondary);white-space:nowrap;position:relative;border-bottom:1px solid var(--dt-border);padding:var(--dt-header-padding-y) var(--dt-cell-padding-x);transition:all .2s ease}thead th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}thead th.sortable:hover{background:#00000008;color:var(--dt-text-primary)}thead th.sorted{color:var(--dt-primary);background:rgba(var(--dt-primary-rgb),.04)}.header-content{display:flex;align-items:center;gap:8px}.sort-icons{display:flex;flex-direction:column;gap:0;opacity:.4;transition:opacity .2s ease}.sortable:hover .sort-icons{opacity:.7}.sorted .sort-icons{opacity:1}.sort-icons ion-icon{font-size:12px;color:var(--dt-text-muted);margin:-3px 0;transition:all .2s ease}.sort-icons ion-icon.active{color:var(--dt-primary);transform:scale(1.2)}tbody tr{border-bottom:1px solid var(--dt-border);transition:all .2s ease;position:relative}tbody tr:last-child{border-bottom:none}tbody tr.clickable{cursor:pointer}tbody tr.selected{background:rgba(var(--dt-primary-rgb),.06)}tbody tr.selected:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--dt-primary)}tbody td{color:var(--dt-text-primary);font-size:14px}.row-highlight-border tbody tr.selected{background:transparent}.row-highlight-border tbody tr.selected:before{width:4px}.row-highlight-both tbody tr.selected:before{width:4px}.hoverable tbody tr:hover:not(.empty-row){background:var(--dt-surface-variant)}.hoverable tbody tr:hover:not(.empty-row).selected{background:rgba(var(--dt-primary-rgb),.08)}.align-left{text-align:left}.align-center{text-align:center}.align-right{text-align:right}.selection-cell{width:52px;text-align:center;padding:8px 12px}.selection-cell ion-checkbox{--size: 20px;--border-radius: 50%;--border-width: 2px;--border-color: var(--dt-border);--border-color-checked: var(--dt-primary);--background: transparent;--background-checked: var(--dt-primary);--checkmark-color: white;--checkmark-width: 2px;margin:0;transition:transform .15s ease}.selection-cell ion-checkbox:hover{transform:scale(1.1)}.selection-cell ion-checkbox::part(container){border-radius:50%}.selection-cell input[type=radio]{width:20px;height:20px;cursor:pointer;accent-color:var(--dt-primary)}.checkbox-circular .selection-cell ion-checkbox{--border-radius: 50%}.checkbox-circular .selection-cell ion-checkbox::part(container){border-radius:50%}.row-number-cell{width:60px;text-align:center;color:var(--dt-text-muted);font-size:12px;font-weight:500}.actions-cell{text-align:center;white-space:nowrap}.sticky-start{position:sticky;left:0;background:inherit;z-index:1;box-shadow:2px 0 4px #0000000d}.sticky-end{position:sticky;right:0;background:inherit;z-index:1;box-shadow:-2px 0 4px #0000000d}.empty-row td{padding:64px 16px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:var(--dt-text-secondary)}.empty-icon{font-size:56px;margin-bottom:20px;opacity:.4;color:var(--dt-text-muted)}.empty-title{font-size:18px;font-weight:600;margin:0 0 8px;color:var(--dt-text-primary)}.empty-description{font-size:14px;margin:0;max-width:320px;color:var(--dt-text-secondary);line-height:1.5}.pagination-container{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-top:1px solid var(--dt-border);background:var(--dt-surface);flex-wrap:wrap;gap:16px}.pagination-info{display:flex;align-items:center;gap:20px;font-size:14px;color:var(--dt-text-secondary)}.pagination-info span{font-weight:500}.page-size-select{--padding-start: 12px;--padding-end: 12px;font-size:14px;min-width:140px;--background: var(--dt-surface-variant);--border-radius: 8px}.pagination-controls{display:flex;align-items:center;gap:8px}.pagination-controls ion-button{--padding-start: 10px;--padding-end: 10px;--border-radius: 8px;--background: var(--dt-surface-variant);--background-hover: var(--dt-border);--color: var(--dt-text-secondary);--color-hover: var(--dt-text-primary);margin:0;height:36px;min-width:36px}.pagination-controls ion-button:disabled{opacity:.4}.pagination-controls ion-button ion-icon{font-size:16px}.page-indicator{padding:0 16px;font-size:14px;color:var(--dt-text-primary);font-weight:600;background:var(--dt-surface-variant);border-radius:8px;height:36px;display:flex;align-items:center;min-width:80px;justify-content:center}.bordered th,.bordered td{border:1px solid var(--dt-border)}.striped tbody tr:nth-child(2n){background:var(--dt-surface-variant)}.striped tbody tr:nth-child(2n).selected{background:rgba(var(--dt-primary-rgb),.08)}.sticky-header .table-wrapper{overflow-y:auto}.sticky-header thead{position:sticky;top:0;z-index:5;box-shadow:0 1px 0 var(--dt-border)}.size-small{--dt-cell-padding-x: 12px;--dt-cell-padding-y: 10px;--dt-header-padding-y: 10px}.size-small th,.size-small td{font-size:13px}.size-small thead th{font-size:12px}.size-small .selection-cell{width:44px;padding:6px 8px}.size-small .selection-cell ion-checkbox{--size: 18px}.size-small .row-number-cell{width:48px;font-size:11px}.size-small .pagination-container{padding:12px 16px}.size-small .pagination-info,.size-small .page-indicator{font-size:13px}.size-small .pagination-controls ion-button{height:32px;min-width:32px}.size-small .page-indicator{height:32px;min-width:70px}.size-large{--dt-cell-padding-x: 20px;--dt-cell-padding-y: 18px;--dt-header-padding-y: 16px}.size-large th,.size-large td{font-size:15px}.size-large thead th{font-size:14px}.size-large .selection-cell{width:60px;padding:12px 16px}.size-large .selection-cell ion-checkbox{--size: 24px}.size-large .row-number-cell{width:72px;font-size:14px}.size-large .pagination-container{padding:20px 24px}.size-large .pagination-info,.size-large .page-indicator{font-size:15px}.size-large .pagination-controls ion-button{height:40px;min-width:40px}.size-large .page-indicator{height:40px;min-width:90px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media (max-width: 768px){.data-table-container{--dt-border-radius: 12px}.pagination-container{flex-direction:column;align-items:stretch;gap:12px}.pagination-info{justify-content:space-between}.pagination-controls{justify-content:center}}.table-toolbar{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:20px;border-bottom:1px solid var(--dt-border);background:var(--dt-surface);flex-wrap:wrap}.table-toolbar:empty{display:none}@media (max-width: 576px){.table-toolbar{flex-direction:column;align-items:stretch;padding:16px}}.status-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:9999px;font-size:12px;font-weight:600;white-space:nowrap;letter-spacing:.025em}.status-badge--success{background:#dcfce7;color:#15803d}.status-badge--warning{background:#fef3c7;color:#b45309}.status-badge--danger{background:#fee2e2;color:#dc2626}.status-badge--primary{background:#e0e7ff;color:#4338ca}.status-badge--secondary{background:#f1f5f9;color:#475569}.status-badge--medium{background:#f1f5f9;color:#64748b}.status-badge--tertiary{background:#f3e8ff;color:#7c3aed}.status-badge--filled.status-badge--success{background:#22c55e;color:#fff}.status-badge--filled.status-badge--warning{background:#f59e0b;color:#fff}.status-badge--filled.status-badge--danger{background:#ef4444;color:#fff}.status-badge--filled.status-badge--primary{background:var(--dt-primary);color:#fff}.status-badge--outlined{background:transparent;border:1.5px solid currentColor}.status-badge--outlined.status-badge--success{color:#16a34a}.status-badge--outlined.status-badge--warning{color:#d97706}.status-badge--outlined.status-badge--danger{color:#dc2626}.status-badge--outlined.status-badge--primary{color:var(--dt-primary)}.action-buttons{display:flex;gap:6px;justify-content:center}.action-buttons ion-button{--padding-start: 8px;--padding-end: 8px;--border-radius: 8px;margin:0}.action-buttons ion-button ion-icon{font-size:18px}.card-list{display:none;padding:12px;gap:12px}.data-card{background:var(--dt-surface);border:1px solid var(--dt-border);border-radius:12px;padding:16px;margin-bottom:12px;transition:all .2s ease}.data-card.selected{border-color:var(--dt-primary);background:rgba(var(--dt-primary-rgb),.04);box-shadow:0 0 0 1px var(--dt-primary)}.data-card.clickable{cursor:pointer}.data-card.clickable:hover{box-shadow:var(--dt-elevation-medium);transform:translateY(-2px)}.data-card.clickable:active{transform:translateY(0)}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid var(--dt-border)}.card-selection{display:flex;align-items:center}.card-body{display:flex;flex-direction:column;gap:12px}.card-field{display:flex;justify-content:space-between;align-items:flex-start;gap:16px}.card-field__label{font-size:12px;font-weight:600;color:var(--dt-text-muted);flex-shrink:0}.card-field__value{font-size:14px;color:var(--dt-text-primary);text-align:right;word-break:break-word;font-weight:500}.card-actions{display:flex;justify-content:flex-end;margin-top:16px;padding-top:12px;border-top:1px solid var(--dt-border)}@media (max-width: 768px){.responsive-cards .table-wrapper{display:none}.responsive-cards .card-list{display:block}}@media (prefers-color-scheme: dark){.data-table-container{--dt-surface: #1e293b;--dt-surface-variant: #334155;--dt-border: #475569;--dt-text-primary: #f1f5f9;--dt-text-secondary: #94a3b8;--dt-text-muted: #64748b}.loading-overlay{background:#1e293be6}.status-badge--success{background:#22c55e33;color:#4ade80}.status-badge--warning{background:#f59e0b33;color:#fbbf24}.status-badge--danger{background:#ef444433;color:#f87171}.status-badge--primary{background:#6366f133;color:#a5b4fc}}\n"] }]
|
|
18953
18969
|
}], propDecorators: { props: [{
|
|
18954
18970
|
type: Input
|
|
18955
18971
|
}], rowClick: [{
|
|
@@ -20824,7 +20840,7 @@ function hasEmulators(config) {
|
|
|
20824
20840
|
* }
|
|
20825
20841
|
* ```
|
|
20826
20842
|
*/
|
|
20827
|
-
class FirebaseService {
|
|
20843
|
+
let FirebaseService$1 = class FirebaseService {
|
|
20828
20844
|
constructor() {
|
|
20829
20845
|
this.auth = inject(Auth);
|
|
20830
20846
|
this.config = inject(VALTECH_FIREBASE_CONFIG);
|
|
@@ -21058,8 +21074,8 @@ class FirebaseService {
|
|
|
21058
21074
|
}
|
|
21059
21075
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FirebaseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
21060
21076
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FirebaseService, providedIn: 'root' }); }
|
|
21061
|
-
}
|
|
21062
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FirebaseService, decorators: [{
|
|
21077
|
+
};
|
|
21078
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FirebaseService$1, decorators: [{
|
|
21063
21079
|
type: Injectable,
|
|
21064
21080
|
args: [{ providedIn: 'root' }]
|
|
21065
21081
|
}], ctorParameters: () => [] });
|
|
@@ -23273,6 +23289,1447 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
23273
23289
|
*/
|
|
23274
23290
|
// Tipos
|
|
23275
23291
|
|
|
23292
|
+
var index = /*#__PURE__*/Object.freeze({
|
|
23293
|
+
__proto__: null,
|
|
23294
|
+
FirebaseService: FirebaseService$1,
|
|
23295
|
+
FirestoreCollection: FirestoreCollection,
|
|
23296
|
+
FirestoreService: FirestoreService,
|
|
23297
|
+
MessagingService: MessagingService,
|
|
23298
|
+
QueryBuilder: QueryBuilder,
|
|
23299
|
+
StorageService: StorageService,
|
|
23300
|
+
VALTECH_FIREBASE_CONFIG: VALTECH_FIREBASE_CONFIG,
|
|
23301
|
+
buildPath: buildPath,
|
|
23302
|
+
extractPathParams: extractPathParams,
|
|
23303
|
+
getCollectionPath: getCollectionPath,
|
|
23304
|
+
getDocumentId: getDocumentId,
|
|
23305
|
+
hasEmulators: hasEmulators,
|
|
23306
|
+
isCollectionPath: isCollectionPath,
|
|
23307
|
+
isDocumentPath: isDocumentPath,
|
|
23308
|
+
isValidPath: isValidPath,
|
|
23309
|
+
joinPath: joinPath,
|
|
23310
|
+
provideValtechFirebase: provideValtechFirebase,
|
|
23311
|
+
query: query
|
|
23312
|
+
});
|
|
23313
|
+
|
|
23314
|
+
/**
|
|
23315
|
+
* Tipos e interfaces para el servicio de autenticación de Valtech.
|
|
23316
|
+
* Alineados con el backend AuthV2.
|
|
23317
|
+
*/
|
|
23318
|
+
/**
|
|
23319
|
+
* Estado inicial de autenticación.
|
|
23320
|
+
*/
|
|
23321
|
+
const INITIAL_AUTH_STATE = {
|
|
23322
|
+
isAuthenticated: false,
|
|
23323
|
+
isLoading: true,
|
|
23324
|
+
accessToken: null,
|
|
23325
|
+
refreshToken: null,
|
|
23326
|
+
userId: null,
|
|
23327
|
+
email: null,
|
|
23328
|
+
roles: [],
|
|
23329
|
+
permissions: [],
|
|
23330
|
+
isSuperAdmin: false,
|
|
23331
|
+
expiresAt: null,
|
|
23332
|
+
error: null,
|
|
23333
|
+
};
|
|
23334
|
+
/**
|
|
23335
|
+
* Estado inicial de MFA.
|
|
23336
|
+
*/
|
|
23337
|
+
const INITIAL_MFA_STATE = {
|
|
23338
|
+
required: false,
|
|
23339
|
+
mfaToken: null,
|
|
23340
|
+
method: null,
|
|
23341
|
+
};
|
|
23342
|
+
|
|
23343
|
+
/**
|
|
23344
|
+
* Servicio para manejo de estado de autenticación con Angular Signals.
|
|
23345
|
+
* Proporciona estado reactivo inmutable.
|
|
23346
|
+
*/
|
|
23347
|
+
class AuthStateService {
|
|
23348
|
+
constructor() {
|
|
23349
|
+
// Estado interno (mutable solo dentro del servicio)
|
|
23350
|
+
this._state = signal(INITIAL_AUTH_STATE);
|
|
23351
|
+
this._mfaPending = signal(INITIAL_MFA_STATE);
|
|
23352
|
+
// =============================================
|
|
23353
|
+
// Signals públicos (readonly)
|
|
23354
|
+
// =============================================
|
|
23355
|
+
/** Estado completo de autenticación */
|
|
23356
|
+
this.state = this._state.asReadonly();
|
|
23357
|
+
/** Estado de MFA pendiente */
|
|
23358
|
+
this.mfaPending = this._mfaPending.asReadonly();
|
|
23359
|
+
/** Usuario está autenticado */
|
|
23360
|
+
this.isAuthenticated = computed(() => this._state().isAuthenticated);
|
|
23361
|
+
/** Estado de carga */
|
|
23362
|
+
this.isLoading = computed(() => this._state().isLoading);
|
|
23363
|
+
/** Token de acceso */
|
|
23364
|
+
this.accessToken = computed(() => this._state().accessToken);
|
|
23365
|
+
/** Roles del usuario */
|
|
23366
|
+
this.roles = computed(() => this._state().roles);
|
|
23367
|
+
/** Permisos del usuario */
|
|
23368
|
+
this.permissions = computed(() => this._state().permissions);
|
|
23369
|
+
/** Usuario es super admin */
|
|
23370
|
+
this.isSuperAdmin = computed(() => this._state().isSuperAdmin);
|
|
23371
|
+
/** Error actual */
|
|
23372
|
+
this.error = computed(() => this._state().error);
|
|
23373
|
+
/** Información del usuario */
|
|
23374
|
+
this.user = computed(() => {
|
|
23375
|
+
const state = this._state();
|
|
23376
|
+
if (!state.isAuthenticated || !state.userId) {
|
|
23377
|
+
return null;
|
|
23378
|
+
}
|
|
23379
|
+
return {
|
|
23380
|
+
userId: state.userId,
|
|
23381
|
+
email: state.email || '',
|
|
23382
|
+
roles: state.roles,
|
|
23383
|
+
permissions: state.permissions,
|
|
23384
|
+
isSuperAdmin: state.isSuperAdmin,
|
|
23385
|
+
};
|
|
23386
|
+
});
|
|
23387
|
+
}
|
|
23388
|
+
// =============================================
|
|
23389
|
+
// Métodos de actualización
|
|
23390
|
+
// =============================================
|
|
23391
|
+
/**
|
|
23392
|
+
* Establece el estado de carga.
|
|
23393
|
+
*/
|
|
23394
|
+
setLoading(isLoading) {
|
|
23395
|
+
this._state.update((s) => ({ ...s, isLoading }));
|
|
23396
|
+
}
|
|
23397
|
+
/**
|
|
23398
|
+
* Establece el estado de autenticación exitosa.
|
|
23399
|
+
*/
|
|
23400
|
+
setAuthenticated(data) {
|
|
23401
|
+
this._state.set({
|
|
23402
|
+
isAuthenticated: true,
|
|
23403
|
+
isLoading: false,
|
|
23404
|
+
accessToken: data.accessToken,
|
|
23405
|
+
refreshToken: data.refreshToken,
|
|
23406
|
+
userId: data.userId || null,
|
|
23407
|
+
email: data.email || null,
|
|
23408
|
+
roles: data.roles,
|
|
23409
|
+
permissions: data.permissions,
|
|
23410
|
+
isSuperAdmin: data.isSuperAdmin,
|
|
23411
|
+
expiresAt: data.expiresAt,
|
|
23412
|
+
error: null,
|
|
23413
|
+
});
|
|
23414
|
+
}
|
|
23415
|
+
/**
|
|
23416
|
+
* Actualiza solo el access token (después de refresh).
|
|
23417
|
+
*/
|
|
23418
|
+
updateAccessToken(accessToken, expiresIn) {
|
|
23419
|
+
const expiresAt = Date.now() + expiresIn * 1000;
|
|
23420
|
+
this._state.update((s) => ({
|
|
23421
|
+
...s,
|
|
23422
|
+
accessToken,
|
|
23423
|
+
expiresAt,
|
|
23424
|
+
}));
|
|
23425
|
+
}
|
|
23426
|
+
/**
|
|
23427
|
+
* Actualiza los permisos.
|
|
23428
|
+
*/
|
|
23429
|
+
updatePermissions(roles, permissions, isSuperAdmin) {
|
|
23430
|
+
this._state.update((s) => ({
|
|
23431
|
+
...s,
|
|
23432
|
+
roles,
|
|
23433
|
+
permissions,
|
|
23434
|
+
isSuperAdmin,
|
|
23435
|
+
}));
|
|
23436
|
+
}
|
|
23437
|
+
/**
|
|
23438
|
+
* Establece un error de autenticación.
|
|
23439
|
+
*/
|
|
23440
|
+
setError(error) {
|
|
23441
|
+
this._state.update((s) => ({
|
|
23442
|
+
...s,
|
|
23443
|
+
error,
|
|
23444
|
+
isLoading: false,
|
|
23445
|
+
}));
|
|
23446
|
+
}
|
|
23447
|
+
/**
|
|
23448
|
+
* Limpia el error.
|
|
23449
|
+
*/
|
|
23450
|
+
clearError() {
|
|
23451
|
+
this._state.update((s) => ({
|
|
23452
|
+
...s,
|
|
23453
|
+
error: null,
|
|
23454
|
+
}));
|
|
23455
|
+
}
|
|
23456
|
+
/**
|
|
23457
|
+
* Establece estado de MFA pendiente.
|
|
23458
|
+
*/
|
|
23459
|
+
setMFAPending(mfaState) {
|
|
23460
|
+
this._mfaPending.set(mfaState);
|
|
23461
|
+
}
|
|
23462
|
+
/**
|
|
23463
|
+
* Limpia el estado de MFA pendiente.
|
|
23464
|
+
*/
|
|
23465
|
+
clearMFAPending() {
|
|
23466
|
+
this._mfaPending.set(INITIAL_MFA_STATE);
|
|
23467
|
+
}
|
|
23468
|
+
/**
|
|
23469
|
+
* Resetea todo el estado a valores iniciales.
|
|
23470
|
+
*/
|
|
23471
|
+
reset() {
|
|
23472
|
+
this._state.set(INITIAL_AUTH_STATE);
|
|
23473
|
+
this._mfaPending.set(INITIAL_MFA_STATE);
|
|
23474
|
+
}
|
|
23475
|
+
/**
|
|
23476
|
+
* Restaura estado desde datos almacenados.
|
|
23477
|
+
*/
|
|
23478
|
+
restoreFromStorage(stored) {
|
|
23479
|
+
if (stored.accessToken) {
|
|
23480
|
+
this._state.set({
|
|
23481
|
+
isAuthenticated: true,
|
|
23482
|
+
isLoading: false,
|
|
23483
|
+
accessToken: stored.accessToken,
|
|
23484
|
+
refreshToken: stored.refreshToken || null,
|
|
23485
|
+
userId: null, // Se extraerá del token
|
|
23486
|
+
email: null, // Se extraerá del token
|
|
23487
|
+
roles: stored.roles || [],
|
|
23488
|
+
permissions: stored.permissions || [],
|
|
23489
|
+
isSuperAdmin: stored.isSuperAdmin || false,
|
|
23490
|
+
expiresAt: stored.expiresAt || null,
|
|
23491
|
+
error: null,
|
|
23492
|
+
});
|
|
23493
|
+
}
|
|
23494
|
+
}
|
|
23495
|
+
/**
|
|
23496
|
+
* Actualiza el userId y email (después de parsear el token).
|
|
23497
|
+
*/
|
|
23498
|
+
updateUserInfo(userId, email) {
|
|
23499
|
+
this._state.update((s) => ({
|
|
23500
|
+
...s,
|
|
23501
|
+
userId,
|
|
23502
|
+
email,
|
|
23503
|
+
}));
|
|
23504
|
+
}
|
|
23505
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
23506
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthStateService, providedIn: 'root' }); }
|
|
23507
|
+
}
|
|
23508
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthStateService, decorators: [{
|
|
23509
|
+
type: Injectable,
|
|
23510
|
+
args: [{ providedIn: 'root' }]
|
|
23511
|
+
}] });
|
|
23512
|
+
|
|
23513
|
+
/**
|
|
23514
|
+
* Servicio para manejo de tokens JWT.
|
|
23515
|
+
* Parseo y validación de tokens sin dependencias externas.
|
|
23516
|
+
*/
|
|
23517
|
+
class TokenService {
|
|
23518
|
+
/**
|
|
23519
|
+
* Parsea un token JWT y extrae los claims.
|
|
23520
|
+
* @param token - Token JWT
|
|
23521
|
+
* @returns Claims del token o null si es inválido
|
|
23522
|
+
*/
|
|
23523
|
+
parseToken(token) {
|
|
23524
|
+
try {
|
|
23525
|
+
const parts = token.split('.');
|
|
23526
|
+
if (parts.length !== 3) {
|
|
23527
|
+
return null;
|
|
23528
|
+
}
|
|
23529
|
+
const payload = parts[1];
|
|
23530
|
+
const decoded = this.base64UrlDecode(payload);
|
|
23531
|
+
return JSON.parse(decoded);
|
|
23532
|
+
}
|
|
23533
|
+
catch {
|
|
23534
|
+
console.warn('[ValtechAuth] Error al parsear token JWT');
|
|
23535
|
+
return null;
|
|
23536
|
+
}
|
|
23537
|
+
}
|
|
23538
|
+
/**
|
|
23539
|
+
* Verifica si un token es válido (no expirado).
|
|
23540
|
+
* @param token - Token JWT
|
|
23541
|
+
* @returns true si el token es válido
|
|
23542
|
+
*/
|
|
23543
|
+
isTokenValid(token) {
|
|
23544
|
+
const claims = this.parseToken(token);
|
|
23545
|
+
if (!claims) {
|
|
23546
|
+
return false;
|
|
23547
|
+
}
|
|
23548
|
+
// exp está en segundos, Date.now() en milisegundos
|
|
23549
|
+
const expirationMs = claims.exp * 1000;
|
|
23550
|
+
return Date.now() < expirationMs;
|
|
23551
|
+
}
|
|
23552
|
+
/**
|
|
23553
|
+
* Obtiene el tiempo restante del token en segundos.
|
|
23554
|
+
* @param token - Token JWT
|
|
23555
|
+
* @returns Segundos restantes o 0 si expirado
|
|
23556
|
+
*/
|
|
23557
|
+
getTimeToExpiry(token) {
|
|
23558
|
+
const claims = this.parseToken(token);
|
|
23559
|
+
if (!claims) {
|
|
23560
|
+
return 0;
|
|
23561
|
+
}
|
|
23562
|
+
const expirationMs = claims.exp * 1000;
|
|
23563
|
+
const remaining = expirationMs - Date.now();
|
|
23564
|
+
return remaining > 0 ? Math.floor(remaining / 1000) : 0;
|
|
23565
|
+
}
|
|
23566
|
+
/**
|
|
23567
|
+
* Obtiene el timestamp de expiración del token.
|
|
23568
|
+
* @param token - Token JWT
|
|
23569
|
+
* @returns Timestamp en milisegundos o null
|
|
23570
|
+
*/
|
|
23571
|
+
getExpirationTime(token) {
|
|
23572
|
+
const claims = this.parseToken(token);
|
|
23573
|
+
if (!claims) {
|
|
23574
|
+
return null;
|
|
23575
|
+
}
|
|
23576
|
+
return claims.exp * 1000;
|
|
23577
|
+
}
|
|
23578
|
+
/**
|
|
23579
|
+
* Extrae el user ID del token.
|
|
23580
|
+
* @param token - Token JWT
|
|
23581
|
+
* @returns User ID o null
|
|
23582
|
+
*/
|
|
23583
|
+
getUserId(token) {
|
|
23584
|
+
const claims = this.parseToken(token);
|
|
23585
|
+
return claims?.uid || null;
|
|
23586
|
+
}
|
|
23587
|
+
/**
|
|
23588
|
+
* Extrae el email del token.
|
|
23589
|
+
* @param token - Token JWT
|
|
23590
|
+
* @returns Email o null
|
|
23591
|
+
*/
|
|
23592
|
+
getEmail(token) {
|
|
23593
|
+
const claims = this.parseToken(token);
|
|
23594
|
+
return claims?.email || null;
|
|
23595
|
+
}
|
|
23596
|
+
/**
|
|
23597
|
+
* Decodifica base64url a string.
|
|
23598
|
+
* Base64url usa - y _ en lugar de + y /
|
|
23599
|
+
*/
|
|
23600
|
+
base64UrlDecode(str) {
|
|
23601
|
+
// Reemplazar caracteres base64url por base64 estándar
|
|
23602
|
+
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
23603
|
+
// Agregar padding si es necesario
|
|
23604
|
+
const padding = base64.length % 4;
|
|
23605
|
+
if (padding) {
|
|
23606
|
+
base64 += '='.repeat(4 - padding);
|
|
23607
|
+
}
|
|
23608
|
+
// Decodificar
|
|
23609
|
+
const decoded = atob(base64);
|
|
23610
|
+
// Manejar caracteres UTF-8
|
|
23611
|
+
return decodeURIComponent(decoded
|
|
23612
|
+
.split('')
|
|
23613
|
+
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
23614
|
+
.join(''));
|
|
23615
|
+
}
|
|
23616
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TokenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
23617
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TokenService, providedIn: 'root' }); }
|
|
23618
|
+
}
|
|
23619
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TokenService, decorators: [{
|
|
23620
|
+
type: Injectable,
|
|
23621
|
+
args: [{ providedIn: 'root' }]
|
|
23622
|
+
}] });
|
|
23623
|
+
|
|
23624
|
+
/**
|
|
23625
|
+
* Servicio para persistencia de estado de autenticación en localStorage.
|
|
23626
|
+
*/
|
|
23627
|
+
class AuthStorageService {
|
|
23628
|
+
constructor() {
|
|
23629
|
+
this.config = inject(VALTECH_AUTH_CONFIG);
|
|
23630
|
+
const prefix = this.config.storagePrefix || 'valtech_auth_';
|
|
23631
|
+
this.keys = {
|
|
23632
|
+
ACCESS_TOKEN: `${prefix}access_token`,
|
|
23633
|
+
REFRESH_TOKEN: `${prefix}refresh_token`,
|
|
23634
|
+
ROLES: `${prefix}roles`,
|
|
23635
|
+
PERMISSIONS: `${prefix}permissions`,
|
|
23636
|
+
IS_SUPER_ADMIN: `${prefix}is_super_admin`,
|
|
23637
|
+
EXPIRES_AT: `${prefix}expires_at`,
|
|
23638
|
+
};
|
|
23639
|
+
}
|
|
23640
|
+
/**
|
|
23641
|
+
* Guarda el estado completo de autenticación.
|
|
23642
|
+
*/
|
|
23643
|
+
saveState(state) {
|
|
23644
|
+
try {
|
|
23645
|
+
localStorage.setItem(this.keys.ACCESS_TOKEN, state.accessToken);
|
|
23646
|
+
localStorage.setItem(this.keys.REFRESH_TOKEN, state.refreshToken);
|
|
23647
|
+
localStorage.setItem(this.keys.ROLES, JSON.stringify(state.roles));
|
|
23648
|
+
localStorage.setItem(this.keys.PERMISSIONS, JSON.stringify(state.permissions));
|
|
23649
|
+
localStorage.setItem(this.keys.IS_SUPER_ADMIN, String(state.isSuperAdmin));
|
|
23650
|
+
if (state.expiresAt) {
|
|
23651
|
+
localStorage.setItem(this.keys.EXPIRES_AT, String(state.expiresAt));
|
|
23652
|
+
}
|
|
23653
|
+
}
|
|
23654
|
+
catch (e) {
|
|
23655
|
+
console.warn('[ValtechAuth] Error guardando estado en storage:', e);
|
|
23656
|
+
}
|
|
23657
|
+
}
|
|
23658
|
+
/**
|
|
23659
|
+
* Carga el estado de autenticación desde storage.
|
|
23660
|
+
*/
|
|
23661
|
+
loadState() {
|
|
23662
|
+
try {
|
|
23663
|
+
const accessToken = localStorage.getItem(this.keys.ACCESS_TOKEN);
|
|
23664
|
+
const refreshToken = localStorage.getItem(this.keys.REFRESH_TOKEN);
|
|
23665
|
+
const rolesJson = localStorage.getItem(this.keys.ROLES);
|
|
23666
|
+
const permissionsJson = localStorage.getItem(this.keys.PERMISSIONS);
|
|
23667
|
+
const isSuperAdmin = localStorage.getItem(this.keys.IS_SUPER_ADMIN) === 'true';
|
|
23668
|
+
const expiresAtStr = localStorage.getItem(this.keys.EXPIRES_AT);
|
|
23669
|
+
return {
|
|
23670
|
+
accessToken: accessToken || undefined,
|
|
23671
|
+
refreshToken: refreshToken || undefined,
|
|
23672
|
+
roles: rolesJson ? JSON.parse(rolesJson) : [],
|
|
23673
|
+
permissions: permissionsJson ? JSON.parse(permissionsJson) : [],
|
|
23674
|
+
isSuperAdmin,
|
|
23675
|
+
expiresAt: expiresAtStr ? Number(expiresAtStr) : undefined,
|
|
23676
|
+
};
|
|
23677
|
+
}
|
|
23678
|
+
catch (e) {
|
|
23679
|
+
console.warn('[ValtechAuth] Error cargando estado desde storage:', e);
|
|
23680
|
+
return {};
|
|
23681
|
+
}
|
|
23682
|
+
}
|
|
23683
|
+
/**
|
|
23684
|
+
* Guarda solo el access token.
|
|
23685
|
+
*/
|
|
23686
|
+
saveAccessToken(token, expiresAt) {
|
|
23687
|
+
try {
|
|
23688
|
+
localStorage.setItem(this.keys.ACCESS_TOKEN, token);
|
|
23689
|
+
if (expiresAt) {
|
|
23690
|
+
localStorage.setItem(this.keys.EXPIRES_AT, String(expiresAt));
|
|
23691
|
+
}
|
|
23692
|
+
}
|
|
23693
|
+
catch (e) {
|
|
23694
|
+
console.warn('[ValtechAuth] Error guardando access token:', e);
|
|
23695
|
+
}
|
|
23696
|
+
}
|
|
23697
|
+
/**
|
|
23698
|
+
* Guarda los permisos actualizados.
|
|
23699
|
+
*/
|
|
23700
|
+
savePermissions(response) {
|
|
23701
|
+
try {
|
|
23702
|
+
localStorage.setItem(this.keys.ROLES, JSON.stringify(response.roles));
|
|
23703
|
+
localStorage.setItem(this.keys.PERMISSIONS, JSON.stringify(response.permissions));
|
|
23704
|
+
localStorage.setItem(this.keys.IS_SUPER_ADMIN, String(response.isSuperAdmin));
|
|
23705
|
+
}
|
|
23706
|
+
catch (e) {
|
|
23707
|
+
console.warn('[ValtechAuth] Error guardando permisos:', e);
|
|
23708
|
+
}
|
|
23709
|
+
}
|
|
23710
|
+
/**
|
|
23711
|
+
* Carga los permisos desde storage.
|
|
23712
|
+
*/
|
|
23713
|
+
loadPermissions() {
|
|
23714
|
+
try {
|
|
23715
|
+
const rolesJson = localStorage.getItem(this.keys.ROLES);
|
|
23716
|
+
const permissionsJson = localStorage.getItem(this.keys.PERMISSIONS);
|
|
23717
|
+
const isSuperAdmin = localStorage.getItem(this.keys.IS_SUPER_ADMIN) === 'true';
|
|
23718
|
+
return {
|
|
23719
|
+
roles: rolesJson ? JSON.parse(rolesJson) : [],
|
|
23720
|
+
permissions: permissionsJson ? JSON.parse(permissionsJson) : [],
|
|
23721
|
+
isSuperAdmin,
|
|
23722
|
+
};
|
|
23723
|
+
}
|
|
23724
|
+
catch {
|
|
23725
|
+
return { roles: [], permissions: [], isSuperAdmin: false };
|
|
23726
|
+
}
|
|
23727
|
+
}
|
|
23728
|
+
/**
|
|
23729
|
+
* Obtiene el refresh token.
|
|
23730
|
+
*/
|
|
23731
|
+
getRefreshToken() {
|
|
23732
|
+
return localStorage.getItem(this.keys.REFRESH_TOKEN);
|
|
23733
|
+
}
|
|
23734
|
+
/**
|
|
23735
|
+
* Limpia todo el estado de autenticación.
|
|
23736
|
+
*/
|
|
23737
|
+
clear() {
|
|
23738
|
+
try {
|
|
23739
|
+
Object.values(this.keys).forEach((key) => localStorage.removeItem(key));
|
|
23740
|
+
}
|
|
23741
|
+
catch (e) {
|
|
23742
|
+
console.warn('[ValtechAuth] Error limpiando storage:', e);
|
|
23743
|
+
}
|
|
23744
|
+
}
|
|
23745
|
+
/**
|
|
23746
|
+
* Verifica si hay estado guardado.
|
|
23747
|
+
*/
|
|
23748
|
+
hasStoredState() {
|
|
23749
|
+
return !!localStorage.getItem(this.keys.ACCESS_TOKEN);
|
|
23750
|
+
}
|
|
23751
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
23752
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthStorageService, providedIn: 'root' }); }
|
|
23753
|
+
}
|
|
23754
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthStorageService, decorators: [{
|
|
23755
|
+
type: Injectable,
|
|
23756
|
+
args: [{ providedIn: 'root' }]
|
|
23757
|
+
}], ctorParameters: () => [] });
|
|
23758
|
+
|
|
23759
|
+
/**
|
|
23760
|
+
* Servicio para sincronización de estado de autenticación entre pestañas.
|
|
23761
|
+
* Usa BroadcastChannel API con fallback a storage events.
|
|
23762
|
+
*/
|
|
23763
|
+
class AuthSyncService {
|
|
23764
|
+
constructor() {
|
|
23765
|
+
this.config = inject(VALTECH_AUTH_CONFIG);
|
|
23766
|
+
this.channel = null;
|
|
23767
|
+
this.eventSubject = new Subject();
|
|
23768
|
+
this.storageListener = null;
|
|
23769
|
+
/** Observable de eventos de sincronización */
|
|
23770
|
+
this.onEvent$ = this.eventSubject.asObservable();
|
|
23771
|
+
const prefix = this.config.storagePrefix || 'valtech_auth_';
|
|
23772
|
+
this.channelName = `${prefix}sync_channel`;
|
|
23773
|
+
}
|
|
23774
|
+
/**
|
|
23775
|
+
* Inicia la sincronización entre pestañas.
|
|
23776
|
+
*/
|
|
23777
|
+
start() {
|
|
23778
|
+
if (!this.config.enableTabSync) {
|
|
23779
|
+
return;
|
|
23780
|
+
}
|
|
23781
|
+
// Intentar usar BroadcastChannel API (mejor rendimiento)
|
|
23782
|
+
if (typeof BroadcastChannel !== 'undefined') {
|
|
23783
|
+
this.initBroadcastChannel();
|
|
23784
|
+
}
|
|
23785
|
+
else {
|
|
23786
|
+
// Fallback a storage events
|
|
23787
|
+
this.initStorageEvents();
|
|
23788
|
+
}
|
|
23789
|
+
}
|
|
23790
|
+
/**
|
|
23791
|
+
* Detiene la sincronización.
|
|
23792
|
+
*/
|
|
23793
|
+
stop() {
|
|
23794
|
+
if (this.channel) {
|
|
23795
|
+
this.channel.close();
|
|
23796
|
+
this.channel = null;
|
|
23797
|
+
}
|
|
23798
|
+
if (this.storageListener) {
|
|
23799
|
+
window.removeEventListener('storage', this.storageListener);
|
|
23800
|
+
this.storageListener = null;
|
|
23801
|
+
}
|
|
23802
|
+
}
|
|
23803
|
+
/**
|
|
23804
|
+
* Envía un evento a otras pestañas.
|
|
23805
|
+
*/
|
|
23806
|
+
broadcast(event) {
|
|
23807
|
+
if (!this.config.enableTabSync) {
|
|
23808
|
+
return;
|
|
23809
|
+
}
|
|
23810
|
+
const fullEvent = {
|
|
23811
|
+
...event,
|
|
23812
|
+
timestamp: Date.now(),
|
|
23813
|
+
};
|
|
23814
|
+
if (this.channel) {
|
|
23815
|
+
this.channel.postMessage(fullEvent);
|
|
23816
|
+
}
|
|
23817
|
+
else {
|
|
23818
|
+
// Fallback: usar localStorage para notificar
|
|
23819
|
+
this.broadcastViaStorage(fullEvent);
|
|
23820
|
+
}
|
|
23821
|
+
}
|
|
23822
|
+
ngOnDestroy() {
|
|
23823
|
+
this.stop();
|
|
23824
|
+
this.eventSubject.complete();
|
|
23825
|
+
}
|
|
23826
|
+
/**
|
|
23827
|
+
* Inicializa BroadcastChannel API.
|
|
23828
|
+
*/
|
|
23829
|
+
initBroadcastChannel() {
|
|
23830
|
+
try {
|
|
23831
|
+
this.channel = new BroadcastChannel(this.channelName);
|
|
23832
|
+
this.channel.onmessage = (event) => {
|
|
23833
|
+
this.handleEvent(event.data);
|
|
23834
|
+
};
|
|
23835
|
+
this.channel.onmessageerror = () => {
|
|
23836
|
+
console.warn('[ValtechAuth] Error en BroadcastChannel, usando fallback');
|
|
23837
|
+
this.channel?.close();
|
|
23838
|
+
this.channel = null;
|
|
23839
|
+
this.initStorageEvents();
|
|
23840
|
+
};
|
|
23841
|
+
}
|
|
23842
|
+
catch {
|
|
23843
|
+
// BroadcastChannel no disponible, usar fallback
|
|
23844
|
+
this.initStorageEvents();
|
|
23845
|
+
}
|
|
23846
|
+
}
|
|
23847
|
+
/**
|
|
23848
|
+
* Inicializa fallback con storage events.
|
|
23849
|
+
*/
|
|
23850
|
+
initStorageEvents() {
|
|
23851
|
+
const storageKey = `${this.config.storagePrefix}sync_event`;
|
|
23852
|
+
this.storageListener = (event) => {
|
|
23853
|
+
if (event.key === storageKey && event.newValue) {
|
|
23854
|
+
try {
|
|
23855
|
+
const syncEvent = JSON.parse(event.newValue);
|
|
23856
|
+
this.handleEvent(syncEvent);
|
|
23857
|
+
}
|
|
23858
|
+
catch {
|
|
23859
|
+
// Ignorar eventos mal formados
|
|
23860
|
+
}
|
|
23861
|
+
}
|
|
23862
|
+
};
|
|
23863
|
+
window.addEventListener('storage', this.storageListener);
|
|
23864
|
+
}
|
|
23865
|
+
/**
|
|
23866
|
+
* Envía evento via localStorage (fallback).
|
|
23867
|
+
*/
|
|
23868
|
+
broadcastViaStorage(event) {
|
|
23869
|
+
const storageKey = `${this.config.storagePrefix}sync_event`;
|
|
23870
|
+
try {
|
|
23871
|
+
// Escribir y luego limpiar para permitir múltiples eventos del mismo tipo
|
|
23872
|
+
localStorage.setItem(storageKey, JSON.stringify(event));
|
|
23873
|
+
// Usar setTimeout para permitir que otras pestañas lean el valor
|
|
23874
|
+
setTimeout(() => {
|
|
23875
|
+
localStorage.removeItem(storageKey);
|
|
23876
|
+
}, 100);
|
|
23877
|
+
}
|
|
23878
|
+
catch {
|
|
23879
|
+
console.warn('[ValtechAuth] Error enviando evento via storage');
|
|
23880
|
+
}
|
|
23881
|
+
}
|
|
23882
|
+
/**
|
|
23883
|
+
* Maneja un evento recibido.
|
|
23884
|
+
*/
|
|
23885
|
+
handleEvent(event) {
|
|
23886
|
+
// Verificar que el evento no sea muy antiguo (más de 5 segundos)
|
|
23887
|
+
const age = Date.now() - event.timestamp;
|
|
23888
|
+
if (age > 5000) {
|
|
23889
|
+
return;
|
|
23890
|
+
}
|
|
23891
|
+
this.eventSubject.next(event);
|
|
23892
|
+
}
|
|
23893
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthSyncService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
23894
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthSyncService, providedIn: 'root' }); }
|
|
23895
|
+
}
|
|
23896
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthSyncService, decorators: [{
|
|
23897
|
+
type: Injectable,
|
|
23898
|
+
args: [{ providedIn: 'root' }]
|
|
23899
|
+
}], ctorParameters: () => [] });
|
|
23900
|
+
|
|
23901
|
+
// Importación opcional de FirebaseService
|
|
23902
|
+
let FirebaseService = null;
|
|
23903
|
+
try {
|
|
23904
|
+
// Intenta importar FirebaseService si está disponible
|
|
23905
|
+
Promise.resolve().then(function () { return index; }).then((m) => {
|
|
23906
|
+
FirebaseService = m.FirebaseService;
|
|
23907
|
+
});
|
|
23908
|
+
}
|
|
23909
|
+
catch {
|
|
23910
|
+
// FirebaseService no disponible
|
|
23911
|
+
}
|
|
23912
|
+
/**
|
|
23913
|
+
* Servicio principal de autenticación.
|
|
23914
|
+
*
|
|
23915
|
+
* @example
|
|
23916
|
+
* ```typescript
|
|
23917
|
+
* import { AuthService } from 'valtech-components';
|
|
23918
|
+
*
|
|
23919
|
+
* @Component({...})
|
|
23920
|
+
* export class LoginComponent {
|
|
23921
|
+
* private auth = inject(AuthService);
|
|
23922
|
+
*
|
|
23923
|
+
* async login() {
|
|
23924
|
+
* await firstValueFrom(this.auth.signin({ email, password }));
|
|
23925
|
+
* if (this.auth.mfaPending().required) {
|
|
23926
|
+
* // Mostrar UI de MFA
|
|
23927
|
+
* } else {
|
|
23928
|
+
* this.router.navigate(['/']);
|
|
23929
|
+
* }
|
|
23930
|
+
* }
|
|
23931
|
+
* }
|
|
23932
|
+
* ```
|
|
23933
|
+
*/
|
|
23934
|
+
class AuthService {
|
|
23935
|
+
constructor() {
|
|
23936
|
+
this.config = inject(VALTECH_AUTH_CONFIG);
|
|
23937
|
+
this.http = inject(HttpClient);
|
|
23938
|
+
this.router = inject(Router);
|
|
23939
|
+
this.stateService = inject(AuthStateService);
|
|
23940
|
+
this.tokenService = inject(TokenService);
|
|
23941
|
+
this.storageService = inject(AuthStorageService);
|
|
23942
|
+
this.syncService = inject(AuthSyncService);
|
|
23943
|
+
// Timer para refresh proactivo
|
|
23944
|
+
this.refreshTimerId = null;
|
|
23945
|
+
this.syncSubscription = null;
|
|
23946
|
+
// =============================================
|
|
23947
|
+
// ESTADO PÚBLICO (Signals readonly)
|
|
23948
|
+
// =============================================
|
|
23949
|
+
/** Estado completo de autenticación */
|
|
23950
|
+
this.state = this.stateService.state;
|
|
23951
|
+
/** Usuario está autenticado */
|
|
23952
|
+
this.isAuthenticated = this.stateService.isAuthenticated;
|
|
23953
|
+
/** Estado de carga */
|
|
23954
|
+
this.isLoading = this.stateService.isLoading;
|
|
23955
|
+
/** Información del usuario */
|
|
23956
|
+
this.user = this.stateService.user;
|
|
23957
|
+
/** Token de acceso */
|
|
23958
|
+
this.accessToken = this.stateService.accessToken;
|
|
23959
|
+
/** Roles del usuario */
|
|
23960
|
+
this.roles = this.stateService.roles;
|
|
23961
|
+
/** Permisos del usuario */
|
|
23962
|
+
this.permissions = this.stateService.permissions;
|
|
23963
|
+
/** Usuario es super admin */
|
|
23964
|
+
this.isSuperAdmin = this.stateService.isSuperAdmin;
|
|
23965
|
+
/** Estado de MFA pendiente */
|
|
23966
|
+
this.mfaPending = this.stateService.mfaPending;
|
|
23967
|
+
/** Error actual */
|
|
23968
|
+
this.error = this.stateService.error;
|
|
23969
|
+
}
|
|
23970
|
+
// =============================================
|
|
23971
|
+
// INICIALIZACIÓN
|
|
23972
|
+
// =============================================
|
|
23973
|
+
/**
|
|
23974
|
+
* Inicializa el servicio de autenticación.
|
|
23975
|
+
* Llamado automáticamente por provideValtechAuth.
|
|
23976
|
+
*/
|
|
23977
|
+
async initialize() {
|
|
23978
|
+
// 1. Cargar estado desde storage
|
|
23979
|
+
const storedState = this.storageService.loadState();
|
|
23980
|
+
if (storedState.accessToken) {
|
|
23981
|
+
// 2. Verificar si token es válido
|
|
23982
|
+
if (this.tokenService.isTokenValid(storedState.accessToken)) {
|
|
23983
|
+
this.stateService.restoreFromStorage(storedState);
|
|
23984
|
+
// Extraer info del token
|
|
23985
|
+
const claims = this.tokenService.parseToken(storedState.accessToken);
|
|
23986
|
+
if (claims) {
|
|
23987
|
+
this.stateService.updateUserInfo(claims.uid, claims.email);
|
|
23988
|
+
}
|
|
23989
|
+
// 3. Iniciar timer de refresco proactivo
|
|
23990
|
+
this.startRefreshTimer();
|
|
23991
|
+
}
|
|
23992
|
+
else if (storedState.refreshToken) {
|
|
23993
|
+
// 4. Token expirado pero hay refresh token - intentar refrescar
|
|
23994
|
+
try {
|
|
23995
|
+
await firstValueFrom(this.refreshAccessToken());
|
|
23996
|
+
}
|
|
23997
|
+
catch {
|
|
23998
|
+
this.clearState();
|
|
23999
|
+
}
|
|
24000
|
+
}
|
|
24001
|
+
else {
|
|
24002
|
+
this.clearState();
|
|
24003
|
+
}
|
|
24004
|
+
}
|
|
24005
|
+
// 5. Iniciar sincronización entre pestañas
|
|
24006
|
+
if (this.config.enableTabSync) {
|
|
24007
|
+
this.syncService.start();
|
|
24008
|
+
this.syncSubscription = this.syncService.onEvent$.subscribe((event) => this.handleSyncEvent(event));
|
|
24009
|
+
}
|
|
24010
|
+
this.stateService.setLoading(false);
|
|
24011
|
+
}
|
|
24012
|
+
ngOnDestroy() {
|
|
24013
|
+
this.stopRefreshTimer();
|
|
24014
|
+
this.syncSubscription?.unsubscribe();
|
|
24015
|
+
}
|
|
24016
|
+
// =============================================
|
|
24017
|
+
// AUTENTICACIÓN
|
|
24018
|
+
// =============================================
|
|
24019
|
+
/**
|
|
24020
|
+
* Inicia sesión con email y contraseña.
|
|
24021
|
+
*/
|
|
24022
|
+
signin(request) {
|
|
24023
|
+
this.stateService.clearError();
|
|
24024
|
+
return this.http
|
|
24025
|
+
.post(`${this.baseUrl}/signin`, request)
|
|
24026
|
+
.pipe(tap((response) => {
|
|
24027
|
+
if (response.mfaRequired) {
|
|
24028
|
+
// MFA requerido - guardar estado temporal
|
|
24029
|
+
this.stateService.setMFAPending({
|
|
24030
|
+
required: true,
|
|
24031
|
+
mfaToken: response.mfaToken,
|
|
24032
|
+
method: response.mfaMethod,
|
|
24033
|
+
});
|
|
24034
|
+
}
|
|
24035
|
+
else if (response.accessToken) {
|
|
24036
|
+
// Login exitoso sin MFA
|
|
24037
|
+
this.handleSuccessfulAuth(response);
|
|
24038
|
+
}
|
|
24039
|
+
}), catchError((error) => this.handleAuthError(error)));
|
|
24040
|
+
}
|
|
24041
|
+
/**
|
|
24042
|
+
* Verifica código MFA.
|
|
24043
|
+
*/
|
|
24044
|
+
verifyMFA(code) {
|
|
24045
|
+
const mfaState = this.mfaPending();
|
|
24046
|
+
if (!mfaState.mfaToken) {
|
|
24047
|
+
return throwError(() => ({
|
|
24048
|
+
code: 'MFA_NOT_PENDING',
|
|
24049
|
+
message: 'No hay verificación MFA pendiente',
|
|
24050
|
+
}));
|
|
24051
|
+
}
|
|
24052
|
+
return this.http
|
|
24053
|
+
.post(`${this.baseUrl}/mfa/verify`, {
|
|
24054
|
+
mfaToken: mfaState.mfaToken,
|
|
24055
|
+
code,
|
|
24056
|
+
})
|
|
24057
|
+
.pipe(tap((response) => {
|
|
24058
|
+
this.stateService.clearMFAPending();
|
|
24059
|
+
this.handleSuccessfulAuth(response);
|
|
24060
|
+
}), catchError((error) => this.handleAuthError(error)));
|
|
24061
|
+
}
|
|
24062
|
+
/**
|
|
24063
|
+
* Refresca el token de acceso.
|
|
24064
|
+
*/
|
|
24065
|
+
refreshAccessToken() {
|
|
24066
|
+
const refreshToken = this.state().refreshToken;
|
|
24067
|
+
if (!refreshToken) {
|
|
24068
|
+
return throwError(() => ({
|
|
24069
|
+
code: 'NO_REFRESH_TOKEN',
|
|
24070
|
+
message: 'No hay token de refresco',
|
|
24071
|
+
}));
|
|
24072
|
+
}
|
|
24073
|
+
return this.http
|
|
24074
|
+
.post(`${this.baseUrl}/refresh`, { refreshToken })
|
|
24075
|
+
.pipe(tap((response) => {
|
|
24076
|
+
const expiresAt = Date.now() + response.expiresIn * 1000;
|
|
24077
|
+
this.stateService.updateAccessToken(response.accessToken, response.expiresIn);
|
|
24078
|
+
this.storageService.saveAccessToken(response.accessToken, expiresAt);
|
|
24079
|
+
this.startRefreshTimer();
|
|
24080
|
+
this.syncService.broadcast({
|
|
24081
|
+
type: 'TOKEN_REFRESH',
|
|
24082
|
+
payload: { accessToken: response.accessToken, expiresAt },
|
|
24083
|
+
});
|
|
24084
|
+
}), catchError((error) => {
|
|
24085
|
+
this.logout();
|
|
24086
|
+
return throwError(() => error);
|
|
24087
|
+
}));
|
|
24088
|
+
}
|
|
24089
|
+
/**
|
|
24090
|
+
* Cierra sesión.
|
|
24091
|
+
*/
|
|
24092
|
+
logout() {
|
|
24093
|
+
const refreshToken = this.state().refreshToken;
|
|
24094
|
+
// Notificar al backend (fire and forget)
|
|
24095
|
+
if (refreshToken) {
|
|
24096
|
+
this.http
|
|
24097
|
+
.post(`${this.baseUrl}/logout`, { refreshToken })
|
|
24098
|
+
.pipe(catchError(() => of(null)))
|
|
24099
|
+
.subscribe();
|
|
24100
|
+
}
|
|
24101
|
+
// Cerrar sesión de Firebase si está integrado
|
|
24102
|
+
this.signOutFirebase();
|
|
24103
|
+
this.clearState();
|
|
24104
|
+
this.syncService.broadcast({ type: 'LOGOUT' });
|
|
24105
|
+
this.router.navigate([this.config.loginRoute]);
|
|
24106
|
+
}
|
|
24107
|
+
// =============================================
|
|
24108
|
+
// MFA SETUP (usuario autenticado)
|
|
24109
|
+
// =============================================
|
|
24110
|
+
/**
|
|
24111
|
+
* Configura MFA para el usuario.
|
|
24112
|
+
*/
|
|
24113
|
+
setupMFA(method, phone) {
|
|
24114
|
+
return this.http
|
|
24115
|
+
.post(`${this.baseUrl}/mfa/setup`, { method, phone })
|
|
24116
|
+
.pipe(catchError((error) => this.handleAuthError(error)));
|
|
24117
|
+
}
|
|
24118
|
+
/**
|
|
24119
|
+
* Confirma la configuración de MFA.
|
|
24120
|
+
*/
|
|
24121
|
+
confirmMFA(code) {
|
|
24122
|
+
return this.http
|
|
24123
|
+
.post(`${this.baseUrl}/mfa/confirm`, { code })
|
|
24124
|
+
.pipe(catchError((error) => this.handleAuthError(error)));
|
|
24125
|
+
}
|
|
24126
|
+
/**
|
|
24127
|
+
* Deshabilita MFA.
|
|
24128
|
+
*/
|
|
24129
|
+
disableMFA(password) {
|
|
24130
|
+
return this.http
|
|
24131
|
+
.post(`${this.baseUrl}/mfa/disable`, { password })
|
|
24132
|
+
.pipe(catchError((error) => this.handleAuthError(error)));
|
|
24133
|
+
}
|
|
24134
|
+
// =============================================
|
|
24135
|
+
// PERMISOS
|
|
24136
|
+
// =============================================
|
|
24137
|
+
/**
|
|
24138
|
+
* Obtiene los permisos actualizados del backend.
|
|
24139
|
+
*/
|
|
24140
|
+
fetchPermissions() {
|
|
24141
|
+
return this.http
|
|
24142
|
+
.get(`${this.baseUrl}/permissions`)
|
|
24143
|
+
.pipe(tap((response) => {
|
|
24144
|
+
this.stateService.updatePermissions(response.roles, response.permissions, response.isSuperAdmin);
|
|
24145
|
+
this.storageService.savePermissions(response);
|
|
24146
|
+
this.syncService.broadcast({ type: 'PERMISSIONS_UPDATE' });
|
|
24147
|
+
}), catchError((error) => this.handleAuthError(error)));
|
|
24148
|
+
}
|
|
24149
|
+
/**
|
|
24150
|
+
* Verifica si el usuario tiene un permiso específico.
|
|
24151
|
+
* Formato: "resource:action" (ej: "templates:edit")
|
|
24152
|
+
*/
|
|
24153
|
+
hasPermission(permission) {
|
|
24154
|
+
if (this.isSuperAdmin())
|
|
24155
|
+
return true;
|
|
24156
|
+
const [resource, action] = permission.split(':');
|
|
24157
|
+
return this.permissions().some((p) => {
|
|
24158
|
+
const [pResource, pAction] = p.split(':');
|
|
24159
|
+
return ((pResource === '*' || pResource === resource) &&
|
|
24160
|
+
(pAction === '*' || pAction === action));
|
|
24161
|
+
});
|
|
24162
|
+
}
|
|
24163
|
+
/**
|
|
24164
|
+
* Verifica si el usuario tiene alguno de los permisos dados.
|
|
24165
|
+
*/
|
|
24166
|
+
hasAnyPermission(permissions) {
|
|
24167
|
+
return permissions.some((p) => this.hasPermission(p));
|
|
24168
|
+
}
|
|
24169
|
+
/**
|
|
24170
|
+
* Verifica si el usuario tiene todos los permisos dados.
|
|
24171
|
+
*/
|
|
24172
|
+
hasAllPermissions(permissions) {
|
|
24173
|
+
return permissions.every((p) => this.hasPermission(p));
|
|
24174
|
+
}
|
|
24175
|
+
/**
|
|
24176
|
+
* Verifica si el usuario tiene un rol específico.
|
|
24177
|
+
*/
|
|
24178
|
+
hasRole(role) {
|
|
24179
|
+
return this.roles().some((r) => r.toLowerCase() === role.toLowerCase());
|
|
24180
|
+
}
|
|
24181
|
+
// =============================================
|
|
24182
|
+
// PRIVATE METHODS
|
|
24183
|
+
// =============================================
|
|
24184
|
+
get baseUrl() {
|
|
24185
|
+
return `${this.config.apiUrl}${this.config.authPrefix}`;
|
|
24186
|
+
}
|
|
24187
|
+
handleSuccessfulAuth(response) {
|
|
24188
|
+
const expiresAt = Date.now() + (response.expiresIn * 1000);
|
|
24189
|
+
const tokenData = this.tokenService.parseToken(response.accessToken);
|
|
24190
|
+
this.stateService.setAuthenticated({
|
|
24191
|
+
accessToken: response.accessToken,
|
|
24192
|
+
refreshToken: response.refreshToken,
|
|
24193
|
+
userId: tokenData?.uid,
|
|
24194
|
+
email: tokenData?.email,
|
|
24195
|
+
roles: response.roles || [],
|
|
24196
|
+
permissions: response.permissions || [],
|
|
24197
|
+
isSuperAdmin: response.permissions?.includes('*:*') || false,
|
|
24198
|
+
expiresAt,
|
|
24199
|
+
});
|
|
24200
|
+
this.storageService.saveState({
|
|
24201
|
+
accessToken: response.accessToken,
|
|
24202
|
+
refreshToken: response.refreshToken,
|
|
24203
|
+
roles: response.roles || [],
|
|
24204
|
+
permissions: response.permissions || [],
|
|
24205
|
+
isSuperAdmin: response.permissions?.includes('*:*') || false,
|
|
24206
|
+
expiresAt,
|
|
24207
|
+
});
|
|
24208
|
+
this.startRefreshTimer();
|
|
24209
|
+
this.syncService.broadcast({ type: 'LOGIN' });
|
|
24210
|
+
// Integración con Firebase
|
|
24211
|
+
if (this.config.enableFirebaseIntegration &&
|
|
24212
|
+
'firebaseToken' in response &&
|
|
24213
|
+
response.firebaseToken) {
|
|
24214
|
+
this.signInWithFirebase(response.firebaseToken);
|
|
24215
|
+
}
|
|
24216
|
+
}
|
|
24217
|
+
clearState() {
|
|
24218
|
+
this.stopRefreshTimer();
|
|
24219
|
+
this.stateService.reset();
|
|
24220
|
+
this.storageService.clear();
|
|
24221
|
+
}
|
|
24222
|
+
startRefreshTimer() {
|
|
24223
|
+
this.stopRefreshTimer();
|
|
24224
|
+
const state = this.stateService.state();
|
|
24225
|
+
if (!state.expiresAt)
|
|
24226
|
+
return;
|
|
24227
|
+
const refreshBeforeMs = (this.config.refreshBeforeExpiry || 60) * 1000;
|
|
24228
|
+
const refreshAt = state.expiresAt - refreshBeforeMs;
|
|
24229
|
+
const delay = refreshAt - Date.now();
|
|
24230
|
+
if (delay > 0) {
|
|
24231
|
+
this.refreshTimerId = setTimeout(() => {
|
|
24232
|
+
this.refreshAccessToken().subscribe({
|
|
24233
|
+
error: () => this.logout(),
|
|
24234
|
+
});
|
|
24235
|
+
}, delay);
|
|
24236
|
+
}
|
|
24237
|
+
else if (state.refreshToken) {
|
|
24238
|
+
// Token ya debería refrescarse, intentar ahora
|
|
24239
|
+
this.refreshAccessToken().subscribe({
|
|
24240
|
+
error: () => this.logout(),
|
|
24241
|
+
});
|
|
24242
|
+
}
|
|
24243
|
+
}
|
|
24244
|
+
stopRefreshTimer() {
|
|
24245
|
+
if (this.refreshTimerId) {
|
|
24246
|
+
clearTimeout(this.refreshTimerId);
|
|
24247
|
+
this.refreshTimerId = null;
|
|
24248
|
+
}
|
|
24249
|
+
}
|
|
24250
|
+
handleSyncEvent(event) {
|
|
24251
|
+
switch (event.type) {
|
|
24252
|
+
case 'LOGIN':
|
|
24253
|
+
case 'TOKEN_REFRESH': {
|
|
24254
|
+
// Recargar estado desde storage
|
|
24255
|
+
const state = this.storageService.loadState();
|
|
24256
|
+
if (state.accessToken) {
|
|
24257
|
+
this.stateService.restoreFromStorage(state);
|
|
24258
|
+
const claims = this.tokenService.parseToken(state.accessToken);
|
|
24259
|
+
if (claims) {
|
|
24260
|
+
this.stateService.updateUserInfo(claims.uid, claims.email);
|
|
24261
|
+
}
|
|
24262
|
+
this.startRefreshTimer();
|
|
24263
|
+
}
|
|
24264
|
+
break;
|
|
24265
|
+
}
|
|
24266
|
+
case 'LOGOUT':
|
|
24267
|
+
this.stateService.reset();
|
|
24268
|
+
this.stopRefreshTimer();
|
|
24269
|
+
this.router.navigate([this.config.loginRoute]);
|
|
24270
|
+
break;
|
|
24271
|
+
case 'PERMISSIONS_UPDATE': {
|
|
24272
|
+
const perms = this.storageService.loadPermissions();
|
|
24273
|
+
this.stateService.updatePermissions(perms.roles, perms.permissions, perms.isSuperAdmin);
|
|
24274
|
+
break;
|
|
24275
|
+
}
|
|
24276
|
+
}
|
|
24277
|
+
}
|
|
24278
|
+
handleAuthError(error) {
|
|
24279
|
+
const authError = {
|
|
24280
|
+
code: error.error?.code || 'UNKNOWN_ERROR',
|
|
24281
|
+
message: error.error?.message || 'Error de autenticación desconocido',
|
|
24282
|
+
};
|
|
24283
|
+
this.stateService.setError(authError);
|
|
24284
|
+
return throwError(() => authError);
|
|
24285
|
+
}
|
|
24286
|
+
// =============================================
|
|
24287
|
+
// FIREBASE INTEGRATION
|
|
24288
|
+
// =============================================
|
|
24289
|
+
async signInWithFirebase(firebaseToken) {
|
|
24290
|
+
try {
|
|
24291
|
+
// Importar FirebaseService dinámicamente
|
|
24292
|
+
const firebase = await Promise.resolve().then(function () { return index; });
|
|
24293
|
+
const injector = (await import('@angular/core')).inject;
|
|
24294
|
+
// Esto es un workaround - en producción se usaría un patrón más robusto
|
|
24295
|
+
console.log('[ValtechAuth] Firebase integration: token received, attempting signin...');
|
|
24296
|
+
// Por ahora, solo loguear que se recibió el token
|
|
24297
|
+
// La integración real requiere inyectar FirebaseService
|
|
24298
|
+
}
|
|
24299
|
+
catch {
|
|
24300
|
+
console.warn('[ValtechAuth] FirebaseService no disponible');
|
|
24301
|
+
}
|
|
24302
|
+
}
|
|
24303
|
+
async signOutFirebase() {
|
|
24304
|
+
if (!this.config.enableFirebaseIntegration)
|
|
24305
|
+
return;
|
|
24306
|
+
try {
|
|
24307
|
+
// Similar al signin, la integración real requiere inyección
|
|
24308
|
+
console.log('[ValtechAuth] Firebase signout triggered');
|
|
24309
|
+
}
|
|
24310
|
+
catch {
|
|
24311
|
+
// Ignorar errores de Firebase
|
|
24312
|
+
}
|
|
24313
|
+
}
|
|
24314
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
24315
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, providedIn: 'root' }); }
|
|
24316
|
+
}
|
|
24317
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, decorators: [{
|
|
24318
|
+
type: Injectable,
|
|
24319
|
+
args: [{ providedIn: 'root' }]
|
|
24320
|
+
}] });
|
|
24321
|
+
|
|
24322
|
+
// Control de estado de refresco (singleton a nivel de módulo)
|
|
24323
|
+
let isRefreshing = false;
|
|
24324
|
+
const refreshTokenSubject = new BehaviorSubject(null);
|
|
24325
|
+
/**
|
|
24326
|
+
* Interceptor HTTP que:
|
|
24327
|
+
* 1. Agrega header Authorization con Bearer token a requests API
|
|
24328
|
+
* 2. Maneja errores 401 refrescando el token automáticamente
|
|
24329
|
+
* 3. Encola requests durante el refresco para evitar múltiples refresh
|
|
24330
|
+
*
|
|
24331
|
+
* @example
|
|
24332
|
+
* ```typescript
|
|
24333
|
+
* // Incluido automáticamente por provideValtechAuth()
|
|
24334
|
+
* // Para uso manual:
|
|
24335
|
+
* import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
24336
|
+
* import { authInterceptor } from 'valtech-components';
|
|
24337
|
+
*
|
|
24338
|
+
* bootstrapApplication(AppComponent, {
|
|
24339
|
+
* providers: [
|
|
24340
|
+
* provideHttpClient(withInterceptors([authInterceptor])),
|
|
24341
|
+
* ],
|
|
24342
|
+
* });
|
|
24343
|
+
* ```
|
|
24344
|
+
*/
|
|
24345
|
+
const authInterceptor = (request, next) => {
|
|
24346
|
+
const authService = inject(AuthService);
|
|
24347
|
+
const config = inject(VALTECH_AUTH_CONFIG);
|
|
24348
|
+
// Omitir requests que no son a nuestra API
|
|
24349
|
+
if (!isApiRequest(request, config.apiUrl)) {
|
|
24350
|
+
return next(request);
|
|
24351
|
+
}
|
|
24352
|
+
// Omitir endpoints de auth que no necesitan token
|
|
24353
|
+
if (isAuthEndpoint(request, config.authPrefix)) {
|
|
24354
|
+
return next(request);
|
|
24355
|
+
}
|
|
24356
|
+
const accessToken = authService.accessToken();
|
|
24357
|
+
// Agregar header de autorización si hay token
|
|
24358
|
+
if (accessToken) {
|
|
24359
|
+
request = addAuthHeader(request, accessToken);
|
|
24360
|
+
}
|
|
24361
|
+
return next(request).pipe(catchError((error) => {
|
|
24362
|
+
if (error.status === 401 && !isAuthEndpoint(request, config.authPrefix)) {
|
|
24363
|
+
return handle401Error(request, next, authService);
|
|
24364
|
+
}
|
|
24365
|
+
if (error.status === 403) {
|
|
24366
|
+
console.error('[ValtechAuth] Permiso denegado:', error.error?.message || 'Acceso prohibido');
|
|
24367
|
+
}
|
|
24368
|
+
return throwError(() => error);
|
|
24369
|
+
}));
|
|
24370
|
+
};
|
|
24371
|
+
/**
|
|
24372
|
+
* Agrega header de autorización a la request.
|
|
24373
|
+
*/
|
|
24374
|
+
function addAuthHeader(request, token) {
|
|
24375
|
+
return request.clone({
|
|
24376
|
+
setHeaders: {
|
|
24377
|
+
Authorization: `Bearer ${token}`,
|
|
24378
|
+
},
|
|
24379
|
+
});
|
|
24380
|
+
}
|
|
24381
|
+
/**
|
|
24382
|
+
* Verifica si la request es a nuestra API.
|
|
24383
|
+
*/
|
|
24384
|
+
function isApiRequest(request, apiUrl) {
|
|
24385
|
+
return request.url.startsWith(apiUrl) || request.url.includes('/v2/auth');
|
|
24386
|
+
}
|
|
24387
|
+
/**
|
|
24388
|
+
* Verifica si la request es a un endpoint de auth que no debe reintentar.
|
|
24389
|
+
*/
|
|
24390
|
+
function isAuthEndpoint(request, authPrefix) {
|
|
24391
|
+
const authEndpoints = ['/signin', '/signup', '/refresh', '/logout', '/mfa/verify'];
|
|
24392
|
+
return authEndpoints.some((endpoint) => request.url.includes(`${authPrefix}${endpoint}`));
|
|
24393
|
+
}
|
|
24394
|
+
/**
|
|
24395
|
+
* Maneja errores 401 refrescando el token.
|
|
24396
|
+
*/
|
|
24397
|
+
function handle401Error(request, next, authService) {
|
|
24398
|
+
if (!isRefreshing) {
|
|
24399
|
+
isRefreshing = true;
|
|
24400
|
+
refreshTokenSubject.next(null);
|
|
24401
|
+
return authService.refreshAccessToken().pipe(switchMap((response) => {
|
|
24402
|
+
refreshTokenSubject.next(response.accessToken);
|
|
24403
|
+
return next(addAuthHeader(request, response.accessToken));
|
|
24404
|
+
}), catchError((error) => {
|
|
24405
|
+
authService.logout();
|
|
24406
|
+
return throwError(() => error);
|
|
24407
|
+
}), finalize(() => {
|
|
24408
|
+
isRefreshing = false;
|
|
24409
|
+
}));
|
|
24410
|
+
}
|
|
24411
|
+
// Esperar a que termine el refresco en curso
|
|
24412
|
+
return refreshTokenSubject.pipe(filter$1((token) => token !== null), take(1), switchMap((token) => next(addAuthHeader(request, token))));
|
|
24413
|
+
}
|
|
24414
|
+
|
|
24415
|
+
/**
|
|
24416
|
+
* Token de inyección para la configuración de Auth.
|
|
24417
|
+
*/
|
|
24418
|
+
const VALTECH_AUTH_CONFIG = new InjectionToken('ValtechAuthConfig');
|
|
24419
|
+
/**
|
|
24420
|
+
* Configuración por defecto.
|
|
24421
|
+
*/
|
|
24422
|
+
const DEFAULT_AUTH_CONFIG = {
|
|
24423
|
+
authPrefix: '/v2/auth',
|
|
24424
|
+
storagePrefix: 'valtech_auth_',
|
|
24425
|
+
refreshBeforeExpiry: 60,
|
|
24426
|
+
enableTabSync: true,
|
|
24427
|
+
loginRoute: '/login',
|
|
24428
|
+
homeRoute: '/',
|
|
24429
|
+
unauthorizedRoute: '/unauthorized',
|
|
24430
|
+
enableFirebaseIntegration: false,
|
|
24431
|
+
};
|
|
24432
|
+
/**
|
|
24433
|
+
* Factory para inicializar el AuthService.
|
|
24434
|
+
*/
|
|
24435
|
+
function initializeAuth(authService) {
|
|
24436
|
+
return () => authService.initialize();
|
|
24437
|
+
}
|
|
24438
|
+
/**
|
|
24439
|
+
* Provee el servicio de autenticación a la aplicación Angular.
|
|
24440
|
+
*
|
|
24441
|
+
* @param config - Configuración de autenticación
|
|
24442
|
+
* @returns EnvironmentProviders para usar en bootstrapApplication
|
|
24443
|
+
*
|
|
24444
|
+
* @example
|
|
24445
|
+
* ```typescript
|
|
24446
|
+
* // main.ts
|
|
24447
|
+
* import { bootstrapApplication } from '@angular/platform-browser';
|
|
24448
|
+
* import { provideValtechAuth } from 'valtech-components';
|
|
24449
|
+
* import { environment } from './environments/environment';
|
|
24450
|
+
*
|
|
24451
|
+
* bootstrapApplication(AppComponent, {
|
|
24452
|
+
* providers: [
|
|
24453
|
+
* provideValtechAuth({
|
|
24454
|
+
* apiUrl: environment.apiUrl,
|
|
24455
|
+
* enableFirebaseIntegration: true,
|
|
24456
|
+
* }),
|
|
24457
|
+
* ],
|
|
24458
|
+
* });
|
|
24459
|
+
* ```
|
|
24460
|
+
*/
|
|
24461
|
+
function provideValtechAuth(config) {
|
|
24462
|
+
const mergedConfig = {
|
|
24463
|
+
...DEFAULT_AUTH_CONFIG,
|
|
24464
|
+
...config,
|
|
24465
|
+
};
|
|
24466
|
+
return makeEnvironmentProviders([
|
|
24467
|
+
{ provide: VALTECH_AUTH_CONFIG, useValue: mergedConfig },
|
|
24468
|
+
provideHttpClient(withInterceptors([authInterceptor])),
|
|
24469
|
+
// Inicializar AuthService al arrancar la app
|
|
24470
|
+
{
|
|
24471
|
+
provide: APP_INITIALIZER,
|
|
24472
|
+
useFactory: initializeAuth,
|
|
24473
|
+
deps: [AuthService],
|
|
24474
|
+
multi: true,
|
|
24475
|
+
},
|
|
24476
|
+
]);
|
|
24477
|
+
}
|
|
24478
|
+
/**
|
|
24479
|
+
* Provee solo el interceptor (para apps que ya tienen AuthService configurado manualmente).
|
|
24480
|
+
*/
|
|
24481
|
+
function provideValtechAuthInterceptor() {
|
|
24482
|
+
return makeEnvironmentProviders([
|
|
24483
|
+
provideHttpClient(withInterceptors([authInterceptor])),
|
|
24484
|
+
]);
|
|
24485
|
+
}
|
|
24486
|
+
|
|
24487
|
+
/**
|
|
24488
|
+
* Guard que verifica si el usuario está autenticado.
|
|
24489
|
+
* Redirige a loginRoute si no está autenticado.
|
|
24490
|
+
*
|
|
24491
|
+
* @example
|
|
24492
|
+
* ```typescript
|
|
24493
|
+
* import { authGuard } from 'valtech-components';
|
|
24494
|
+
*
|
|
24495
|
+
* const routes: Routes = [
|
|
24496
|
+
* {
|
|
24497
|
+
* path: 'dashboard',
|
|
24498
|
+
* canActivate: [authGuard],
|
|
24499
|
+
* loadComponent: () => import('./dashboard.page'),
|
|
24500
|
+
* },
|
|
24501
|
+
* ];
|
|
24502
|
+
* ```
|
|
24503
|
+
*/
|
|
24504
|
+
const authGuard = () => {
|
|
24505
|
+
const authService = inject(AuthService);
|
|
24506
|
+
const router = inject(Router);
|
|
24507
|
+
const config = inject(VALTECH_AUTH_CONFIG);
|
|
24508
|
+
if (authService.isAuthenticated()) {
|
|
24509
|
+
return true;
|
|
24510
|
+
}
|
|
24511
|
+
return router.createUrlTree([config.loginRoute]);
|
|
24512
|
+
};
|
|
24513
|
+
/**
|
|
24514
|
+
* Guard que verifica si el usuario NO está autenticado.
|
|
24515
|
+
* Redirige a homeRoute si ya está autenticado.
|
|
24516
|
+
* Útil para páginas de login/registro.
|
|
24517
|
+
*
|
|
24518
|
+
* @example
|
|
24519
|
+
* ```typescript
|
|
24520
|
+
* import { guestGuard } from 'valtech-components';
|
|
24521
|
+
*
|
|
24522
|
+
* const routes: Routes = [
|
|
24523
|
+
* {
|
|
24524
|
+
* path: 'login',
|
|
24525
|
+
* canActivate: [guestGuard],
|
|
24526
|
+
* loadComponent: () => import('./login.page'),
|
|
24527
|
+
* },
|
|
24528
|
+
* ];
|
|
24529
|
+
* ```
|
|
24530
|
+
*/
|
|
24531
|
+
const guestGuard = () => {
|
|
24532
|
+
const authService = inject(AuthService);
|
|
24533
|
+
const router = inject(Router);
|
|
24534
|
+
const config = inject(VALTECH_AUTH_CONFIG);
|
|
24535
|
+
if (!authService.isAuthenticated()) {
|
|
24536
|
+
return true;
|
|
24537
|
+
}
|
|
24538
|
+
return router.createUrlTree([config.homeRoute]);
|
|
24539
|
+
};
|
|
24540
|
+
/**
|
|
24541
|
+
* Factory para crear guard de permisos.
|
|
24542
|
+
* Verifica si el usuario tiene el permiso especificado.
|
|
24543
|
+
*
|
|
24544
|
+
* @param permissions - Permiso o lista de permisos requeridos (OR)
|
|
24545
|
+
* @returns Guard function
|
|
24546
|
+
*
|
|
24547
|
+
* @example
|
|
24548
|
+
* ```typescript
|
|
24549
|
+
* import { authGuard, permissionGuard } from 'valtech-components';
|
|
24550
|
+
*
|
|
24551
|
+
* const routes: Routes = [
|
|
24552
|
+
* {
|
|
24553
|
+
* path: 'templates',
|
|
24554
|
+
* canActivate: [authGuard, permissionGuard('templates:read')],
|
|
24555
|
+
* loadComponent: () => import('./templates.page'),
|
|
24556
|
+
* },
|
|
24557
|
+
* {
|
|
24558
|
+
* path: 'admin',
|
|
24559
|
+
* canActivate: [authGuard, permissionGuard(['admin:*', 'super_admin'])],
|
|
24560
|
+
* loadComponent: () => import('./admin.page'),
|
|
24561
|
+
* },
|
|
24562
|
+
* ];
|
|
24563
|
+
* ```
|
|
24564
|
+
*/
|
|
24565
|
+
function permissionGuard(permissions) {
|
|
24566
|
+
return () => {
|
|
24567
|
+
const authService = inject(AuthService);
|
|
24568
|
+
const router = inject(Router);
|
|
24569
|
+
const config = inject(VALTECH_AUTH_CONFIG);
|
|
24570
|
+
const permArray = Array.isArray(permissions) ? permissions : [permissions];
|
|
24571
|
+
if (authService.hasAnyPermission(permArray)) {
|
|
24572
|
+
return true;
|
|
24573
|
+
}
|
|
24574
|
+
console.warn(`[ValtechAuth] Permiso denegado. Requerido: ${permArray.join(' o ')}`);
|
|
24575
|
+
return router.createUrlTree([config.unauthorizedRoute]);
|
|
24576
|
+
};
|
|
24577
|
+
}
|
|
24578
|
+
/**
|
|
24579
|
+
* Guard que lee permisos desde route.data.
|
|
24580
|
+
* Permite configurar permisos directamente en la definición de rutas.
|
|
24581
|
+
*
|
|
24582
|
+
* @example
|
|
24583
|
+
* ```typescript
|
|
24584
|
+
* import { authGuard, permissionGuardFromRoute } from 'valtech-components';
|
|
24585
|
+
*
|
|
24586
|
+
* const routes: Routes = [
|
|
24587
|
+
* {
|
|
24588
|
+
* path: 'admin/users',
|
|
24589
|
+
* canActivate: [authGuard, permissionGuardFromRoute],
|
|
24590
|
+
* data: {
|
|
24591
|
+
* permissions: ['users:read', 'users:manage'],
|
|
24592
|
+
* requireAll: false // true = AND, false = OR (default)
|
|
24593
|
+
* },
|
|
24594
|
+
* loadComponent: () => import('./users.page'),
|
|
24595
|
+
* },
|
|
24596
|
+
* ];
|
|
24597
|
+
* ```
|
|
24598
|
+
*/
|
|
24599
|
+
const permissionGuardFromRoute = (route) => {
|
|
24600
|
+
const authService = inject(AuthService);
|
|
24601
|
+
const router = inject(Router);
|
|
24602
|
+
const config = inject(VALTECH_AUTH_CONFIG);
|
|
24603
|
+
const permissions = route.data['permissions'];
|
|
24604
|
+
const requireAll = route.data['requireAll'];
|
|
24605
|
+
if (!permissions || permissions.length === 0) {
|
|
24606
|
+
return true;
|
|
24607
|
+
}
|
|
24608
|
+
const hasAccess = requireAll
|
|
24609
|
+
? authService.hasAllPermissions(permissions)
|
|
24610
|
+
: authService.hasAnyPermission(permissions);
|
|
24611
|
+
if (hasAccess) {
|
|
24612
|
+
return true;
|
|
24613
|
+
}
|
|
24614
|
+
console.warn(`[ValtechAuth] Permiso denegado. Requerido: ${permissions.join(requireAll ? ' y ' : ' o ')}`);
|
|
24615
|
+
return router.createUrlTree([config.unauthorizedRoute]);
|
|
24616
|
+
};
|
|
24617
|
+
/**
|
|
24618
|
+
* Guard que verifica si el usuario es super admin.
|
|
24619
|
+
*
|
|
24620
|
+
* @example
|
|
24621
|
+
* ```typescript
|
|
24622
|
+
* import { authGuard, superAdminGuard } from 'valtech-components';
|
|
24623
|
+
*
|
|
24624
|
+
* const routes: Routes = [
|
|
24625
|
+
* {
|
|
24626
|
+
* path: 'super-admin',
|
|
24627
|
+
* canActivate: [authGuard, superAdminGuard],
|
|
24628
|
+
* loadComponent: () => import('./super-admin.page'),
|
|
24629
|
+
* },
|
|
24630
|
+
* ];
|
|
24631
|
+
* ```
|
|
24632
|
+
*/
|
|
24633
|
+
const superAdminGuard = () => {
|
|
24634
|
+
const authService = inject(AuthService);
|
|
24635
|
+
const router = inject(Router);
|
|
24636
|
+
const config = inject(VALTECH_AUTH_CONFIG);
|
|
24637
|
+
if (authService.isSuperAdmin()) {
|
|
24638
|
+
return true;
|
|
24639
|
+
}
|
|
24640
|
+
console.warn('[ValtechAuth] Acceso de super admin requerido');
|
|
24641
|
+
return router.createUrlTree([config.unauthorizedRoute]);
|
|
24642
|
+
};
|
|
24643
|
+
/**
|
|
24644
|
+
* Guard que verifica si el usuario tiene un rol específico.
|
|
24645
|
+
*
|
|
24646
|
+
* @param roles - Rol o lista de roles requeridos (OR)
|
|
24647
|
+
* @returns Guard function
|
|
24648
|
+
*
|
|
24649
|
+
* @example
|
|
24650
|
+
* ```typescript
|
|
24651
|
+
* import { authGuard, roleGuard } from 'valtech-components';
|
|
24652
|
+
*
|
|
24653
|
+
* const routes: Routes = [
|
|
24654
|
+
* {
|
|
24655
|
+
* path: 'editor',
|
|
24656
|
+
* canActivate: [authGuard, roleGuard(['editor', 'admin'])],
|
|
24657
|
+
* loadComponent: () => import('./editor.page'),
|
|
24658
|
+
* },
|
|
24659
|
+
* ];
|
|
24660
|
+
* ```
|
|
24661
|
+
*/
|
|
24662
|
+
function roleGuard(roles) {
|
|
24663
|
+
return () => {
|
|
24664
|
+
const authService = inject(AuthService);
|
|
24665
|
+
const router = inject(Router);
|
|
24666
|
+
const config = inject(VALTECH_AUTH_CONFIG);
|
|
24667
|
+
const roleArray = Array.isArray(roles) ? roles : [roles];
|
|
24668
|
+
const hasRole = roleArray.some((role) => authService.hasRole(role));
|
|
24669
|
+
if (hasRole) {
|
|
24670
|
+
return true;
|
|
24671
|
+
}
|
|
24672
|
+
console.warn(`[ValtechAuth] Rol requerido: ${roleArray.join(' o ')}`);
|
|
24673
|
+
return router.createUrlTree([config.unauthorizedRoute]);
|
|
24674
|
+
};
|
|
24675
|
+
}
|
|
24676
|
+
|
|
24677
|
+
/**
|
|
24678
|
+
* Valtech Auth Service
|
|
24679
|
+
*
|
|
24680
|
+
* Servicio de autenticación reutilizable para aplicaciones Angular.
|
|
24681
|
+
* Proporciona autenticación con AuthV2, MFA, sincronización entre pestañas,
|
|
24682
|
+
* y refresh proactivo de tokens.
|
|
24683
|
+
*
|
|
24684
|
+
* @example
|
|
24685
|
+
* ```typescript
|
|
24686
|
+
* // En main.ts
|
|
24687
|
+
* import { bootstrapApplication } from '@angular/platform-browser';
|
|
24688
|
+
* import { provideValtechAuth } from 'valtech-components';
|
|
24689
|
+
* import { environment } from './environments/environment';
|
|
24690
|
+
*
|
|
24691
|
+
* bootstrapApplication(AppComponent, {
|
|
24692
|
+
* providers: [
|
|
24693
|
+
* provideValtechAuth({
|
|
24694
|
+
* apiUrl: environment.apiUrl,
|
|
24695
|
+
* enableFirebaseIntegration: true,
|
|
24696
|
+
* }),
|
|
24697
|
+
* ],
|
|
24698
|
+
* });
|
|
24699
|
+
*
|
|
24700
|
+
* // En app.routes.ts
|
|
24701
|
+
* import { authGuard, guestGuard, permissionGuard } from 'valtech-components';
|
|
24702
|
+
*
|
|
24703
|
+
* const routes: Routes = [
|
|
24704
|
+
* { path: 'login', canActivate: [guestGuard], loadComponent: () => import('./login.page') },
|
|
24705
|
+
* { path: 'dashboard', canActivate: [authGuard], loadComponent: () => import('./dashboard.page') },
|
|
24706
|
+
* { path: 'admin', canActivate: [authGuard, permissionGuard('admin:*')], loadComponent: () => import('./admin.page') },
|
|
24707
|
+
* ];
|
|
24708
|
+
*
|
|
24709
|
+
* // En componentes
|
|
24710
|
+
* import { AuthService } from 'valtech-components';
|
|
24711
|
+
*
|
|
24712
|
+
* @Component({...})
|
|
24713
|
+
* export class LoginComponent {
|
|
24714
|
+
* private auth = inject(AuthService);
|
|
24715
|
+
*
|
|
24716
|
+
* async login() {
|
|
24717
|
+
* await firstValueFrom(this.auth.signin({ email, password }));
|
|
24718
|
+
* if (this.auth.mfaPending().required) {
|
|
24719
|
+
* // Mostrar UI de MFA
|
|
24720
|
+
* } else {
|
|
24721
|
+
* this.router.navigate(['/dashboard']);
|
|
24722
|
+
* }
|
|
24723
|
+
* }
|
|
24724
|
+
*
|
|
24725
|
+
* // En template: usar signals directamente
|
|
24726
|
+
* // {{ auth.user()?.email }}
|
|
24727
|
+
* // @if (auth.hasPermission('templates:edit')) { ... }
|
|
24728
|
+
* }
|
|
24729
|
+
* ```
|
|
24730
|
+
*/
|
|
24731
|
+
// Tipos
|
|
24732
|
+
|
|
23276
24733
|
/*
|
|
23277
24734
|
* Public API Surface of valtech-components
|
|
23278
24735
|
*/
|
|
@@ -23281,5 +24738,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
23281
24738
|
* Generated bundle index. Do not edit.
|
|
23282
24739
|
*/
|
|
23283
24740
|
|
|
23284
|
-
export { ARTICLE_SPACING, AccordionComponent, ActionHeaderComponent, ActionType, AlertBoxComponent, ArticleBuilder, ArticleComponent, 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_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, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FabComponent, FileInputComponent, FirebaseService, FirestoreCollection, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FunHeaderComponent, GlowCardComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfoComponent, InputType, ItemListComponent, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinksAccordionComponent, LinksCakeComponent, LocalStorageService, LocaleService, MODAL_SIZES, MOTION, MenuComponent, MessagingService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, 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, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TabsComponent, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, ToolbarActionType, ToolbarComponent, VALTECH_FIREBASE_CONFIG, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, buildPath, createGlowCardProps, createNumberFromToField, createTitleProps, extractPathParams, getCollectionPath, getDocumentId, goToTop, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isValidPath, joinPath, maxLength, provideValtechFirebase, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue };
|
|
24741
|
+
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, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FabComponent, FileInputComponent, FirebaseService$1 as FirebaseService, FirestoreCollection, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FunHeaderComponent, GlowCardComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfoComponent, InputType, ItemListComponent, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinksAccordionComponent, LinksCakeComponent, LocalStorageService, LocaleService, MODAL_SIZES, MOTION, MenuComponent, MessagingService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, 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, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TabsComponent, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, VALTECH_AUTH_CONFIG, VALTECH_FIREBASE_CONFIG, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, buildPath, createGlowCardProps, createNumberFromToField, createTitleProps, extractPathParams, getCollectionPath, getDocumentId, goToTop, guestGuard, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isValidPath, joinPath, maxLength, permissionGuard, permissionGuardFromRoute, provideValtechAuth, provideValtechAuthInterceptor, provideValtechFirebase, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, superAdminGuard };
|
|
23285
24742
|
//# sourceMappingURL=valtech-components.mjs.map
|