valtech-components 2.0.983 → 2.0.985
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/bottom-nav/bottom-nav.component.mjs +3 -3
- package/esm2022/lib/components/organisms/login-attempt-modal/login-attempt-modal.component.mjs +298 -0
- package/esm2022/lib/components/organisms/security-view/security-view.component.mjs +819 -0
- package/esm2022/lib/components/organisms/security-view/security-view.i18n.mjs +105 -0
- package/esm2022/lib/components/organisms/security-view/security.routes.mjs +29 -0
- package/esm2022/lib/components/organisms/security-view/types.mjs +2 -0
- package/esm2022/lib/components/organisms/session-list-modal/session-list-modal.component.mjs +206 -0
- package/esm2022/lib/components/organisms/session-list-modal/types.mjs +2 -0
- package/esm2022/lib/services/font-size/font-size.service.mjs +3 -1
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +7 -1
- package/fesm2022/valtech-components.mjs +2397 -990
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/organisms/login-attempt-modal/login-attempt-modal.component.d.ts +58 -0
- package/lib/components/organisms/security-view/security-view.component.d.ts +130 -0
- package/lib/components/organisms/security-view/security-view.i18n.d.ts +12 -0
- package/lib/components/organisms/security-view/security.routes.d.ts +24 -0
- package/lib/components/organisms/security-view/types.d.ts +48 -0
- package/lib/components/organisms/session-list-modal/session-list-modal.component.d.ts +56 -0
- package/lib/components/organisms/session-list-modal/types.d.ts +27 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +6 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, InjectionToken, makeEnvironmentProviders, APP_INITIALIZER, ErrorHandler, Injectable, DestroyRef, Inject, signal, computed, PLATFORM_ID, isDevMode, Optional, runInInjectionContext, effect, Pipe, TemplateRef, ViewContainerRef, input, Directive, EventEmitter, Component, Input, Output, ChangeDetectionStrategy, HostBinding, HostListener, NgZone, ViewChild, ChangeDetectorRef, output, ContentChild, ElementRef, viewChild, Injector, isSignal, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { inject, InjectionToken, makeEnvironmentProviders, APP_INITIALIZER, ErrorHandler, Injectable, DestroyRef, Inject, signal, computed, PLATFORM_ID, isDevMode, Optional, runInInjectionContext, effect, Pipe, TemplateRef, ViewContainerRef, input, Directive, EventEmitter, Component, Input, Output, ChangeDetectionStrategy, HostBinding, HostListener, NgZone, ViewChild, ChangeDetectorRef, output, ContentChild, ElementRef, viewChild, untracked, Injector, isSignal, ViewEncapsulation } from '@angular/core';
|
|
3
3
|
import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, shareReplay, race, timer, forkJoin } from 'rxjs';
|
|
4
4
|
import { catchError, switchMap, finalize, filter, take, map as map$1, tap, retry, timeout, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
|
|
5
5
|
import * as i1$3 from '@angular/common/http';
|
|
@@ -25,7 +25,7 @@ import { getApps, getApp, initializeApp as initializeApp$1 } from 'firebase/app'
|
|
|
25
25
|
import * as i2$1 from '@ionic/angular/standalone';
|
|
26
26
|
import { AlertController, 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, IonPopover, IonList, IonItem, IonRadioGroup, IonRadio, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonRippleEffect, IonAccordion, IonAccordionGroup, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonChip, IonNote, ToastController as ToastController$1, IonCol, IonRow, IonRefresher, IonRefresherContent, IonMenuButton, IonFooter, IonListHeader, IonInfiniteScroll, IonInfiniteScrollContent, IonGrid, MenuController, IonMenu, IonMenuToggle, IonSplitPane } from '@ionic/angular/standalone';
|
|
27
27
|
import { addIcons } from 'ionicons';
|
|
28
|
-
import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, peopleOutline, phonePortraitOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, ellipse, starOutline, starHalf, heartHalf, checkmarkCircle, timeOutline, flag, trendingUp, trendingDown, remove, analytics, people, cash, cart, eye, chatbubbleOutline, thumbsUpOutline, thumbsUp, happyOutline, happy, sadOutline, sad, chevronUp, pin, pencil, callOutline, logoWhatsapp, paperPlaneOutline, mailOutline, chevronDownCircleOutline, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, cloudOfflineOutline, documentOutline, attachOutline, cameraOutline, closeCircleOutline, imageOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, menuOutline } from 'ionicons/icons';
|
|
28
|
+
import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, peopleOutline, phonePortraitOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, ellipse, starOutline, starHalf, heartHalf, checkmarkCircle, timeOutline, flag, trendingUp, trendingDown, remove, analytics, people, cash, cart, eye, chatbubbleOutline, thumbsUpOutline, thumbsUp, happyOutline, happy, sadOutline, sad, chevronUp, pin, pencil, callOutline, logoWhatsapp, paperPlaneOutline, mailOutline, chevronDownCircleOutline, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, cloudOfflineOutline, documentOutline, attachOutline, cameraOutline, closeCircleOutline, imageOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, laptopOutline, menuOutline } from 'ionicons/icons';
|
|
29
29
|
import { Browser } from '@capacitor/browser';
|
|
30
30
|
import * as i1$6 from '@angular/platform-browser';
|
|
31
31
|
import { DomSanitizer, Meta, Title } from '@angular/platform-browser';
|
|
@@ -54,7 +54,7 @@ import 'prismjs/components/prism-json';
|
|
|
54
54
|
* Current version of valtech-components.
|
|
55
55
|
* This is automatically updated during the publish process.
|
|
56
56
|
*/
|
|
57
|
-
const VERSION = '2.0.
|
|
57
|
+
const VERSION = '2.0.985';
|
|
58
58
|
|
|
59
59
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
60
60
|
let isRefreshing = false;
|
|
@@ -40269,7 +40269,7 @@ class BottomNavComponent {
|
|
|
40269
40269
|
}
|
|
40270
40270
|
</div>
|
|
40271
40271
|
</nav>
|
|
40272
|
-
`, isInline: true, styles: [":host{display:block;position:fixed;bottom:0;left:0;right:0;z-index:100;pointer-events:none}.bottom-nav{--bottom-nav-bg: var(--ion-background-color);--bottom-nav-active: var(--ion-color-primary);--bottom-nav-inactive: var(--ion-color-medium);--bottom-nav-radius: 16px 16px 0 0;--bottom-nav-height: 56px;--bottom-nav-fab-size: 56px;--bottom-nav-max-width: var(--val-container-md, 720px);--fab-color: var(--ion-color-primary);pointer-events:auto;background:var(--bottom-nav-bg);border-radius:var(--bottom-nav-radius);height:var(--bottom-nav-height);padding:0 8px;max-width:var(--bottom-nav-max-width);margin:0 auto}@media (min-width: 576px){.bottom-nav{--bottom-nav-height: 64px}}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000014}.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.75);backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border-top:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.06)}.bottom-nav--floating{margin:0 auto 8px;width:calc(100% - 32px);border-radius:32px;border:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.08);box-shadow:0 4px 24px #00000014,0 8px 32px #0000000a;--bottom-nav-radius: 32px}.bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.82);backdrop-filter:blur(28px) saturate(200%);-webkit-backdrop-filter:blur(28px) saturate(200%);border:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.08)}.bottom-nav--floating.bottom-nav--elevated{box-shadow:0 4px 24px #0000001a,0 12px 48px #00000014}.bottom-nav--safe-area{padding-bottom:env(safe-area-inset-bottom,0);height:calc(var(--bottom-nav-height) + env(safe-area-inset-bottom,0))}.bottom-nav--floating.bottom-nav--safe-area{margin-bottom:calc(8px + env(safe-area-inset-bottom,0));padding-bottom:0;height:var(--bottom-nav-height)}.bottom-nav__container{display:flex;align-items:center;justify-content:space-around;height:var(--bottom-nav-height);max-width:500px;margin:0 auto}.bottom-nav__tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;height:100%;padding:8px 4px;background:transparent;border:none;cursor:pointer;position:relative;overflow:hidden;color:var(--bottom-nav-inactive);transition:color .2s ease;max-width:80px;--ripple-color: var(--bottom-nav-active)}.bottom-nav__tab:focus-visible{outline:2px solid var(--bottom-nav-active);outline-offset:-2px;border-radius:8px}.bottom-nav__tab--active{color:var(--bottom-nav-active)}.bottom-nav__tab--disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.bottom-nav__tab[data-animation=scale] .bottom-nav__tab-icon{transition:transform .2s cubic-bezier(.4,0,.2,1)}.bottom-nav__tab[data-animation=scale].bottom-nav__tab--active .bottom-nav__tab-icon{transform:scale(1.15)}.bottom-nav__tab[data-animation=fade]{transition:opacity .2s ease,color .2s ease}.bottom-nav__tab[data-animation=fade]:not(.bottom-nav__tab--active){opacity:.6}.bottom-nav__tab[data-animation=slide] .bottom-nav__tab-label{transition:transform .2s ease,opacity .2s ease;transform:translateY(6px);opacity:0}.bottom-nav__tab[data-animation=slide].bottom-nav__tab--active .bottom-nav__tab-label{transform:translateY(0);opacity:1}.bottom-nav__tab-icon{position:relative;font-size:1.5rem;line-height:1}.bottom-nav__tab-icon ion-icon{display:block}.bottom-nav__tab-label{font-size:.6875rem;font-weight:500;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;letter-spacing:.01em}.bottom-nav__badge{position:absolute;top:-4px;right:-10px;min-width:18px;height:18px;padding:0 5px;font-size:.625rem;font-weight:600;line-height:18px;text-align:center;color:#fff;background-color:var(--ion-color-danger);border-radius:9px;box-shadow:0 1px 3px #0003}.bottom-nav__badge--dot{min-width:10px;width:10px;height:10px;padding:0;top:-2px;right:-4px;border-radius:50%}.bottom-nav__fab{position:relative;flex-shrink:0;width:var(--bottom-nav-fab-size);height:var(--bottom-nav-fab-size);margin:0 12px;margin-top:calc(var(--bottom-nav-fab-size) * -.35);background:var(--fab-color);border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;font-size:1.75rem;box-shadow:0 4px 14px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.4);transition:transform .2s cubic-bezier(.4,0,.2,1),box-shadow .2s ease;overflow:hidden;--ripple-color: rgba(255, 255, 255, .3)}.bottom-nav__fab:hover{transform:scale(1.08);box-shadow:0 6px 20px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.5)}.bottom-nav__fab:active{transform:scale(.95)}.bottom-nav__fab:focus-visible{outline:3px solid white;outline-offset:2px}.bottom-nav__fab--small{--bottom-nav-fab-size: 48px;font-size:1.5rem;margin-top:-14.4px}.bottom-nav__fab ion-icon{display:block}.bottom-nav--hide-labels{--bottom-nav-height: 56px}.bottom-nav--hide-labels .bottom-nav__tab-label{display:none}.bottom-nav--hide-labels .bottom-nav__tab-icon{font-size:1.625rem}:host-context(.dark) .bottom-nav--translucent,:host-context(.ion-palette-dark) .bottom-nav--translucent,:host-context(html.ion-palette-dark) .bottom-nav--translucent,:host-context(body.dark) .bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.85);border-top-color:#ffffff14}:host-context(.dark) .bottom-nav--elevated,:host-context(.ion-palette-dark) .bottom-nav--elevated,:host-context(html.ion-palette-dark) .bottom-nav--elevated,:host-context(body.dark) .bottom-nav--elevated,:host-context([data-theme=dark]) .bottom-nav--elevated{box-shadow:0 -4px 20px #0000004d}:host-context(.dark) .bottom-nav--floating,:host-context(.ion-palette-dark) .bottom-nav--floating,:host-context(html.ion-palette-dark) .bottom-nav--floating,:host-context(body.dark) .bottom-nav--floating,:host-context([data-theme=dark]) .bottom-nav--floating{border-color:#ffffff1a;box-shadow:0 4px 24px #0003,0 8px 32px #00000026}:host-context(.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(.ion-palette-dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(html.ion-palette-dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(body.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.88);border-color:#ffffff1a}:host-context(.dark) .bottom-nav__fab,:host-context(.ion-palette-dark) .bottom-nav__fab,:host-context(html.ion-palette-dark) .bottom-nav__fab,:host-context(body.dark) .bottom-nav__fab,:host-context([data-theme=dark]) .bottom-nav__fab{box-shadow:0 4px 14px #00000080}:host-context(.dark) .bottom-nav__fab:hover,:host-context(.ion-palette-dark) .bottom-nav__fab:hover,:host-context(html.ion-palette-dark) .bottom-nav__fab:hover,:host-context(body.dark) .bottom-nav__fab:hover,:host-context([data-theme=dark]) .bottom-nav__fab:hover{box-shadow:0 6px 20px #0009}@supports (padding-bottom: env(safe-area-inset-bottom)){.bottom-nav--safe-area .bottom-nav__container{height:var(--bottom-nav-height)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$5.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonRippleEffect, selector: "ion-ripple-effect", inputs: ["type"] }] }); }
|
|
40272
|
+
`, isInline: true, styles: [":host{display:block;position:fixed;bottom:0;left:0;right:0;z-index:100;pointer-events:none}.bottom-nav{--bottom-nav-bg: var(--ion-background-color);--bottom-nav-active: var(--ion-color-primary);--bottom-nav-inactive: var(--ion-color-medium);--bottom-nav-radius: 16px 16px 0 0;--bottom-nav-height: 56px;--bottom-nav-fab-size: 56px;--bottom-nav-max-width: var(--val-container-md, 720px);--fab-color: var(--ion-color-primary);pointer-events:auto;background:var(--bottom-nav-bg);border-radius:var(--bottom-nav-radius);height:var(--bottom-nav-height);padding:0 8px;max-width:var(--bottom-nav-max-width);margin:0 auto}@media (min-width: 576px){.bottom-nav{--bottom-nav-height: 64px}}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000014}.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.75);backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border-top:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.06)}.bottom-nav--floating{margin:0 auto 8px;width:calc(100% - 32px);border-radius:32px;border:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.08);box-shadow:0 4px 24px #00000014,0 8px 32px #0000000a;--bottom-nav-radius: 32px}.bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.82);backdrop-filter:blur(28px) saturate(200%);-webkit-backdrop-filter:blur(28px) saturate(200%);border:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.08)}.bottom-nav--floating.bottom-nav--elevated{box-shadow:0 4px 24px #0000001a,0 12px 48px #00000014}.bottom-nav--safe-area{padding-bottom:env(safe-area-inset-bottom,0);height:calc(var(--bottom-nav-height) + env(safe-area-inset-bottom,0))}.bottom-nav--floating.bottom-nav--safe-area{margin-bottom:calc(8px + env(safe-area-inset-bottom,0));padding-bottom:0;height:var(--bottom-nav-height)}.bottom-nav__container{display:flex;align-items:center;justify-content:space-around;height:var(--bottom-nav-height);max-width:500px;margin:0 auto}.bottom-nav__tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;height:100%;padding:8px 4px;background:transparent;border:none;cursor:pointer;position:relative;overflow:hidden;color:var(--bottom-nav-inactive);transition:color .2s ease;max-width:80px;--ripple-color: var(--bottom-nav-active)}.bottom-nav__tab:focus-visible{outline:2px solid var(--bottom-nav-active);outline-offset:-2px;border-radius:8px}.bottom-nav__tab--active{color:var(--bottom-nav-active)}.bottom-nav__tab--disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.bottom-nav__tab[data-animation=scale] .bottom-nav__tab-icon{transition:transform .2s cubic-bezier(.4,0,.2,1)}.bottom-nav__tab[data-animation=scale].bottom-nav__tab--active .bottom-nav__tab-icon{transform:scale(1.15)}.bottom-nav__tab[data-animation=fade]{transition:opacity .2s ease,color .2s ease}.bottom-nav__tab[data-animation=fade]:not(.bottom-nav__tab--active){opacity:.6}.bottom-nav__tab[data-animation=slide] .bottom-nav__tab-label{transition:transform .2s ease,opacity .2s ease;transform:translateY(6px);opacity:0}.bottom-nav__tab[data-animation=slide].bottom-nav__tab--active .bottom-nav__tab-label{transform:translateY(0);opacity:1}.bottom-nav__tab-icon{position:relative;font-size:1.5rem;line-height:1}.bottom-nav__tab-icon ion-icon{display:block}.bottom-nav__tab-label{font-size:.6875rem;font-weight:500;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;letter-spacing:.01em}.bottom-nav__badge{position:absolute;top:-4px;right:-10px;min-width:18px;height:18px;padding:0 5px;font-size:.625rem;font-weight:600;line-height:18px;text-align:center;color:#fff;background-color:var(--ion-color-danger);border-radius:9px;box-shadow:0 1px 3px #0003}.bottom-nav__badge--dot{min-width:10px;width:10px;height:10px;padding:0;top:-2px;right:-4px;border-radius:50%}.bottom-nav__fab{position:relative;flex-shrink:0;width:var(--bottom-nav-fab-size);height:var(--bottom-nav-fab-size);margin:0 12px;margin-top:calc(var(--bottom-nav-fab-size) * -.35);background:var(--fab-color);border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;font-size:1.75rem;box-shadow:0 4px 14px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.4);transition:transform .2s cubic-bezier(.4,0,.2,1),box-shadow .2s ease;overflow:hidden;--ripple-color: rgba(255, 255, 255, .3)}.bottom-nav__fab:hover{transform:scale(1.08);box-shadow:0 6px 20px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.5)}.bottom-nav__fab:active{transform:scale(.95)}.bottom-nav__fab:focus-visible{outline:3px solid white;outline-offset:2px}.bottom-nav__fab--small{--bottom-nav-fab-size: 48px;font-size:1.5rem;margin-top:-14.4px}.bottom-nav__fab ion-icon{display:block}.bottom-nav--hide-labels{--bottom-nav-height: 56px}.bottom-nav--hide-labels .bottom-nav__tab-label{display:none}.bottom-nav--hide-labels .bottom-nav__tab-icon{font-size:1.625rem}:host-context(body.font-large) .bottom-nav{--bottom-nav-height: 56px}:host-context(body.font-large) .bottom-nav .bottom-nav__tab-label{display:none}:host-context(body.font-large) .bottom-nav .bottom-nav__tab-icon{font-size:1.625rem}:host-context(.dark) .bottom-nav--translucent,:host-context(.ion-palette-dark) .bottom-nav--translucent,:host-context(html.ion-palette-dark) .bottom-nav--translucent,:host-context(body.dark) .bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.85);border-top-color:#ffffff14}:host-context(.dark) .bottom-nav--elevated,:host-context(.ion-palette-dark) .bottom-nav--elevated,:host-context(html.ion-palette-dark) .bottom-nav--elevated,:host-context(body.dark) .bottom-nav--elevated,:host-context([data-theme=dark]) .bottom-nav--elevated{box-shadow:0 -4px 20px #0000004d}:host-context(.dark) .bottom-nav--floating,:host-context(.ion-palette-dark) .bottom-nav--floating,:host-context(html.ion-palette-dark) .bottom-nav--floating,:host-context(body.dark) .bottom-nav--floating,:host-context([data-theme=dark]) .bottom-nav--floating{border-color:#ffffff1a;box-shadow:0 4px 24px #0003,0 8px 32px #00000026}:host-context(.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(.ion-palette-dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(html.ion-palette-dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(body.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.88);border-color:#ffffff1a}:host-context(.dark) .bottom-nav__fab,:host-context(.ion-palette-dark) .bottom-nav__fab,:host-context(html.ion-palette-dark) .bottom-nav__fab,:host-context(body.dark) .bottom-nav__fab,:host-context([data-theme=dark]) .bottom-nav__fab{box-shadow:0 4px 14px #00000080}:host-context(.dark) .bottom-nav__fab:hover,:host-context(.ion-palette-dark) .bottom-nav__fab:hover,:host-context(html.ion-palette-dark) .bottom-nav__fab:hover,:host-context(body.dark) .bottom-nav__fab:hover,:host-context([data-theme=dark]) .bottom-nav__fab:hover{box-shadow:0 6px 20px #0009}@supports (padding-bottom: env(safe-area-inset-bottom)){.bottom-nav--safe-area .bottom-nav__container{height:var(--bottom-nav-height)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$5.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonRippleEffect, selector: "ion-ripple-effect", inputs: ["type"] }] }); }
|
|
40273
40273
|
}
|
|
40274
40274
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BottomNavComponent, decorators: [{
|
|
40275
40275
|
type: Component,
|
|
@@ -40343,7 +40343,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
40343
40343
|
}
|
|
40344
40344
|
</div>
|
|
40345
40345
|
</nav>
|
|
40346
|
-
`, styles: [":host{display:block;position:fixed;bottom:0;left:0;right:0;z-index:100;pointer-events:none}.bottom-nav{--bottom-nav-bg: var(--ion-background-color);--bottom-nav-active: var(--ion-color-primary);--bottom-nav-inactive: var(--ion-color-medium);--bottom-nav-radius: 16px 16px 0 0;--bottom-nav-height: 56px;--bottom-nav-fab-size: 56px;--bottom-nav-max-width: var(--val-container-md, 720px);--fab-color: var(--ion-color-primary);pointer-events:auto;background:var(--bottom-nav-bg);border-radius:var(--bottom-nav-radius);height:var(--bottom-nav-height);padding:0 8px;max-width:var(--bottom-nav-max-width);margin:0 auto}@media (min-width: 576px){.bottom-nav{--bottom-nav-height: 64px}}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000014}.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.75);backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border-top:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.06)}.bottom-nav--floating{margin:0 auto 8px;width:calc(100% - 32px);border-radius:32px;border:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.08);box-shadow:0 4px 24px #00000014,0 8px 32px #0000000a;--bottom-nav-radius: 32px}.bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.82);backdrop-filter:blur(28px) saturate(200%);-webkit-backdrop-filter:blur(28px) saturate(200%);border:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.08)}.bottom-nav--floating.bottom-nav--elevated{box-shadow:0 4px 24px #0000001a,0 12px 48px #00000014}.bottom-nav--safe-area{padding-bottom:env(safe-area-inset-bottom,0);height:calc(var(--bottom-nav-height) + env(safe-area-inset-bottom,0))}.bottom-nav--floating.bottom-nav--safe-area{margin-bottom:calc(8px + env(safe-area-inset-bottom,0));padding-bottom:0;height:var(--bottom-nav-height)}.bottom-nav__container{display:flex;align-items:center;justify-content:space-around;height:var(--bottom-nav-height);max-width:500px;margin:0 auto}.bottom-nav__tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;height:100%;padding:8px 4px;background:transparent;border:none;cursor:pointer;position:relative;overflow:hidden;color:var(--bottom-nav-inactive);transition:color .2s ease;max-width:80px;--ripple-color: var(--bottom-nav-active)}.bottom-nav__tab:focus-visible{outline:2px solid var(--bottom-nav-active);outline-offset:-2px;border-radius:8px}.bottom-nav__tab--active{color:var(--bottom-nav-active)}.bottom-nav__tab--disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.bottom-nav__tab[data-animation=scale] .bottom-nav__tab-icon{transition:transform .2s cubic-bezier(.4,0,.2,1)}.bottom-nav__tab[data-animation=scale].bottom-nav__tab--active .bottom-nav__tab-icon{transform:scale(1.15)}.bottom-nav__tab[data-animation=fade]{transition:opacity .2s ease,color .2s ease}.bottom-nav__tab[data-animation=fade]:not(.bottom-nav__tab--active){opacity:.6}.bottom-nav__tab[data-animation=slide] .bottom-nav__tab-label{transition:transform .2s ease,opacity .2s ease;transform:translateY(6px);opacity:0}.bottom-nav__tab[data-animation=slide].bottom-nav__tab--active .bottom-nav__tab-label{transform:translateY(0);opacity:1}.bottom-nav__tab-icon{position:relative;font-size:1.5rem;line-height:1}.bottom-nav__tab-icon ion-icon{display:block}.bottom-nav__tab-label{font-size:.6875rem;font-weight:500;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;letter-spacing:.01em}.bottom-nav__badge{position:absolute;top:-4px;right:-10px;min-width:18px;height:18px;padding:0 5px;font-size:.625rem;font-weight:600;line-height:18px;text-align:center;color:#fff;background-color:var(--ion-color-danger);border-radius:9px;box-shadow:0 1px 3px #0003}.bottom-nav__badge--dot{min-width:10px;width:10px;height:10px;padding:0;top:-2px;right:-4px;border-radius:50%}.bottom-nav__fab{position:relative;flex-shrink:0;width:var(--bottom-nav-fab-size);height:var(--bottom-nav-fab-size);margin:0 12px;margin-top:calc(var(--bottom-nav-fab-size) * -.35);background:var(--fab-color);border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;font-size:1.75rem;box-shadow:0 4px 14px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.4);transition:transform .2s cubic-bezier(.4,0,.2,1),box-shadow .2s ease;overflow:hidden;--ripple-color: rgba(255, 255, 255, .3)}.bottom-nav__fab:hover{transform:scale(1.08);box-shadow:0 6px 20px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.5)}.bottom-nav__fab:active{transform:scale(.95)}.bottom-nav__fab:focus-visible{outline:3px solid white;outline-offset:2px}.bottom-nav__fab--small{--bottom-nav-fab-size: 48px;font-size:1.5rem;margin-top:-14.4px}.bottom-nav__fab ion-icon{display:block}.bottom-nav--hide-labels{--bottom-nav-height: 56px}.bottom-nav--hide-labels .bottom-nav__tab-label{display:none}.bottom-nav--hide-labels .bottom-nav__tab-icon{font-size:1.625rem}:host-context(.dark) .bottom-nav--translucent,:host-context(.ion-palette-dark) .bottom-nav--translucent,:host-context(html.ion-palette-dark) .bottom-nav--translucent,:host-context(body.dark) .bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.85);border-top-color:#ffffff14}:host-context(.dark) .bottom-nav--elevated,:host-context(.ion-palette-dark) .bottom-nav--elevated,:host-context(html.ion-palette-dark) .bottom-nav--elevated,:host-context(body.dark) .bottom-nav--elevated,:host-context([data-theme=dark]) .bottom-nav--elevated{box-shadow:0 -4px 20px #0000004d}:host-context(.dark) .bottom-nav--floating,:host-context(.ion-palette-dark) .bottom-nav--floating,:host-context(html.ion-palette-dark) .bottom-nav--floating,:host-context(body.dark) .bottom-nav--floating,:host-context([data-theme=dark]) .bottom-nav--floating{border-color:#ffffff1a;box-shadow:0 4px 24px #0003,0 8px 32px #00000026}:host-context(.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(.ion-palette-dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(html.ion-palette-dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(body.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.88);border-color:#ffffff1a}:host-context(.dark) .bottom-nav__fab,:host-context(.ion-palette-dark) .bottom-nav__fab,:host-context(html.ion-palette-dark) .bottom-nav__fab,:host-context(body.dark) .bottom-nav__fab,:host-context([data-theme=dark]) .bottom-nav__fab{box-shadow:0 4px 14px #00000080}:host-context(.dark) .bottom-nav__fab:hover,:host-context(.ion-palette-dark) .bottom-nav__fab:hover,:host-context(html.ion-palette-dark) .bottom-nav__fab:hover,:host-context(body.dark) .bottom-nav__fab:hover,:host-context([data-theme=dark]) .bottom-nav__fab:hover{box-shadow:0 6px 20px #0009}@supports (padding-bottom: env(safe-area-inset-bottom)){.bottom-nav--safe-area .bottom-nav__container{height:var(--bottom-nav-height)}}\n"] }]
|
|
40346
|
+
`, styles: [":host{display:block;position:fixed;bottom:0;left:0;right:0;z-index:100;pointer-events:none}.bottom-nav{--bottom-nav-bg: var(--ion-background-color);--bottom-nav-active: var(--ion-color-primary);--bottom-nav-inactive: var(--ion-color-medium);--bottom-nav-radius: 16px 16px 0 0;--bottom-nav-height: 56px;--bottom-nav-fab-size: 56px;--bottom-nav-max-width: var(--val-container-md, 720px);--fab-color: var(--ion-color-primary);pointer-events:auto;background:var(--bottom-nav-bg);border-radius:var(--bottom-nav-radius);height:var(--bottom-nav-height);padding:0 8px;max-width:var(--bottom-nav-max-width);margin:0 auto}@media (min-width: 576px){.bottom-nav{--bottom-nav-height: 64px}}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000014}.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.75);backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border-top:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.06)}.bottom-nav--floating{margin:0 auto 8px;width:calc(100% - 32px);border-radius:32px;border:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.08);box-shadow:0 4px 24px #00000014,0 8px 32px #0000000a;--bottom-nav-radius: 32px}.bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.82);backdrop-filter:blur(28px) saturate(200%);-webkit-backdrop-filter:blur(28px) saturate(200%);border:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.08)}.bottom-nav--floating.bottom-nav--elevated{box-shadow:0 4px 24px #0000001a,0 12px 48px #00000014}.bottom-nav--safe-area{padding-bottom:env(safe-area-inset-bottom,0);height:calc(var(--bottom-nav-height) + env(safe-area-inset-bottom,0))}.bottom-nav--floating.bottom-nav--safe-area{margin-bottom:calc(8px + env(safe-area-inset-bottom,0));padding-bottom:0;height:var(--bottom-nav-height)}.bottom-nav__container{display:flex;align-items:center;justify-content:space-around;height:var(--bottom-nav-height);max-width:500px;margin:0 auto}.bottom-nav__tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;height:100%;padding:8px 4px;background:transparent;border:none;cursor:pointer;position:relative;overflow:hidden;color:var(--bottom-nav-inactive);transition:color .2s ease;max-width:80px;--ripple-color: var(--bottom-nav-active)}.bottom-nav__tab:focus-visible{outline:2px solid var(--bottom-nav-active);outline-offset:-2px;border-radius:8px}.bottom-nav__tab--active{color:var(--bottom-nav-active)}.bottom-nav__tab--disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.bottom-nav__tab[data-animation=scale] .bottom-nav__tab-icon{transition:transform .2s cubic-bezier(.4,0,.2,1)}.bottom-nav__tab[data-animation=scale].bottom-nav__tab--active .bottom-nav__tab-icon{transform:scale(1.15)}.bottom-nav__tab[data-animation=fade]{transition:opacity .2s ease,color .2s ease}.bottom-nav__tab[data-animation=fade]:not(.bottom-nav__tab--active){opacity:.6}.bottom-nav__tab[data-animation=slide] .bottom-nav__tab-label{transition:transform .2s ease,opacity .2s ease;transform:translateY(6px);opacity:0}.bottom-nav__tab[data-animation=slide].bottom-nav__tab--active .bottom-nav__tab-label{transform:translateY(0);opacity:1}.bottom-nav__tab-icon{position:relative;font-size:1.5rem;line-height:1}.bottom-nav__tab-icon ion-icon{display:block}.bottom-nav__tab-label{font-size:.6875rem;font-weight:500;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;letter-spacing:.01em}.bottom-nav__badge{position:absolute;top:-4px;right:-10px;min-width:18px;height:18px;padding:0 5px;font-size:.625rem;font-weight:600;line-height:18px;text-align:center;color:#fff;background-color:var(--ion-color-danger);border-radius:9px;box-shadow:0 1px 3px #0003}.bottom-nav__badge--dot{min-width:10px;width:10px;height:10px;padding:0;top:-2px;right:-4px;border-radius:50%}.bottom-nav__fab{position:relative;flex-shrink:0;width:var(--bottom-nav-fab-size);height:var(--bottom-nav-fab-size);margin:0 12px;margin-top:calc(var(--bottom-nav-fab-size) * -.35);background:var(--fab-color);border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;font-size:1.75rem;box-shadow:0 4px 14px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.4);transition:transform .2s cubic-bezier(.4,0,.2,1),box-shadow .2s ease;overflow:hidden;--ripple-color: rgba(255, 255, 255, .3)}.bottom-nav__fab:hover{transform:scale(1.08);box-shadow:0 6px 20px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.5)}.bottom-nav__fab:active{transform:scale(.95)}.bottom-nav__fab:focus-visible{outline:3px solid white;outline-offset:2px}.bottom-nav__fab--small{--bottom-nav-fab-size: 48px;font-size:1.5rem;margin-top:-14.4px}.bottom-nav__fab ion-icon{display:block}.bottom-nav--hide-labels{--bottom-nav-height: 56px}.bottom-nav--hide-labels .bottom-nav__tab-label{display:none}.bottom-nav--hide-labels .bottom-nav__tab-icon{font-size:1.625rem}:host-context(body.font-large) .bottom-nav{--bottom-nav-height: 56px}:host-context(body.font-large) .bottom-nav .bottom-nav__tab-label{display:none}:host-context(body.font-large) .bottom-nav .bottom-nav__tab-icon{font-size:1.625rem}:host-context(.dark) .bottom-nav--translucent,:host-context(.ion-palette-dark) .bottom-nav--translucent,:host-context(html.ion-palette-dark) .bottom-nav--translucent,:host-context(body.dark) .bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.85);border-top-color:#ffffff14}:host-context(.dark) .bottom-nav--elevated,:host-context(.ion-palette-dark) .bottom-nav--elevated,:host-context(html.ion-palette-dark) .bottom-nav--elevated,:host-context(body.dark) .bottom-nav--elevated,:host-context([data-theme=dark]) .bottom-nav--elevated{box-shadow:0 -4px 20px #0000004d}:host-context(.dark) .bottom-nav--floating,:host-context(.ion-palette-dark) .bottom-nav--floating,:host-context(html.ion-palette-dark) .bottom-nav--floating,:host-context(body.dark) .bottom-nav--floating,:host-context([data-theme=dark]) .bottom-nav--floating{border-color:#ffffff1a;box-shadow:0 4px 24px #0003,0 8px 32px #00000026}:host-context(.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(.ion-palette-dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(html.ion-palette-dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(body.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.88);border-color:#ffffff1a}:host-context(.dark) .bottom-nav__fab,:host-context(.ion-palette-dark) .bottom-nav__fab,:host-context(html.ion-palette-dark) .bottom-nav__fab,:host-context(body.dark) .bottom-nav__fab,:host-context([data-theme=dark]) .bottom-nav__fab{box-shadow:0 4px 14px #00000080}:host-context(.dark) .bottom-nav__fab:hover,:host-context(.ion-palette-dark) .bottom-nav__fab:hover,:host-context(html.ion-palette-dark) .bottom-nav__fab:hover,:host-context(body.dark) .bottom-nav__fab:hover,:host-context([data-theme=dark]) .bottom-nav__fab:hover{box-shadow:0 6px 20px #0009}@supports (padding-bottom: env(safe-area-inset-bottom)){.bottom-nav--safe-area .bottom-nav__container{height:var(--bottom-nav-height)}}\n"] }]
|
|
40347
40347
|
}], propDecorators: { tabClick: [{
|
|
40348
40348
|
type: Output
|
|
40349
40349
|
}], fabClick: [{
|
|
@@ -41652,7 +41652,7 @@ const PROFILE_VIEW_I18N = {
|
|
|
41652
41652
|
const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
|
|
41653
41653
|
const HANDLE_MIN = 3;
|
|
41654
41654
|
const HANDLE_MAX = 20;
|
|
41655
|
-
const DEFAULT_NAMESPACE$
|
|
41655
|
+
const DEFAULT_NAMESPACE$2 = 'Settings.Profile';
|
|
41656
41656
|
/**
|
|
41657
41657
|
* `val-profile-view` — vista Perfil full-feature autocontenida (organism).
|
|
41658
41658
|
*
|
|
@@ -41702,7 +41702,7 @@ class ProfileViewComponent {
|
|
|
41702
41702
|
showPhone: merged.showPhone ?? true,
|
|
41703
41703
|
showShareCta: merged.showShareCta ?? true,
|
|
41704
41704
|
profileBaseUrl: merged.profileBaseUrl ?? '',
|
|
41705
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
41705
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$2,
|
|
41706
41706
|
onSaved: merged.onSaved,
|
|
41707
41707
|
onAvatarUploaded: merged.onAvatarUploaded,
|
|
41708
41708
|
};
|
|
@@ -42342,6 +42342,8 @@ class FontSizeService {
|
|
|
42342
42342
|
if (!this.isBrowser)
|
|
42343
42343
|
return;
|
|
42344
42344
|
document.documentElement.style.fontSize = SIZE_PX[size];
|
|
42345
|
+
document.body.classList.toggle('font-large', size === FontSizeOption.LARGE);
|
|
42346
|
+
document.body.classList.toggle('font-small', size === FontSizeOption.SMALL);
|
|
42345
42347
|
}
|
|
42346
42348
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FontSizeService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
42347
42349
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FontSizeService, providedIn: 'root' }); }
|
|
@@ -42616,7 +42618,7 @@ const PREFERENCES_VIEW_I18N = {
|
|
|
42616
42618
|
},
|
|
42617
42619
|
};
|
|
42618
42620
|
|
|
42619
|
-
const DEFAULT_NAMESPACE = 'Settings.General';
|
|
42621
|
+
const DEFAULT_NAMESPACE$1 = 'Settings.General';
|
|
42620
42622
|
const DEFAULT_LANGUAGES = ['es', 'en'];
|
|
42621
42623
|
/**
|
|
42622
42624
|
* `val-preferences-view` — vista de preferencias de UI cross-app (tema +
|
|
@@ -42653,7 +42655,7 @@ class PreferencesViewComponent {
|
|
|
42653
42655
|
showLanguage: merged.showLanguage ?? true,
|
|
42654
42656
|
showFontSize: merged.showFontSize ?? true,
|
|
42655
42657
|
supportedLanguages: merged.supportedLanguages ?? DEFAULT_LANGUAGES,
|
|
42656
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
42658
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$1,
|
|
42657
42659
|
onThemeChanged: merged.onThemeChanged,
|
|
42658
42660
|
onLanguageChanged: merged.onLanguageChanged,
|
|
42659
42661
|
onFontSizeChanged: merged.onFontSizeChanged,
|
|
@@ -43357,387 +43359,2308 @@ function provideValtechSettingsRoutes(opts) {
|
|
|
43357
43359
|
];
|
|
43358
43360
|
}
|
|
43359
43361
|
|
|
43360
|
-
|
|
43361
|
-
const CALLOUT_LABELS = {
|
|
43362
|
-
es: {
|
|
43363
|
-
NOTE: 'Nota',
|
|
43364
|
-
TIP: 'Tip',
|
|
43365
|
-
INFO: 'Info',
|
|
43366
|
-
IMPORTANT: 'Importante',
|
|
43367
|
-
WARNING: 'Atención',
|
|
43368
|
-
CAUTION: 'Precaución',
|
|
43369
|
-
},
|
|
43370
|
-
en: {
|
|
43371
|
-
NOTE: 'Note',
|
|
43372
|
-
TIP: 'Tip',
|
|
43373
|
-
INFO: 'Info',
|
|
43374
|
-
IMPORTANT: 'Important',
|
|
43375
|
-
WARNING: 'Warning',
|
|
43376
|
-
CAUTION: 'Caution',
|
|
43377
|
-
},
|
|
43378
|
-
pt: {
|
|
43379
|
-
NOTE: 'Nota',
|
|
43380
|
-
TIP: 'Dica',
|
|
43381
|
-
INFO: 'Info',
|
|
43382
|
-
IMPORTANT: 'Importante',
|
|
43383
|
-
WARNING: 'Atenção',
|
|
43384
|
-
CAUTION: 'Cuidado',
|
|
43385
|
-
},
|
|
43386
|
-
};
|
|
43387
|
-
/**
|
|
43388
|
-
* Per-callout-kind color (Ionic semantic color). Fixed across locales since
|
|
43389
|
-
* red = danger no matter what language you speak.
|
|
43390
|
-
*/
|
|
43391
|
-
const CALLOUT_COLORS = {
|
|
43392
|
-
NOTE: 'primary',
|
|
43393
|
-
TIP: 'success',
|
|
43394
|
-
INFO: 'tertiary',
|
|
43395
|
-
IMPORTANT: 'warning',
|
|
43396
|
-
WARNING: 'warning',
|
|
43397
|
-
CAUTION: 'danger',
|
|
43398
|
-
};
|
|
43399
|
-
const BOX_DRAWING = /[┌┐└┘├┤┬┴┼─│╔╗╚╝═║]/;
|
|
43400
|
-
const CALLOUT_RE = /^>\s*\[!(NOTE|TIP|INFO|WARNING|CAUTION|IMPORTANT)\]\s*(.*)$/i;
|
|
43401
|
-
const FENCE_RE = /^```([\w-]*)\s*$/;
|
|
43402
|
-
const HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
43403
|
-
const SEPARATOR_RE = /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
|
|
43404
|
-
const UNORDERED_RE = /^\s*[-*+]\s+(.+)$/;
|
|
43405
|
-
const CHECKLIST_RE = /^\s*[-*+]\s+\[([ xX])\]\s+(.+)$/;
|
|
43406
|
-
const ORDERED_RE = /^\s*\d+\.\s+(.+)$/;
|
|
43407
|
-
const TABLE_DIVIDER_RE = /^\s*\|?[\s:|-]+\|?\s*$/;
|
|
43408
|
-
const TABLE_ROW_RE = /^\s*\|(.+)\|\s*$/;
|
|
43362
|
+
addIcons({ shieldCheckmarkOutline, checkmarkCircleOutline });
|
|
43409
43363
|
/**
|
|
43410
|
-
*
|
|
43411
|
-
* (build-time generation) and from the Angular `MarkdownArticleParserService` wrapper.
|
|
43364
|
+
* `val-login-attempt-modal` — modal de confirmación de intento de inicio de sesión.
|
|
43412
43365
|
*
|
|
43413
|
-
*
|
|
43414
|
-
*
|
|
43415
|
-
*
|
|
43416
|
-
*
|
|
43417
|
-
*
|
|
43418
|
-
*
|
|
43419
|
-
*
|
|
43420
|
-
*
|
|
43421
|
-
*
|
|
43366
|
+
* Se abre cuando el usuario llega a la vista Seguridad vía
|
|
43367
|
+
* `?token=...` desde el email/push de "nuevo acceso". Muestra el dispositivo
|
|
43368
|
+
* detectado y dos acciones: confirmar que fue uno mismo (cierra sin hacer nada)
|
|
43369
|
+
* o bloquear el dispositivo (ejecuta el token de acción).
|
|
43370
|
+
*
|
|
43371
|
+
* Inputs vía `componentProps` del ModalService. Sin `@Output` (no funcionan con
|
|
43372
|
+
* `modalController.create()`) — usa callback `onBlock` y `_modalRef.dismiss`.
|
|
43373
|
+
* Recibe los labels ya traducidos (agnóstico a i18n).
|
|
43374
|
+
*
|
|
43375
|
+
* Header canónico (Regla #5): sin `ion-title`, botón Cerrar texto en `slot=end`,
|
|
43376
|
+
* título en el body con `val-display`.
|
|
43422
43377
|
*/
|
|
43423
|
-
|
|
43424
|
-
|
|
43425
|
-
|
|
43426
|
-
|
|
43427
|
-
|
|
43428
|
-
|
|
43429
|
-
|
|
43430
|
-
|
|
43431
|
-
|
|
43432
|
-
|
|
43433
|
-
|
|
43434
|
-
|
|
43435
|
-
|
|
43436
|
-
|
|
43437
|
-
|
|
43438
|
-
|
|
43439
|
-
|
|
43440
|
-
|
|
43441
|
-
|
|
43442
|
-
|
|
43443
|
-
|
|
43444
|
-
|
|
43445
|
-
|
|
43446
|
-
|
|
43447
|
-
|
|
43448
|
-
|
|
43449
|
-
|
|
43450
|
-
|
|
43451
|
-
|
|
43452
|
-
|
|
43453
|
-
|
|
43454
|
-
|
|
43455
|
-
|
|
43456
|
-
|
|
43457
|
-
|
|
43458
|
-
|
|
43459
|
-
|
|
43460
|
-
type: 'code',
|
|
43461
|
-
props: { code: body.join('\n'), language: 'text', theme: 'dark' },
|
|
43462
|
-
});
|
|
43463
|
-
continue;
|
|
43464
|
-
}
|
|
43465
|
-
if (SEPARATOR_RE.test(line)) {
|
|
43466
|
-
elements.push({ type: 'separator', props: { style: 'line' } });
|
|
43467
|
-
i++;
|
|
43468
|
-
continue;
|
|
43469
|
-
}
|
|
43470
|
-
const heading = line.match(HEADING_RE);
|
|
43471
|
-
if (heading) {
|
|
43472
|
-
elements.push(makeHeading(heading[1].length, heading[2].trim()));
|
|
43473
|
-
i++;
|
|
43474
|
-
continue;
|
|
43475
|
-
}
|
|
43476
|
-
if (line.startsWith('>')) {
|
|
43477
|
-
const block = [];
|
|
43478
|
-
while (i < lines.length && lines[i].startsWith('>')) {
|
|
43479
|
-
block.push(lines[i]);
|
|
43480
|
-
i++;
|
|
43481
|
-
}
|
|
43482
|
-
elements.push(makeQuoteOrCallout(block, labels));
|
|
43483
|
-
continue;
|
|
43484
|
-
}
|
|
43485
|
-
if (TABLE_ROW_RE.test(line)) {
|
|
43486
|
-
const rows = [];
|
|
43487
|
-
while (i < lines.length && TABLE_ROW_RE.test(lines[i])) {
|
|
43488
|
-
rows.push(lines[i]);
|
|
43489
|
-
i++;
|
|
43490
|
-
}
|
|
43491
|
-
elements.push(...makeTable(rows));
|
|
43492
|
-
continue;
|
|
43493
|
-
}
|
|
43494
|
-
if (CHECKLIST_RE.test(line) || UNORDERED_RE.test(line) || ORDERED_RE.test(line)) {
|
|
43495
|
-
const listType = CHECKLIST_RE.test(line)
|
|
43496
|
-
? 'checklist'
|
|
43497
|
-
: ORDERED_RE.test(line)
|
|
43498
|
-
? 'ordered'
|
|
43499
|
-
: 'unordered';
|
|
43500
|
-
const items = [];
|
|
43501
|
-
while (i < lines.length && lines[i].trim() !== '') {
|
|
43502
|
-
const cur = lines[i];
|
|
43503
|
-
const check = cur.match(CHECKLIST_RE);
|
|
43504
|
-
const unord = cur.match(UNORDERED_RE);
|
|
43505
|
-
const ord = cur.match(ORDERED_RE);
|
|
43506
|
-
if (check)
|
|
43507
|
-
items.push({ text: processInlineBold(check[2].trim()) });
|
|
43508
|
-
else if (ord && listType === 'ordered')
|
|
43509
|
-
items.push({ text: processInlineBold(ord[1].trim()) });
|
|
43510
|
-
else if (unord && listType !== 'ordered')
|
|
43511
|
-
items.push({ text: processInlineBold(unord[1].trim()) });
|
|
43512
|
-
else
|
|
43513
|
-
break;
|
|
43514
|
-
i++;
|
|
43515
|
-
}
|
|
43516
|
-
elements.push({ type: 'list', props: { items, listType } });
|
|
43517
|
-
continue;
|
|
43378
|
+
class LoginAttemptModalComponent {
|
|
43379
|
+
constructor() {
|
|
43380
|
+
/** Título del modal (i18n ya resuelto). */
|
|
43381
|
+
this.title = '';
|
|
43382
|
+
/** Texto explicativo. */
|
|
43383
|
+
this.body = '';
|
|
43384
|
+
/** Descripción del dispositivo (navegador · OS · ubicación). */
|
|
43385
|
+
this.deviceLabel = '';
|
|
43386
|
+
/** Metadata secundaria (fecha, IP). Opcional. */
|
|
43387
|
+
this.metaLabel = '';
|
|
43388
|
+
/** Mensaje mostrado tras bloquear. */
|
|
43389
|
+
this.doneLabel = '';
|
|
43390
|
+
/** Labels de botones. */
|
|
43391
|
+
this.mineLabel = '';
|
|
43392
|
+
this.blockLabel = '';
|
|
43393
|
+
this.closeLabel = '';
|
|
43394
|
+
/** `true` si el dispositivo es móvil — cambia el icono. */
|
|
43395
|
+
this.isMobile = false;
|
|
43396
|
+
/** `true` tras bloquear con éxito — cambia el modal a estado "hecho". */
|
|
43397
|
+
this.done = signal(false);
|
|
43398
|
+
this.working = false;
|
|
43399
|
+
}
|
|
43400
|
+
get deviceIcon() {
|
|
43401
|
+
return this.isMobile ? 'phone-portrait-outline' : 'laptop-outline';
|
|
43402
|
+
}
|
|
43403
|
+
/** "Sí, fui yo" — cierra sin acción. El dispositivo ya está activo. */
|
|
43404
|
+
confirmMine() {
|
|
43405
|
+
this._modalRef?.dismiss(undefined, 'mine');
|
|
43406
|
+
}
|
|
43407
|
+
/** "No fui yo" — ejecuta el bloqueo y pasa a estado done. */
|
|
43408
|
+
async confirmBlock() {
|
|
43409
|
+
if (this.working || !this.onBlock)
|
|
43410
|
+
return;
|
|
43411
|
+
this.working = true;
|
|
43412
|
+
try {
|
|
43413
|
+
await this.onBlock();
|
|
43414
|
+
this.done.set(true);
|
|
43518
43415
|
}
|
|
43519
|
-
|
|
43520
|
-
|
|
43521
|
-
|
|
43522
|
-
i++;
|
|
43416
|
+
catch {
|
|
43417
|
+
// El parent muestra el toast de error; cerramos el modal.
|
|
43418
|
+
this._modalRef?.dismiss(undefined, 'error');
|
|
43523
43419
|
}
|
|
43524
|
-
|
|
43525
|
-
|
|
43526
|
-
elements.push({
|
|
43527
|
-
type: 'paragraph',
|
|
43528
|
-
props: {
|
|
43529
|
-
content: text,
|
|
43530
|
-
size: 'medium',
|
|
43531
|
-
color: 'dark',
|
|
43532
|
-
bold: false,
|
|
43533
|
-
processLinks: true,
|
|
43534
|
-
allowPartialBold: true,
|
|
43535
|
-
},
|
|
43536
|
-
});
|
|
43420
|
+
finally {
|
|
43421
|
+
this.working = false;
|
|
43537
43422
|
}
|
|
43538
43423
|
}
|
|
43539
|
-
|
|
43540
|
-
|
|
43541
|
-
|
|
43542
|
-
|
|
43543
|
-
maxWidth: '900px',
|
|
43544
|
-
centered: true,
|
|
43545
|
-
theme: 'auto',
|
|
43546
|
-
...metadataOverrides,
|
|
43547
|
-
};
|
|
43548
|
-
}
|
|
43549
|
-
function normalize(md) {
|
|
43550
|
-
return md.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
43551
|
-
}
|
|
43552
|
-
function startsNewBlock(line) {
|
|
43553
|
-
return (HEADING_RE.test(line) ||
|
|
43554
|
-
FENCE_RE.test(line) ||
|
|
43555
|
-
SEPARATOR_RE.test(line) ||
|
|
43556
|
-
TABLE_ROW_RE.test(line) ||
|
|
43557
|
-
UNORDERED_RE.test(line) ||
|
|
43558
|
-
ORDERED_RE.test(line) ||
|
|
43559
|
-
CHECKLIST_RE.test(line) ||
|
|
43560
|
-
line.startsWith('>') ||
|
|
43561
|
-
BOX_DRAWING.test(line));
|
|
43562
|
-
}
|
|
43563
|
-
function processInlineBold(text) {
|
|
43564
|
-
return text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
43565
|
-
}
|
|
43566
|
-
function makeHeading(level, content) {
|
|
43567
|
-
if (level === 1) {
|
|
43424
|
+
close() {
|
|
43425
|
+
this._modalRef?.dismiss(undefined, this.done() ? 'blocked' : 'close');
|
|
43426
|
+
}
|
|
43427
|
+
get mineBtn() {
|
|
43568
43428
|
return {
|
|
43569
|
-
|
|
43570
|
-
|
|
43429
|
+
text: this.mineLabel,
|
|
43430
|
+
color: 'medium',
|
|
43431
|
+
fill: 'outline',
|
|
43432
|
+
size: 'default',
|
|
43433
|
+
type: 'button',
|
|
43434
|
+
state: ComponentStates.ENABLED,
|
|
43571
43435
|
};
|
|
43572
43436
|
}
|
|
43573
|
-
|
|
43574
|
-
|
|
43575
|
-
|
|
43576
|
-
|
|
43577
|
-
|
|
43578
|
-
|
|
43579
|
-
|
|
43580
|
-
|
|
43581
|
-
|
|
43582
|
-
const lines = block.map(l => l.replace(/^>\s?/, ''));
|
|
43583
|
-
if (callout) {
|
|
43584
|
-
const type = callout[1].toUpperCase();
|
|
43585
|
-
const firstLineRest = callout[2] || '';
|
|
43586
|
-
const rest = lines.slice(1).join(' ').trim();
|
|
43587
|
-
const text = processInlineBold([firstLineRest, rest].filter(Boolean).join(' ').trim());
|
|
43588
|
-
return makeNote(type, text, labels);
|
|
43437
|
+
get blockBtn() {
|
|
43438
|
+
return {
|
|
43439
|
+
text: this.blockLabel,
|
|
43440
|
+
color: 'danger',
|
|
43441
|
+
fill: 'solid',
|
|
43442
|
+
size: 'default',
|
|
43443
|
+
type: 'button',
|
|
43444
|
+
state: ComponentStates.ENABLED,
|
|
43445
|
+
};
|
|
43589
43446
|
}
|
|
43590
|
-
|
|
43591
|
-
|
|
43592
|
-
|
|
43593
|
-
|
|
43594
|
-
|
|
43447
|
+
get closeBtn() {
|
|
43448
|
+
return {
|
|
43449
|
+
text: this.closeLabel,
|
|
43450
|
+
color: 'dark',
|
|
43451
|
+
fill: 'solid',
|
|
43452
|
+
size: 'default',
|
|
43453
|
+
type: 'button',
|
|
43454
|
+
state: ComponentStates.ENABLED,
|
|
43455
|
+
};
|
|
43456
|
+
}
|
|
43457
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LoginAttemptModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
43458
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: LoginAttemptModalComponent, isStandalone: true, selector: "val-login-attempt-modal", inputs: { title: "title", body: "body", deviceLabel: "deviceLabel", metaLabel: "metaLabel", doneLabel: "doneLabel", mineLabel: "mineLabel", blockLabel: "blockLabel", closeLabel: "closeLabel", isMobile: "isMobile", onBlock: "onBlock", _modalRef: "_modalRef" }, ngImport: i0, template: `
|
|
43459
|
+
<ion-header>
|
|
43460
|
+
<ion-toolbar>
|
|
43461
|
+
<ion-buttons slot="end">
|
|
43462
|
+
<ion-button fill="clear" color="dark" (click)="close()">
|
|
43463
|
+
<strong>{{ closeLabel }}</strong>
|
|
43464
|
+
</ion-button>
|
|
43465
|
+
</ion-buttons>
|
|
43466
|
+
</ion-toolbar>
|
|
43467
|
+
</ion-header>
|
|
43468
|
+
<ion-content class="ion-padding">
|
|
43469
|
+
<val-display [props]="{ content: title, size: 'small', color: 'dark' }" />
|
|
43470
|
+
<div class="attempt">
|
|
43471
|
+
<div class="attempt__hero">
|
|
43472
|
+
<ion-icon name="shield-checkmark-outline" aria-hidden="true" />
|
|
43473
|
+
</div>
|
|
43474
|
+
|
|
43475
|
+
<val-text
|
|
43476
|
+
[props]="{
|
|
43477
|
+
content: body,
|
|
43595
43478
|
size: 'medium',
|
|
43596
|
-
color: '
|
|
43479
|
+
color: 'dark',
|
|
43597
43480
|
bold: false,
|
|
43598
|
-
|
|
43599
|
-
|
|
43600
|
-
|
|
43601
|
-
|
|
43481
|
+
}"
|
|
43482
|
+
/>
|
|
43483
|
+
|
|
43484
|
+
<div class="attempt__device">
|
|
43485
|
+
<ion-icon [name]="deviceIcon" aria-hidden="true" />
|
|
43486
|
+
<div class="attempt__device-body">
|
|
43487
|
+
<val-text
|
|
43488
|
+
[props]="{
|
|
43489
|
+
content: deviceLabel,
|
|
43490
|
+
size: 'medium',
|
|
43491
|
+
color: 'dark',
|
|
43492
|
+
bold: true,
|
|
43493
|
+
}"
|
|
43494
|
+
/>
|
|
43495
|
+
@if (metaLabel) {
|
|
43496
|
+
<val-text
|
|
43497
|
+
[props]="{
|
|
43498
|
+
content: metaLabel,
|
|
43499
|
+
size: 'small',
|
|
43500
|
+
color: 'medium',
|
|
43501
|
+
bold: false,
|
|
43502
|
+
}"
|
|
43503
|
+
/>
|
|
43504
|
+
}
|
|
43505
|
+
</div>
|
|
43506
|
+
</div>
|
|
43507
|
+
|
|
43508
|
+
@if (done()) {
|
|
43509
|
+
<div class="attempt__done">
|
|
43510
|
+
<ion-icon name="checkmark-circle-outline" aria-hidden="true" />
|
|
43511
|
+
<val-text
|
|
43512
|
+
[props]="{
|
|
43513
|
+
content: doneLabel,
|
|
43514
|
+
size: 'small',
|
|
43515
|
+
color: 'medium',
|
|
43516
|
+
bold: false,
|
|
43517
|
+
}"
|
|
43518
|
+
/>
|
|
43519
|
+
</div>
|
|
43520
|
+
}
|
|
43521
|
+
</div>
|
|
43522
|
+
</ion-content>
|
|
43523
|
+
|
|
43524
|
+
@if (!done()) {
|
|
43525
|
+
<div class="attempt__actions">
|
|
43526
|
+
<val-button [props]="mineBtn" (click)="confirmMine()" />
|
|
43527
|
+
<val-button [props]="blockBtn" (click)="confirmBlock()" />
|
|
43528
|
+
</div>
|
|
43529
|
+
} @else {
|
|
43530
|
+
<div class="attempt__actions">
|
|
43531
|
+
<val-button [props]="closeBtn" (click)="close()" />
|
|
43532
|
+
</div>
|
|
43533
|
+
}
|
|
43534
|
+
`, isInline: true, styles: [":host{display:block}.attempt{display:flex;flex-direction:column;gap:18px;align-items:flex-start}.attempt__hero{width:56px;height:56px;border-radius:16px;display:flex;align-items:center;justify-content:center;background:#7026df1a}.attempt__hero ion-icon{font-size:30px;color:var(--ion-color-primary, #7026df)}.attempt__device{display:flex;align-items:center;gap:12px;width:100%;padding:14px 16px;border-radius:12px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.attempt__device ion-icon{font-size:26px;color:var(--ion-color-dark);flex-shrink:0}.attempt__device-body{display:flex;flex-direction:column;gap:2px;min-width:0}.attempt__done{display:flex;align-items:center;gap:8px}.attempt__done ion-icon{font-size:20px;color:var(--ion-color-success);flex-shrink:0}.attempt__actions{display:flex;gap:10px;padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px));border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.attempt__actions val-button{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
|
|
43602
43535
|
}
|
|
43603
|
-
|
|
43604
|
-
|
|
43605
|
-
|
|
43606
|
-
|
|
43607
|
-
|
|
43608
|
-
|
|
43609
|
-
|
|
43610
|
-
|
|
43536
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LoginAttemptModalComponent, decorators: [{
|
|
43537
|
+
type: Component,
|
|
43538
|
+
args: [{ selector: 'val-login-attempt-modal', standalone: true, imports: [
|
|
43539
|
+
CommonModule,
|
|
43540
|
+
IonContent,
|
|
43541
|
+
IonHeader,
|
|
43542
|
+
IonToolbar,
|
|
43543
|
+
IonButtons,
|
|
43544
|
+
IonButton,
|
|
43545
|
+
IonIcon,
|
|
43546
|
+
DisplayComponent,
|
|
43547
|
+
TextComponent,
|
|
43548
|
+
ButtonComponent,
|
|
43549
|
+
], template: `
|
|
43550
|
+
<ion-header>
|
|
43551
|
+
<ion-toolbar>
|
|
43552
|
+
<ion-buttons slot="end">
|
|
43553
|
+
<ion-button fill="clear" color="dark" (click)="close()">
|
|
43554
|
+
<strong>{{ closeLabel }}</strong>
|
|
43555
|
+
</ion-button>
|
|
43556
|
+
</ion-buttons>
|
|
43557
|
+
</ion-toolbar>
|
|
43558
|
+
</ion-header>
|
|
43559
|
+
<ion-content class="ion-padding">
|
|
43560
|
+
<val-display [props]="{ content: title, size: 'small', color: 'dark' }" />
|
|
43561
|
+
<div class="attempt">
|
|
43562
|
+
<div class="attempt__hero">
|
|
43563
|
+
<ion-icon name="shield-checkmark-outline" aria-hidden="true" />
|
|
43564
|
+
</div>
|
|
43565
|
+
|
|
43566
|
+
<val-text
|
|
43567
|
+
[props]="{
|
|
43568
|
+
content: body,
|
|
43611
43569
|
size: 'medium',
|
|
43612
|
-
|
|
43613
|
-
|
|
43614
|
-
|
|
43615
|
-
|
|
43616
|
-
|
|
43617
|
-
|
|
43618
|
-
|
|
43619
|
-
|
|
43620
|
-
|
|
43621
|
-
|
|
43622
|
-
|
|
43623
|
-
.replace(/^\|/, '')
|
|
43624
|
-
.replace(/\|$/, '')
|
|
43625
|
-
.split('|')
|
|
43626
|
-
.map(c => c.trim()));
|
|
43627
|
-
if (parsed.length === 0)
|
|
43628
|
-
return [];
|
|
43629
|
-
const header = parsed[0];
|
|
43630
|
-
const dataRows = parsed.slice(1);
|
|
43631
|
-
if (dataRows.length === 0) {
|
|
43632
|
-
return [
|
|
43633
|
-
{
|
|
43634
|
-
type: 'paragraph',
|
|
43635
|
-
props: {
|
|
43636
|
-
content: header.join(' · '),
|
|
43637
|
-
size: 'medium',
|
|
43638
|
-
color: 'dark',
|
|
43639
|
-
bold: false,
|
|
43640
|
-
processLinks: true,
|
|
43641
|
-
allowPartialBold: true,
|
|
43642
|
-
},
|
|
43643
|
-
},
|
|
43644
|
-
];
|
|
43645
|
-
}
|
|
43646
|
-
return dataRows.map(row => {
|
|
43647
|
-
const pairs = row.map((cell, idx) => {
|
|
43648
|
-
const key = header[idx] ?? '';
|
|
43649
|
-
return key ? `<strong>${key}:</strong> ${cell}` : cell;
|
|
43650
|
-
});
|
|
43651
|
-
return {
|
|
43652
|
-
type: 'paragraph',
|
|
43653
|
-
props: {
|
|
43654
|
-
content: pairs.join(' · '),
|
|
43570
|
+
color: 'dark',
|
|
43571
|
+
bold: false,
|
|
43572
|
+
}"
|
|
43573
|
+
/>
|
|
43574
|
+
|
|
43575
|
+
<div class="attempt__device">
|
|
43576
|
+
<ion-icon [name]="deviceIcon" aria-hidden="true" />
|
|
43577
|
+
<div class="attempt__device-body">
|
|
43578
|
+
<val-text
|
|
43579
|
+
[props]="{
|
|
43580
|
+
content: deviceLabel,
|
|
43655
43581
|
size: 'medium',
|
|
43656
43582
|
color: 'dark',
|
|
43583
|
+
bold: true,
|
|
43584
|
+
}"
|
|
43585
|
+
/>
|
|
43586
|
+
@if (metaLabel) {
|
|
43587
|
+
<val-text
|
|
43588
|
+
[props]="{
|
|
43589
|
+
content: metaLabel,
|
|
43590
|
+
size: 'small',
|
|
43591
|
+
color: 'medium',
|
|
43592
|
+
bold: false,
|
|
43593
|
+
}"
|
|
43594
|
+
/>
|
|
43595
|
+
}
|
|
43596
|
+
</div>
|
|
43597
|
+
</div>
|
|
43598
|
+
|
|
43599
|
+
@if (done()) {
|
|
43600
|
+
<div class="attempt__done">
|
|
43601
|
+
<ion-icon name="checkmark-circle-outline" aria-hidden="true" />
|
|
43602
|
+
<val-text
|
|
43603
|
+
[props]="{
|
|
43604
|
+
content: doneLabel,
|
|
43605
|
+
size: 'small',
|
|
43606
|
+
color: 'medium',
|
|
43657
43607
|
bold: false,
|
|
43658
|
-
|
|
43659
|
-
|
|
43660
|
-
|
|
43661
|
-
}
|
|
43662
|
-
|
|
43663
|
-
|
|
43608
|
+
}"
|
|
43609
|
+
/>
|
|
43610
|
+
</div>
|
|
43611
|
+
}
|
|
43612
|
+
</div>
|
|
43613
|
+
</ion-content>
|
|
43614
|
+
|
|
43615
|
+
@if (!done()) {
|
|
43616
|
+
<div class="attempt__actions">
|
|
43617
|
+
<val-button [props]="mineBtn" (click)="confirmMine()" />
|
|
43618
|
+
<val-button [props]="blockBtn" (click)="confirmBlock()" />
|
|
43619
|
+
</div>
|
|
43620
|
+
} @else {
|
|
43621
|
+
<div class="attempt__actions">
|
|
43622
|
+
<val-button [props]="closeBtn" (click)="close()" />
|
|
43623
|
+
</div>
|
|
43624
|
+
}
|
|
43625
|
+
`, styles: [":host{display:block}.attempt{display:flex;flex-direction:column;gap:18px;align-items:flex-start}.attempt__hero{width:56px;height:56px;border-radius:16px;display:flex;align-items:center;justify-content:center;background:#7026df1a}.attempt__hero ion-icon{font-size:30px;color:var(--ion-color-primary, #7026df)}.attempt__device{display:flex;align-items:center;gap:12px;width:100%;padding:14px 16px;border-radius:12px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.attempt__device ion-icon{font-size:26px;color:var(--ion-color-dark);flex-shrink:0}.attempt__device-body{display:flex;flex-direction:column;gap:2px;min-width:0}.attempt__done{display:flex;align-items:center;gap:8px}.attempt__done ion-icon{font-size:20px;color:var(--ion-color-success);flex-shrink:0}.attempt__actions{display:flex;gap:10px;padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px));border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.attempt__actions val-button{flex:1}\n"] }]
|
|
43626
|
+
}], propDecorators: { title: [{
|
|
43627
|
+
type: Input
|
|
43628
|
+
}], body: [{
|
|
43629
|
+
type: Input
|
|
43630
|
+
}], deviceLabel: [{
|
|
43631
|
+
type: Input
|
|
43632
|
+
}], metaLabel: [{
|
|
43633
|
+
type: Input
|
|
43634
|
+
}], doneLabel: [{
|
|
43635
|
+
type: Input
|
|
43636
|
+
}], mineLabel: [{
|
|
43637
|
+
type: Input
|
|
43638
|
+
}], blockLabel: [{
|
|
43639
|
+
type: Input
|
|
43640
|
+
}], closeLabel: [{
|
|
43641
|
+
type: Input
|
|
43642
|
+
}], isMobile: [{
|
|
43643
|
+
type: Input
|
|
43644
|
+
}], onBlock: [{
|
|
43645
|
+
type: Input
|
|
43646
|
+
}], _modalRef: [{
|
|
43647
|
+
type: Input
|
|
43648
|
+
}] } });
|
|
43664
43649
|
|
|
43665
43650
|
/**
|
|
43666
|
-
* `val-
|
|
43651
|
+
* `val-session-list-modal` — modal de "Ver todas las sesiones activas". Organism
|
|
43652
|
+
* compartido entre la vista Seguridad y la vista Cuenta del factory.
|
|
43667
43653
|
*
|
|
43668
|
-
*
|
|
43669
|
-
*
|
|
43670
|
-
* reusar la misma data (`FaqMetadata.categories`) para emitir el structured
|
|
43671
|
-
* data `FAQPage` (JSON-LD) y ganar rich results en buscadores.
|
|
43654
|
+
* Render reusa el mismo markup de la sección de sesiones inline (mismas clases
|
|
43655
|
+
* CSS) pero sin truncar la lista.
|
|
43672
43656
|
*
|
|
43673
|
-
*
|
|
43674
|
-
* `
|
|
43675
|
-
*
|
|
43657
|
+
* Inputs (vienen de `componentProps` del ModalService):
|
|
43658
|
+
* - `sessions` — snapshot inicial (mostrado al abrir)
|
|
43659
|
+
* - `thisLabel` / `revokeLabel` — i18n strings ya resueltas
|
|
43660
|
+
* - `onRevoke` — callback async que el parent ejecuta contra el backend; debe
|
|
43661
|
+
* retornar la lista actualizada para que el modal refresque en sitio.
|
|
43676
43662
|
*
|
|
43677
|
-
*
|
|
43678
|
-
*
|
|
43679
|
-
*
|
|
43680
|
-
*
|
|
43663
|
+
* `@Output` events NO funcionan con `modalController.create()` — por eso usamos
|
|
43664
|
+
* callback pasado por props + `_modalRef` para dismiss desde dentro.
|
|
43665
|
+
*
|
|
43666
|
+
* Header canónico (Regla #5): sin `ion-title`, botón Cerrar texto en `slot=end`,
|
|
43667
|
+
* título en el body con `val-display`.
|
|
43681
43668
|
*/
|
|
43682
|
-
class
|
|
43669
|
+
class SessionListModalComponent {
|
|
43683
43670
|
constructor() {
|
|
43684
|
-
|
|
43685
|
-
this.
|
|
43686
|
-
/**
|
|
43687
|
-
this.
|
|
43688
|
-
/**
|
|
43689
|
-
|
|
43690
|
-
|
|
43691
|
-
|
|
43692
|
-
|
|
43693
|
-
this.
|
|
43694
|
-
|
|
43695
|
-
|
|
43696
|
-
|
|
43697
|
-
|
|
43698
|
-
map.set(item.id, parseMarkdownArticle(item.answer, { locale }));
|
|
43699
|
-
}
|
|
43700
|
-
}
|
|
43701
|
-
return map;
|
|
43702
|
-
});
|
|
43703
|
-
/**
|
|
43704
|
-
* Categorías visibles tras el filtro. Una categoría desaparece si ninguna
|
|
43705
|
-
* de sus preguntas matchea. Match sobre pregunta + respuesta (raw markdown),
|
|
43706
|
-
* case-insensitive.
|
|
43707
|
-
*/
|
|
43708
|
-
this.visibleCategories = computed(() => {
|
|
43709
|
-
const q = this.query().trim().toLowerCase();
|
|
43710
|
-
const cats = this.props_().categories ?? [];
|
|
43711
|
-
if (!q)
|
|
43712
|
-
return cats;
|
|
43713
|
-
return cats
|
|
43714
|
-
.map(cat => ({
|
|
43715
|
-
...cat,
|
|
43716
|
-
items: cat.items.filter(it => it.question.toLowerCase().includes(q) || it.answer.toLowerCase().includes(q)),
|
|
43717
|
-
}))
|
|
43718
|
-
.filter(cat => cat.items.length > 0);
|
|
43719
|
-
});
|
|
43720
|
-
/** `true` si se debe mostrar el label de cada categoría. */
|
|
43721
|
-
this.showCategoryLabel = computed(() => {
|
|
43722
|
-
const p = this.props_();
|
|
43723
|
-
if (p.hideSingleCategoryLabel === false)
|
|
43724
|
-
return true;
|
|
43725
|
-
return (p.categories?.length ?? 0) > 1;
|
|
43726
|
-
});
|
|
43727
|
-
}
|
|
43728
|
-
set props(value) {
|
|
43729
|
-
if (value)
|
|
43730
|
-
this.props_.set(value);
|
|
43731
|
-
}
|
|
43732
|
-
get props() {
|
|
43733
|
-
return this.props_();
|
|
43671
|
+
/** Título del modal (ya traducido). */
|
|
43672
|
+
this.title = '';
|
|
43673
|
+
/** Label del botón cerrar del header (ya traducido). */
|
|
43674
|
+
this.closeLabel = '';
|
|
43675
|
+
/** Label "Esta sesión" (en idioma activo). */
|
|
43676
|
+
this.thisLabel = '';
|
|
43677
|
+
/** Label del botón revoke individual. */
|
|
43678
|
+
this.revokeLabel = '';
|
|
43679
|
+
/** Lista local mutable — refrescada después de cada revoke confirmado. */
|
|
43680
|
+
this.sessionsLocal = signal([]);
|
|
43681
|
+
}
|
|
43682
|
+
/** Snapshot inicial — el modal mantiene su propia copia y la refresca tras revoke. */
|
|
43683
|
+
set sessions(value) {
|
|
43684
|
+
this.sessionsLocal.set([...value]);
|
|
43734
43685
|
}
|
|
43735
|
-
|
|
43736
|
-
|
|
43737
|
-
return this.parsedAnswers().get(item.id) ?? { elements: [] };
|
|
43686
|
+
close() {
|
|
43687
|
+
this._modalRef?.dismiss(undefined, 'close');
|
|
43738
43688
|
}
|
|
43739
|
-
|
|
43740
|
-
this.
|
|
43689
|
+
async handleRevoke(sessionId) {
|
|
43690
|
+
if (!this.onRevoke)
|
|
43691
|
+
return;
|
|
43692
|
+
try {
|
|
43693
|
+
const updated = await this.onRevoke(sessionId);
|
|
43694
|
+
this.sessionsLocal.set([...updated]);
|
|
43695
|
+
}
|
|
43696
|
+
catch {
|
|
43697
|
+
// El parent muestra el toast de error; modal no necesita feedback extra.
|
|
43698
|
+
}
|
|
43699
|
+
}
|
|
43700
|
+
get revokeBtn() {
|
|
43701
|
+
return {
|
|
43702
|
+
text: this.revokeLabel,
|
|
43703
|
+
color: 'dark',
|
|
43704
|
+
fill: 'clear',
|
|
43705
|
+
size: 'small',
|
|
43706
|
+
type: 'button',
|
|
43707
|
+
state: ComponentStates.ENABLED,
|
|
43708
|
+
};
|
|
43709
|
+
}
|
|
43710
|
+
deviceIcon(s) {
|
|
43711
|
+
const ua = (s.deviceInfo || '').toLowerCase();
|
|
43712
|
+
const isMobile = /iphone|ipad|ipod|android|mobile/.test(ua);
|
|
43713
|
+
return isMobile ? 'phone-portrait-outline' : 'laptop-outline';
|
|
43714
|
+
}
|
|
43715
|
+
formatMeta(s) {
|
|
43716
|
+
const parts = [];
|
|
43717
|
+
if (s.location)
|
|
43718
|
+
parts.push(s.location);
|
|
43719
|
+
else if (s.ip)
|
|
43720
|
+
parts.push(s.ip);
|
|
43721
|
+
if (s.createdAt)
|
|
43722
|
+
parts.push(new Date(s.createdAt).toLocaleString());
|
|
43723
|
+
return parts.join(' · ');
|
|
43724
|
+
}
|
|
43725
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SessionListModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
43726
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SessionListModalComponent, isStandalone: true, selector: "val-session-list-modal", inputs: { sessions: "sessions", title: "title", closeLabel: "closeLabel", thisLabel: "thisLabel", revokeLabel: "revokeLabel", onRevoke: "onRevoke", _modalRef: "_modalRef" }, ngImport: i0, template: `
|
|
43727
|
+
<ion-header>
|
|
43728
|
+
<ion-toolbar>
|
|
43729
|
+
<ion-buttons slot="end">
|
|
43730
|
+
<ion-button fill="clear" color="dark" (click)="close()">
|
|
43731
|
+
<strong>{{ closeLabel }}</strong>
|
|
43732
|
+
</ion-button>
|
|
43733
|
+
</ion-buttons>
|
|
43734
|
+
</ion-toolbar>
|
|
43735
|
+
</ion-header>
|
|
43736
|
+
<ion-content class="ion-padding">
|
|
43737
|
+
<val-display [props]="{ content: title, size: 'small', color: 'dark' }" />
|
|
43738
|
+
<ul class="sessions">
|
|
43739
|
+
@for (s of sessionsLocal(); track s.sessionId) {
|
|
43740
|
+
<li class="session" [class.session--current]="s.isCurrent">
|
|
43741
|
+
<ion-icon class="session__icon" [name]="deviceIcon(s)" aria-hidden="true" />
|
|
43742
|
+
<div class="session__body">
|
|
43743
|
+
<val-text
|
|
43744
|
+
[props]="{
|
|
43745
|
+
content: s.deviceInfo || s.deviceId || '—',
|
|
43746
|
+
size: 'medium',
|
|
43747
|
+
color: 'dark',
|
|
43748
|
+
bold: true,
|
|
43749
|
+
}"
|
|
43750
|
+
/>
|
|
43751
|
+
<val-text
|
|
43752
|
+
[props]="{
|
|
43753
|
+
content: formatMeta(s),
|
|
43754
|
+
size: 'small',
|
|
43755
|
+
color: 'medium',
|
|
43756
|
+
bold: false,
|
|
43757
|
+
}"
|
|
43758
|
+
/>
|
|
43759
|
+
</div>
|
|
43760
|
+
<div class="session__end">
|
|
43761
|
+
@if (s.isCurrent) {
|
|
43762
|
+
<span class="session__badge">{{ thisLabel }}</span>
|
|
43763
|
+
} @else {
|
|
43764
|
+
<val-button [props]="revokeBtn" (click)="handleRevoke(s.sessionId)" />
|
|
43765
|
+
}
|
|
43766
|
+
</div>
|
|
43767
|
+
</li>
|
|
43768
|
+
}
|
|
43769
|
+
</ul>
|
|
43770
|
+
</ion-content>
|
|
43771
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.sessions{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:10px}.session{display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:14px;background:var(--ion-color-light, rgba(0, 0, 0, .04));border:1px solid transparent}.session--current{background:#2dd36f1a;border-color:#2dd36f4d}.session__icon{font-size:26px;color:var(--ion-color-dark, #1a1a1a);flex-shrink:0}.session--current .session__icon{color:var(--ion-color-success-shade, #28ba62)}.session__body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.session__end{flex-shrink:0}.session__badge{display:inline-block;padding:4px 10px;font-size:11px;font-weight:700;letter-spacing:.02em;text-transform:uppercase;color:var(--ion-color-success-shade, #28ba62);background:#2dd36f2e;border-radius:999px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
|
|
43772
|
+
}
|
|
43773
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SessionListModalComponent, decorators: [{
|
|
43774
|
+
type: Component,
|
|
43775
|
+
args: [{ selector: 'val-session-list-modal', standalone: true, imports: [
|
|
43776
|
+
CommonModule,
|
|
43777
|
+
IonContent,
|
|
43778
|
+
IonHeader,
|
|
43779
|
+
IonToolbar,
|
|
43780
|
+
IonButtons,
|
|
43781
|
+
IonButton,
|
|
43782
|
+
IonIcon,
|
|
43783
|
+
DisplayComponent,
|
|
43784
|
+
TextComponent,
|
|
43785
|
+
ButtonComponent,
|
|
43786
|
+
], template: `
|
|
43787
|
+
<ion-header>
|
|
43788
|
+
<ion-toolbar>
|
|
43789
|
+
<ion-buttons slot="end">
|
|
43790
|
+
<ion-button fill="clear" color="dark" (click)="close()">
|
|
43791
|
+
<strong>{{ closeLabel }}</strong>
|
|
43792
|
+
</ion-button>
|
|
43793
|
+
</ion-buttons>
|
|
43794
|
+
</ion-toolbar>
|
|
43795
|
+
</ion-header>
|
|
43796
|
+
<ion-content class="ion-padding">
|
|
43797
|
+
<val-display [props]="{ content: title, size: 'small', color: 'dark' }" />
|
|
43798
|
+
<ul class="sessions">
|
|
43799
|
+
@for (s of sessionsLocal(); track s.sessionId) {
|
|
43800
|
+
<li class="session" [class.session--current]="s.isCurrent">
|
|
43801
|
+
<ion-icon class="session__icon" [name]="deviceIcon(s)" aria-hidden="true" />
|
|
43802
|
+
<div class="session__body">
|
|
43803
|
+
<val-text
|
|
43804
|
+
[props]="{
|
|
43805
|
+
content: s.deviceInfo || s.deviceId || '—',
|
|
43806
|
+
size: 'medium',
|
|
43807
|
+
color: 'dark',
|
|
43808
|
+
bold: true,
|
|
43809
|
+
}"
|
|
43810
|
+
/>
|
|
43811
|
+
<val-text
|
|
43812
|
+
[props]="{
|
|
43813
|
+
content: formatMeta(s),
|
|
43814
|
+
size: 'small',
|
|
43815
|
+
color: 'medium',
|
|
43816
|
+
bold: false,
|
|
43817
|
+
}"
|
|
43818
|
+
/>
|
|
43819
|
+
</div>
|
|
43820
|
+
<div class="session__end">
|
|
43821
|
+
@if (s.isCurrent) {
|
|
43822
|
+
<span class="session__badge">{{ thisLabel }}</span>
|
|
43823
|
+
} @else {
|
|
43824
|
+
<val-button [props]="revokeBtn" (click)="handleRevoke(s.sessionId)" />
|
|
43825
|
+
}
|
|
43826
|
+
</div>
|
|
43827
|
+
</li>
|
|
43828
|
+
}
|
|
43829
|
+
</ul>
|
|
43830
|
+
</ion-content>
|
|
43831
|
+
`, styles: [":host{display:block;height:100%}.sessions{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:10px}.session{display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:14px;background:var(--ion-color-light, rgba(0, 0, 0, .04));border:1px solid transparent}.session--current{background:#2dd36f1a;border-color:#2dd36f4d}.session__icon{font-size:26px;color:var(--ion-color-dark, #1a1a1a);flex-shrink:0}.session--current .session__icon{color:var(--ion-color-success-shade, #28ba62)}.session__body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.session__end{flex-shrink:0}.session__badge{display:inline-block;padding:4px 10px;font-size:11px;font-weight:700;letter-spacing:.02em;text-transform:uppercase;color:var(--ion-color-success-shade, #28ba62);background:#2dd36f2e;border-radius:999px}\n"] }]
|
|
43832
|
+
}], propDecorators: { sessions: [{
|
|
43833
|
+
type: Input
|
|
43834
|
+
}], title: [{
|
|
43835
|
+
type: Input
|
|
43836
|
+
}], closeLabel: [{
|
|
43837
|
+
type: Input
|
|
43838
|
+
}], thisLabel: [{
|
|
43839
|
+
type: Input
|
|
43840
|
+
}], revokeLabel: [{
|
|
43841
|
+
type: Input
|
|
43842
|
+
}], onRevoke: [{
|
|
43843
|
+
type: Input
|
|
43844
|
+
}], _modalRef: [{
|
|
43845
|
+
type: Input
|
|
43846
|
+
}] } });
|
|
43847
|
+
|
|
43848
|
+
/**
|
|
43849
|
+
* Standard cross-app site links (env-aware)
|
|
43850
|
+
*
|
|
43851
|
+
* En el factory MyValtech cada producto satélite (showcase, sigify, …) enlaza a
|
|
43852
|
+
* `web` —la landing de marca— para soporte, contacto y contenido legal. `web`
|
|
43853
|
+
* es el dueño canónico de esas rutas. Este módulo centraliza:
|
|
43854
|
+
*
|
|
43855
|
+
* 1. El mapa ambiente → base URL de `web` (`VALTECH_WEB_BASE_URLS`).
|
|
43856
|
+
* 2. El catálogo de paths estándar que viven en `web` (`VALTECH_SITE_PATHS`).
|
|
43857
|
+
*
|
|
43858
|
+
* El ambiente se decide en BUILD TIME (campo `valtechEnv` en el `environment*.ts`
|
|
43859
|
+
* de cada app), igual que `apiUrl`. Es el mecanismo más confiable: detectar por
|
|
43860
|
+
* hostname en runtime se rompe con dominios custom / `web.app` / variantes de
|
|
43861
|
+
* localhost.
|
|
43862
|
+
*
|
|
43863
|
+
* La resolución real (join baseUrl + path, abrir en tab nueva, `?lang=`) la hace
|
|
43864
|
+
* `LegalLinkService`; este módulo solo le entrega el `baseUrl` correcto por env.
|
|
43865
|
+
*
|
|
43866
|
+
* @see provideValtechSite — wiring de una sola línea para apps satélite.
|
|
43867
|
+
*/
|
|
43868
|
+
/**
|
|
43869
|
+
* Base URL de `web` (landing de marca) por ambiente.
|
|
43870
|
+
*
|
|
43871
|
+
* - `local` → `ng serve` de `web` (puerto 8100).
|
|
43872
|
+
* - `dev` → proyecto Firebase `myvaltech-dev`.
|
|
43873
|
+
* - `prod` → dominio custom de producción.
|
|
43874
|
+
*
|
|
43875
|
+
* Sin trailing slash (lo asume `LegalLinkService`).
|
|
43876
|
+
*/
|
|
43877
|
+
const VALTECH_WEB_BASE_URLS = {
|
|
43878
|
+
local: 'http://localhost:8100',
|
|
43879
|
+
dev: 'https://myvaltech-dev.web.app',
|
|
43880
|
+
prod: 'https://myvaltech.com',
|
|
43881
|
+
};
|
|
43882
|
+
/**
|
|
43883
|
+
* Resuelve la base URL de `web` para un ambiente.
|
|
43884
|
+
*
|
|
43885
|
+
* Fallback a `prod` cuando el env es desconocido o falta: ante la duda, apuntar
|
|
43886
|
+
* a producción es lo seguro (nunca enlaza a un host local/dev inexistente).
|
|
43887
|
+
*/
|
|
43888
|
+
function resolveWebBaseUrl(env) {
|
|
43889
|
+
if (env && env in VALTECH_WEB_BASE_URLS) {
|
|
43890
|
+
return VALTECH_WEB_BASE_URLS[env];
|
|
43891
|
+
}
|
|
43892
|
+
return VALTECH_WEB_BASE_URLS.prod;
|
|
43893
|
+
}
|
|
43894
|
+
/**
|
|
43895
|
+
* Catálogo de paths estándar que viven en `web`. Las apps resuelven estos
|
|
43896
|
+
* contra la base URL del ambiente vía `LegalLinkService.resolve(path)`.
|
|
43897
|
+
*
|
|
43898
|
+
* Mantener alineado con las rutas reales de `web/src/app/app.routes.ts` y con
|
|
43899
|
+
* `VALTECH_COMPANY_LINKS` (footer).
|
|
43900
|
+
*/
|
|
43901
|
+
const VALTECH_SITE_PATHS = {
|
|
43902
|
+
home: '/',
|
|
43903
|
+
support: '/contact',
|
|
43904
|
+
contact: '/contact',
|
|
43905
|
+
faq: '/faq',
|
|
43906
|
+
about: '/about',
|
|
43907
|
+
terms: '/legal/terms',
|
|
43908
|
+
privacy: '/legal/privacy',
|
|
43909
|
+
cookies: '/legal/cookies',
|
|
43910
|
+
legalNotice: '/legal/legal-notice',
|
|
43911
|
+
};
|
|
43912
|
+
|
|
43913
|
+
const VALTECH_LEGAL_CONFIG = new InjectionToken('VALTECH_LEGAL_CONFIG');
|
|
43914
|
+
/**
|
|
43915
|
+
* Resolves legal/site paths against a configurable main-site base URL.
|
|
43916
|
+
*
|
|
43917
|
+
* - **Main site mode**: no `provideValtechLegal` called → `resolve('/legal/terms')` returns `/legal/terms` (relative).
|
|
43918
|
+
* - **Satellite mode**: `provideValtechLegal({ baseUrl: 'https://myvaltech.com' })` → `resolve('/legal/terms')` returns `'https://myvaltech.com/legal/terms'` and `isExternal('/legal/terms')` returns `true`.
|
|
43919
|
+
*
|
|
43920
|
+
* Absolute URLs passed in are returned unchanged (no double-prefix).
|
|
43921
|
+
*/
|
|
43922
|
+
class LegalLinkService {
|
|
43923
|
+
constructor() {
|
|
43924
|
+
this.config = inject(VALTECH_LEGAL_CONFIG, { optional: true }) ?? {};
|
|
43925
|
+
}
|
|
43926
|
+
/** Effective base URL (null if running as main site). */
|
|
43927
|
+
get baseUrl() {
|
|
43928
|
+
return this.config.baseUrl?.replace(/\/$/, '') ?? null;
|
|
43929
|
+
}
|
|
43930
|
+
/** Whether resolved external links should open in a new tab. */
|
|
43931
|
+
get openInNewTab() {
|
|
43932
|
+
return this.config.openInNewTab ?? Boolean(this.config.baseUrl);
|
|
43933
|
+
}
|
|
43934
|
+
/**
|
|
43935
|
+
* Returns the URL to use for a given internal path. Absolute URLs pass through.
|
|
43936
|
+
*
|
|
43937
|
+
* When `locale` is provided AND the resolved URL would be cross-origin, appends
|
|
43938
|
+
* `?lang={locale}` so the main site can render the legal doc in the user's
|
|
43939
|
+
* active language (instead of its default). Locale is omitted for same-origin
|
|
43940
|
+
* resolves since the main site reads locale from its own `LocaleService`.
|
|
43941
|
+
*
|
|
43942
|
+
* @example
|
|
43943
|
+
* resolve('/legal/terms')
|
|
43944
|
+
* // main site: '/legal/terms'
|
|
43945
|
+
* // satellite: 'https://myvaltech.com/legal/terms'
|
|
43946
|
+
*
|
|
43947
|
+
* resolve('/legal/terms', { locale: 'pt' })
|
|
43948
|
+
* // main site: '/legal/terms' (locale stays implicit)
|
|
43949
|
+
* // satellite: 'https://myvaltech.com/legal/terms?lang=pt'
|
|
43950
|
+
*
|
|
43951
|
+
* resolve('https://x.com/y') // unchanged
|
|
43952
|
+
*/
|
|
43953
|
+
resolve(path, options) {
|
|
43954
|
+
if (!path)
|
|
43955
|
+
return path;
|
|
43956
|
+
if (/^https?:\/\//i.test(path))
|
|
43957
|
+
return path;
|
|
43958
|
+
const base = this.baseUrl;
|
|
43959
|
+
if (!base)
|
|
43960
|
+
return path;
|
|
43961
|
+
const url = `${base}${path.startsWith('/') ? '' : '/'}${path}`;
|
|
43962
|
+
if (options?.locale) {
|
|
43963
|
+
const sep = url.includes('?') ? '&' : '?';
|
|
43964
|
+
return `${url}${sep}lang=${encodeURIComponent(options.locale)}`;
|
|
43965
|
+
}
|
|
43966
|
+
return url;
|
|
43967
|
+
}
|
|
43968
|
+
/** `true` if the path would be resolved to a cross-origin URL. */
|
|
43969
|
+
isExternal(path) {
|
|
43970
|
+
if (/^https?:\/\//i.test(path))
|
|
43971
|
+
return true;
|
|
43972
|
+
return this.baseUrl !== null;
|
|
43973
|
+
}
|
|
43974
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LegalLinkService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
43975
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LegalLinkService, providedIn: 'root' }); }
|
|
43976
|
+
}
|
|
43977
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LegalLinkService, decorators: [{
|
|
43978
|
+
type: Injectable,
|
|
43979
|
+
args: [{ providedIn: 'root' }]
|
|
43980
|
+
}] });
|
|
43981
|
+
/**
|
|
43982
|
+
* Wires `LegalLinkService` for satellite apps. Omit in the main site (the one
|
|
43983
|
+
* that hosts the canonical /legal/* routes) so links stay relative.
|
|
43984
|
+
*
|
|
43985
|
+
* @example
|
|
43986
|
+
* // main.ts of showcase / sigify / etc.
|
|
43987
|
+
* provideValtechLegal({
|
|
43988
|
+
* baseUrl: 'https://myvaltech.com',
|
|
43989
|
+
* openInNewTab: true,
|
|
43990
|
+
* }),
|
|
43991
|
+
*/
|
|
43992
|
+
function provideValtechLegal(config) {
|
|
43993
|
+
return { provide: VALTECH_LEGAL_CONFIG, useValue: config };
|
|
43994
|
+
}
|
|
43995
|
+
/**
|
|
43996
|
+
* Wiring de una sola línea para que una app satélite apunte sus links de sitio
|
|
43997
|
+
* (legal, soporte, contacto, …) a `web` —la landing de marca— en el ambiente
|
|
43998
|
+
* correcto. Resuelve el `baseUrl` desde `VALTECH_WEB_BASE_URLS` según el `env`
|
|
43999
|
+
* baked en build-time (`environment.valtechEnv`) y delega en `provideValtechLegal`.
|
|
44000
|
+
*
|
|
44001
|
+
* Ambiente desconocido/ausente → fallback a `prod` (ver `resolveWebBaseUrl`).
|
|
44002
|
+
* El main site (`web`) NO llama esto — sus links quedan relativos.
|
|
44003
|
+
*
|
|
44004
|
+
* @example
|
|
44005
|
+
* // main.ts de showcase / sigify / …
|
|
44006
|
+
* import { environment } from './environments/environment';
|
|
44007
|
+
* provideValtechSite(environment.valtechEnv), // 'local' | 'dev' | 'prod'
|
|
44008
|
+
*
|
|
44009
|
+
* @see resolveWebBaseUrl
|
|
44010
|
+
* @see VALTECH_SITE_PATHS — catálogo de paths estándar que viven en `web`.
|
|
44011
|
+
*/
|
|
44012
|
+
function provideValtechSite(env, options) {
|
|
44013
|
+
return provideValtechLegal({
|
|
44014
|
+
baseUrl: resolveWebBaseUrl(env),
|
|
44015
|
+
openInNewTab: options?.openInNewTab ?? true,
|
|
44016
|
+
});
|
|
44017
|
+
}
|
|
44018
|
+
|
|
44019
|
+
/**
|
|
44020
|
+
* Default modal sizes.
|
|
44021
|
+
*/
|
|
44022
|
+
const MODAL_SIZES = {
|
|
44023
|
+
small: { width: '320px', height: '220px' },
|
|
44024
|
+
medium: { width: '500px', height: '400px' },
|
|
44025
|
+
large: { width: '800px', height: '80vh' },
|
|
44026
|
+
full: { width: '100%', height: '100%' },
|
|
44027
|
+
auto: { width: 'auto', height: '50vh' },
|
|
44028
|
+
};
|
|
44029
|
+
/**
|
|
44030
|
+
* Default modal buttons.
|
|
44031
|
+
*/
|
|
44032
|
+
const DEFAULT_MODAL_CONFIRM_BUTTON = {
|
|
44033
|
+
text: 'Confirm',
|
|
44034
|
+
role: 'confirm',
|
|
44035
|
+
color: 'primary',
|
|
44036
|
+
fill: 'solid',
|
|
44037
|
+
};
|
|
44038
|
+
const DEFAULT_MODAL_CANCEL_BUTTON = {
|
|
44039
|
+
text: 'Cancel',
|
|
44040
|
+
role: 'cancel',
|
|
44041
|
+
color: 'medium',
|
|
44042
|
+
fill: 'clear',
|
|
44043
|
+
};
|
|
44044
|
+
|
|
44045
|
+
/**
|
|
44046
|
+
* Internal component for simple content modals.
|
|
44047
|
+
* Used by ModalService.openSimple()
|
|
44048
|
+
*
|
|
44049
|
+
* Header + título + close button delegados a `val-modal-shell` (estándar
|
|
44050
|
+
* canónico — ver Regla #5 en `frontend/CLAUDE.md`). El cuerpo (innerHTML) +
|
|
44051
|
+
* footer con botones quedan locales.
|
|
44052
|
+
*/
|
|
44053
|
+
class SimpleModalContentComponent {
|
|
44054
|
+
constructor() {
|
|
44055
|
+
this.title = '';
|
|
44056
|
+
this.content = '';
|
|
44057
|
+
this.buttons = [];
|
|
44058
|
+
this.showCloseButton = true;
|
|
44059
|
+
}
|
|
44060
|
+
async onButtonClick(button) {
|
|
44061
|
+
if (button.handler) {
|
|
44062
|
+
const result = await button.handler();
|
|
44063
|
+
// If handler returns false, don't dismiss
|
|
44064
|
+
if (result === false) {
|
|
44065
|
+
return;
|
|
44066
|
+
}
|
|
44067
|
+
}
|
|
44068
|
+
this._modalRef?.dismiss(undefined, button.role || 'confirm');
|
|
44069
|
+
}
|
|
44070
|
+
onDismiss(role) {
|
|
44071
|
+
this._modalRef?.dismiss(undefined, role);
|
|
44072
|
+
}
|
|
44073
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SimpleModalContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
44074
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SimpleModalContentComponent, isStandalone: true, selector: "val-simple-modal-content", inputs: { title: "title", content: "content", buttons: "buttons", showCloseButton: "showCloseButton", _modalRef: "_modalRef" }, ngImport: i0, template: `
|
|
44075
|
+
<val-modal-shell [title]="title" [showClose]="showCloseButton" (close)="onDismiss('close')">
|
|
44076
|
+
<div [innerHTML]="content"></div>
|
|
44077
|
+
</val-modal-shell>
|
|
44078
|
+
|
|
44079
|
+
@if (buttons && buttons.length > 0) {
|
|
44080
|
+
<ion-footer>
|
|
44081
|
+
<ion-toolbar>
|
|
44082
|
+
<div class="button-container">
|
|
44083
|
+
@for (button of buttons; track button.text) {
|
|
44084
|
+
<ion-button
|
|
44085
|
+
[color]="button.color || 'medium'"
|
|
44086
|
+
[fill]="button.fill || 'solid'"
|
|
44087
|
+
[size]="button.size || 'default'"
|
|
44088
|
+
[disabled]="button.disabled || button.loading"
|
|
44089
|
+
[class]="button.cssClass"
|
|
44090
|
+
(click)="onButtonClick(button)"
|
|
44091
|
+
>
|
|
44092
|
+
@if (button.loading) {
|
|
44093
|
+
<ion-spinner name="circular"></ion-spinner>
|
|
44094
|
+
} @else {
|
|
44095
|
+
{{ button.text }}
|
|
44096
|
+
}
|
|
44097
|
+
</ion-button>
|
|
44098
|
+
}
|
|
44099
|
+
</div>
|
|
44100
|
+
</ion-toolbar>
|
|
44101
|
+
</ion-footer>
|
|
44102
|
+
}
|
|
44103
|
+
`, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}ion-footer{flex-shrink:0}.button-container{display:flex;justify-content:flex-end;gap:8px;padding:8px 0}ion-footer ion-toolbar{--padding-start: 16px;--padding-end: 16px}:host ::ng-deep ul,:host ::ng-deep ol{padding-left:20px;margin:8px 0}:host ::ng-deep li{margin:4px 0}:host ::ng-deep p{margin:8px 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { 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: IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: ModalShellComponent, selector: "val-modal-shell", inputs: ["title", "subtitle", "closeLabel", "showClose"], outputs: ["close"] }] }); }
|
|
44104
|
+
}
|
|
44105
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SimpleModalContentComponent, decorators: [{
|
|
44106
|
+
type: Component,
|
|
44107
|
+
args: [{ selector: 'val-simple-modal-content', standalone: true, imports: [CommonModule, IonToolbar, IonButton, IonFooter, IonSpinner, ModalShellComponent], template: `
|
|
44108
|
+
<val-modal-shell [title]="title" [showClose]="showCloseButton" (close)="onDismiss('close')">
|
|
44109
|
+
<div [innerHTML]="content"></div>
|
|
44110
|
+
</val-modal-shell>
|
|
44111
|
+
|
|
44112
|
+
@if (buttons && buttons.length > 0) {
|
|
44113
|
+
<ion-footer>
|
|
44114
|
+
<ion-toolbar>
|
|
44115
|
+
<div class="button-container">
|
|
44116
|
+
@for (button of buttons; track button.text) {
|
|
44117
|
+
<ion-button
|
|
44118
|
+
[color]="button.color || 'medium'"
|
|
44119
|
+
[fill]="button.fill || 'solid'"
|
|
44120
|
+
[size]="button.size || 'default'"
|
|
44121
|
+
[disabled]="button.disabled || button.loading"
|
|
44122
|
+
[class]="button.cssClass"
|
|
44123
|
+
(click)="onButtonClick(button)"
|
|
44124
|
+
>
|
|
44125
|
+
@if (button.loading) {
|
|
44126
|
+
<ion-spinner name="circular"></ion-spinner>
|
|
44127
|
+
} @else {
|
|
44128
|
+
{{ button.text }}
|
|
44129
|
+
}
|
|
44130
|
+
</ion-button>
|
|
44131
|
+
}
|
|
44132
|
+
</div>
|
|
44133
|
+
</ion-toolbar>
|
|
44134
|
+
</ion-footer>
|
|
44135
|
+
}
|
|
44136
|
+
`, styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}ion-footer{flex-shrink:0}.button-container{display:flex;justify-content:flex-end;gap:8px;padding:8px 0}ion-footer ion-toolbar{--padding-start: 16px;--padding-end: 16px}:host ::ng-deep ul,:host ::ng-deep ol{padding-left:20px;margin:8px 0}:host ::ng-deep li{margin:4px 0}:host ::ng-deep p{margin:8px 0}\n"] }]
|
|
44137
|
+
}], propDecorators: { title: [{
|
|
44138
|
+
type: Input
|
|
44139
|
+
}], content: [{
|
|
44140
|
+
type: Input
|
|
44141
|
+
}], buttons: [{
|
|
44142
|
+
type: Input
|
|
44143
|
+
}], showCloseButton: [{
|
|
44144
|
+
type: Input
|
|
44145
|
+
}], _modalRef: [{
|
|
44146
|
+
type: Input
|
|
44147
|
+
}] } });
|
|
44148
|
+
|
|
44149
|
+
/**
|
|
44150
|
+
* ModalService
|
|
44151
|
+
*
|
|
44152
|
+
* A service for creating and managing modals with various configurations.
|
|
44153
|
+
*
|
|
44154
|
+
* @example Basic component modal
|
|
44155
|
+
* ```typescript
|
|
44156
|
+
* const result = await modalService.open({
|
|
44157
|
+
* component: MyFormComponent,
|
|
44158
|
+
* componentProps: { userId: 123 },
|
|
44159
|
+
* header: { title: 'Edit User', showCloseButton: true },
|
|
44160
|
+
* size: 'medium'
|
|
44161
|
+
* });
|
|
44162
|
+
*
|
|
44163
|
+
* if (result.role === 'confirm') {
|
|
44164
|
+
* console.log('Saved:', result.data);
|
|
44165
|
+
* }
|
|
44166
|
+
* ```
|
|
44167
|
+
*
|
|
44168
|
+
* @example Simple content modal
|
|
44169
|
+
* ```typescript
|
|
44170
|
+
* await modalService.openSimple({
|
|
44171
|
+
* title: 'Terms of Service',
|
|
44172
|
+
* content: '<p>Terms content here...</p>',
|
|
44173
|
+
* size: 'large',
|
|
44174
|
+
* showCloseButton: true
|
|
44175
|
+
* });
|
|
44176
|
+
* ```
|
|
44177
|
+
*
|
|
44178
|
+
* @example Sheet modal (mobile)
|
|
44179
|
+
* ```typescript
|
|
44180
|
+
* const result = await modalService.openSheet({
|
|
44181
|
+
* component: FilterComponent,
|
|
44182
|
+
* breakpoints: {
|
|
44183
|
+
* initialBreakpoint: 0.5,
|
|
44184
|
+
* breakpoints: [0, 0.5, 1],
|
|
44185
|
+
* showHandle: true
|
|
44186
|
+
* }
|
|
44187
|
+
* });
|
|
44188
|
+
* ```
|
|
44189
|
+
*/
|
|
44190
|
+
class ModalService {
|
|
44191
|
+
constructor(modalController) {
|
|
44192
|
+
this.modalController = modalController;
|
|
44193
|
+
}
|
|
44194
|
+
/**
|
|
44195
|
+
* Opens a modal with a component.
|
|
44196
|
+
* @param options - Modal configuration
|
|
44197
|
+
* @returns Promise resolving to the modal result
|
|
44198
|
+
*/
|
|
44199
|
+
async open(options) {
|
|
44200
|
+
// A sheet modal (breakpoints present) gets its height from the breakpoints
|
|
44201
|
+
// and must span the full viewport width. Applying a size preset's
|
|
44202
|
+
// `--width`/`--height` here collapses the sheet (e.g. `auto` → shrink to
|
|
44203
|
+
// content) or fights the breakpoint height. So when it's a sheet we skip
|
|
44204
|
+
// the size preset entirely.
|
|
44205
|
+
const isSheet = !!options.breakpoints?.breakpoints?.length;
|
|
44206
|
+
const size = isSheet ? undefined : MODAL_SIZES[options.size || 'medium'];
|
|
44207
|
+
const cssClasses = this.buildCssClasses(options);
|
|
44208
|
+
const modal = await this.modalController.create({
|
|
44209
|
+
component: options.component,
|
|
44210
|
+
componentProps: {
|
|
44211
|
+
...options.componentProps,
|
|
44212
|
+
// Pass modal reference for dismiss from within component
|
|
44213
|
+
_modalRef: {
|
|
44214
|
+
dismiss: (data, role) => modal.dismiss(data, role),
|
|
44215
|
+
},
|
|
44216
|
+
},
|
|
44217
|
+
id: options.id,
|
|
44218
|
+
showBackdrop: options.showBackdrop ?? true,
|
|
44219
|
+
backdropDismiss: options.backdropDismiss ?? true,
|
|
44220
|
+
keyboardClose: options.keyboardClose ?? true,
|
|
44221
|
+
cssClass: cssClasses,
|
|
44222
|
+
mode: options.mode,
|
|
44223
|
+
animated: options.animated ?? true,
|
|
44224
|
+
canDismiss: this.normalizeCanDismiss(options.canDismiss ?? true),
|
|
44225
|
+
presentingElement: options.presentingElement,
|
|
44226
|
+
// Sheet modal options
|
|
44227
|
+
initialBreakpoint: options.breakpoints?.initialBreakpoint,
|
|
44228
|
+
breakpoints: options.breakpoints?.breakpoints,
|
|
44229
|
+
handle: options.breakpoints?.showHandle,
|
|
44230
|
+
handleBehavior: options.breakpoints?.handleBehavior,
|
|
44231
|
+
});
|
|
44232
|
+
// Apply custom size styles. Skipped for sheet modals — their dimensions
|
|
44233
|
+
// come from the breakpoints (Ionic spans the sheet full-width and uses the
|
|
44234
|
+
// breakpoint values for height).
|
|
44235
|
+
if (!isSheet && (options.width || options.height || size)) {
|
|
44236
|
+
const modalEl = modal;
|
|
44237
|
+
modalEl.style.setProperty('--width', options.width || size?.width || 'auto');
|
|
44238
|
+
modalEl.style.setProperty('--height', options.height || size?.height || '50vh');
|
|
44239
|
+
}
|
|
44240
|
+
await modal.present();
|
|
44241
|
+
const { data, role } = await modal.onDidDismiss();
|
|
44242
|
+
return { data, role };
|
|
44243
|
+
}
|
|
44244
|
+
/**
|
|
44245
|
+
* Opens a simple content modal without a custom component.
|
|
44246
|
+
* Uses Ionic's alert-like styling but with modal capabilities.
|
|
44247
|
+
* @param options - Simple modal configuration
|
|
44248
|
+
* @returns Promise resolving to the modal result
|
|
44249
|
+
*/
|
|
44250
|
+
async openSimple(options) {
|
|
44251
|
+
return this.open({
|
|
44252
|
+
component: SimpleModalContentComponent,
|
|
44253
|
+
componentProps: {
|
|
44254
|
+
title: options.title,
|
|
44255
|
+
content: options.content,
|
|
44256
|
+
buttons: options.buttons || [DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON],
|
|
44257
|
+
showCloseButton: options.showCloseButton ?? true,
|
|
44258
|
+
},
|
|
44259
|
+
size: options.size || 'medium',
|
|
44260
|
+
backdropDismiss: options.backdropDismiss ?? true,
|
|
44261
|
+
cssClass: ['simple-modal', options.cssClass].filter(Boolean).join(' '),
|
|
44262
|
+
});
|
|
44263
|
+
}
|
|
44264
|
+
/**
|
|
44265
|
+
* Opens a confirmation modal.
|
|
44266
|
+
* @param title - Modal title
|
|
44267
|
+
* @param message - Modal message
|
|
44268
|
+
* @param confirmButton - Confirm button config (optional)
|
|
44269
|
+
* @param cancelButton - Cancel button config (optional)
|
|
44270
|
+
* @returns Promise resolving to true if confirmed
|
|
44271
|
+
*/
|
|
44272
|
+
async confirm(title, message, confirmButton, cancelButton) {
|
|
44273
|
+
const result = await this.openSimple({
|
|
44274
|
+
title,
|
|
44275
|
+
content: message,
|
|
44276
|
+
size: 'small',
|
|
44277
|
+
showCloseButton: false,
|
|
44278
|
+
backdropDismiss: false,
|
|
44279
|
+
buttons: [
|
|
44280
|
+
{ ...DEFAULT_MODAL_CANCEL_BUTTON, ...cancelButton },
|
|
44281
|
+
{ ...DEFAULT_MODAL_CONFIRM_BUTTON, ...confirmButton },
|
|
44282
|
+
],
|
|
44283
|
+
});
|
|
44284
|
+
return result.role === 'confirm';
|
|
44285
|
+
}
|
|
44286
|
+
/**
|
|
44287
|
+
* Opens a sheet modal (iOS-style bottom sheet).
|
|
44288
|
+
* @param options - Modal configuration with sheet-specific options
|
|
44289
|
+
* @returns Promise resolving to the modal result
|
|
44290
|
+
*/
|
|
44291
|
+
async openSheet(options) {
|
|
44292
|
+
return this.open({
|
|
44293
|
+
...options,
|
|
44294
|
+
breakpoints: {
|
|
44295
|
+
initialBreakpoint: options.initialBreakpoint ?? 0.5,
|
|
44296
|
+
breakpoints: options.breakpoints ?? [0, 0.5, 1],
|
|
44297
|
+
showHandle: options.showHandle ?? true,
|
|
44298
|
+
},
|
|
44299
|
+
cssClass: ['sheet-modal', options.cssClass].filter(Boolean).join(' '),
|
|
44300
|
+
});
|
|
44301
|
+
}
|
|
44302
|
+
/**
|
|
44303
|
+
* Opens a fullscreen modal.
|
|
44304
|
+
* @param options - Modal configuration
|
|
44305
|
+
* @returns Promise resolving to the modal result
|
|
44306
|
+
*/
|
|
44307
|
+
async openFullscreen(options) {
|
|
44308
|
+
return this.open({
|
|
44309
|
+
...options,
|
|
44310
|
+
size: 'full',
|
|
44311
|
+
cssClass: ['fullscreen-modal', options.cssClass].filter(Boolean).join(' '),
|
|
44312
|
+
});
|
|
44313
|
+
}
|
|
44314
|
+
/**
|
|
44315
|
+
* Dismisses the top-most modal.
|
|
44316
|
+
* @param data - Optional data to return
|
|
44317
|
+
* @param role - Optional role
|
|
44318
|
+
*/
|
|
44319
|
+
async dismiss(data, role) {
|
|
44320
|
+
return this.modalController.dismiss(data, role);
|
|
44321
|
+
}
|
|
44322
|
+
/**
|
|
44323
|
+
* Gets the top-most modal.
|
|
44324
|
+
*/
|
|
44325
|
+
async getTop() {
|
|
44326
|
+
return this.modalController.getTop();
|
|
44327
|
+
}
|
|
44328
|
+
normalizeCanDismiss(canDismiss) {
|
|
44329
|
+
if (typeof canDismiss === 'boolean') {
|
|
44330
|
+
return canDismiss;
|
|
44331
|
+
}
|
|
44332
|
+
// Wrap the function to ensure it returns a Promise
|
|
44333
|
+
return async (data, role) => {
|
|
44334
|
+
const result = canDismiss();
|
|
44335
|
+
return Promise.resolve(result);
|
|
44336
|
+
};
|
|
44337
|
+
}
|
|
44338
|
+
buildCssClasses(options) {
|
|
44339
|
+
const classes = ['val-modal'];
|
|
44340
|
+
if (options.size) {
|
|
44341
|
+
classes.push(`modal-${options.size}`);
|
|
44342
|
+
}
|
|
44343
|
+
if (options.header) {
|
|
44344
|
+
classes.push('modal-with-header');
|
|
44345
|
+
}
|
|
44346
|
+
if (options.footer) {
|
|
44347
|
+
classes.push('modal-with-footer');
|
|
44348
|
+
}
|
|
44349
|
+
if (options.cssClass) {
|
|
44350
|
+
classes.push(options.cssClass);
|
|
44351
|
+
}
|
|
44352
|
+
return classes.join(' ');
|
|
44353
|
+
}
|
|
44354
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalService, deps: [{ token: i2$1.ModalController }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
44355
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalService, providedIn: 'root' }); }
|
|
44356
|
+
}
|
|
44357
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalService, decorators: [{
|
|
44358
|
+
type: Injectable,
|
|
44359
|
+
args: [{
|
|
44360
|
+
providedIn: 'root',
|
|
44361
|
+
}]
|
|
44362
|
+
}], ctorParameters: () => [{ type: i2$1.ModalController }] });
|
|
44363
|
+
|
|
44364
|
+
/**
|
|
44365
|
+
* Defaults i18n (es/en) embebidos en `val-security-view`. Auto-registrados en el
|
|
44366
|
+
* constructor del componente si el consumer no proveyó el namespace
|
|
44367
|
+
* (`Settings.Security` por default). Garantiza que nunca haya una key faltante
|
|
44368
|
+
* evaluada por cada change-detection (ver nota en valtech-components/CLAUDE.md
|
|
44369
|
+
* sobre el storm de CD con `val-debug-console`). El consumer puede override
|
|
44370
|
+
* registrando el mismo namespace antes de que el componente monte.
|
|
44371
|
+
*
|
|
44372
|
+
* SOLO incluye las keys que la vista realmente usa.
|
|
44373
|
+
*/
|
|
44374
|
+
const SECURITY_VIEW_I18N = {
|
|
44375
|
+
es: {
|
|
44376
|
+
pageTitle: 'Seguridad',
|
|
44377
|
+
pageDescription: 'Contraseñas, MFA y sesiones activas',
|
|
44378
|
+
blocked: 'Dispositivo bloqueado',
|
|
44379
|
+
tokenActionFailed: 'El enlace del email no es válido o expiró.',
|
|
44380
|
+
loginAttemptTitle: 'Confirmar inicio de sesión',
|
|
44381
|
+
loginAttemptBody: 'Detectamos un inicio de sesión en este dispositivo. Si fuiste tú, no tienes que hacer nada. Si no lo reconoces, bloquéalo.',
|
|
44382
|
+
loginAttemptMine: 'Sí, fui yo',
|
|
44383
|
+
loginAttemptBlock: 'No fui yo — bloquear',
|
|
44384
|
+
loginAttemptClose: 'Cerrar',
|
|
44385
|
+
loginAttemptDone: 'Dispositivo bloqueado. Restablece tu contraseña para reforzar tu cuenta.',
|
|
44386
|
+
loginAttemptUnknownDevice: 'Dispositivo desconocido',
|
|
44387
|
+
sessionsTitle: 'Sesiones activas',
|
|
44388
|
+
sessionsTitleCount: 'Sesiones activas ({n})',
|
|
44389
|
+
sessionsLoading: 'Cargando sesiones…',
|
|
44390
|
+
sessionsEmpty: 'No hay sesiones activas.',
|
|
44391
|
+
sessionsThis: 'Esta sesión',
|
|
44392
|
+
sessionsRevoke: 'Cerrar',
|
|
44393
|
+
sessionsRevokeAll: 'Cerrar todas las demás',
|
|
44394
|
+
sessionsViewAll: 'Ver todas',
|
|
44395
|
+
sessionsModalTitle: 'Todas las sesiones activas',
|
|
44396
|
+
sessionsRevoked: 'Sesión cerrada.',
|
|
44397
|
+
sessionsAllRevoked: 'Todas las demás sesiones fueron cerradas.',
|
|
44398
|
+
sessionsError: 'No se pudo completar la operación.',
|
|
44399
|
+
revokeAllConfirmTitle: 'Cerrar todas las otras sesiones',
|
|
44400
|
+
revokeAllConfirmMessage: 'Vas a cerrar todas tus sesiones excepto la actual. Tendrás que volver a iniciar sesión en los otros dispositivos.',
|
|
44401
|
+
revokeAllConfirm: 'Cerrar todas',
|
|
44402
|
+
cancel: 'Cancelar',
|
|
44403
|
+
quickActionsTitle: 'Otras opciones',
|
|
44404
|
+
changePassword: 'Cambiar contraseña',
|
|
44405
|
+
changePasswordDesc: 'Actualiza tu contraseña de acceso',
|
|
44406
|
+
changeEmail: 'Cambiar email',
|
|
44407
|
+
changeEmailDesc: 'Actualiza el correo electrónico de tu cuenta',
|
|
44408
|
+
mfa: 'Autenticación de dos pasos (MFA)',
|
|
44409
|
+
mfaDesc: 'Agrega un segundo factor a tu inicio de sesión',
|
|
44410
|
+
sessions: 'Sesiones activas',
|
|
44411
|
+
sessionsDesc: 'Revisa y cierra sesiones en otros dispositivos',
|
|
44412
|
+
policiesTitle: 'Políticas relacionadas',
|
|
44413
|
+
privacyLink: 'Política de Privacidad',
|
|
44414
|
+
cookiesLink: 'Política de Cookies',
|
|
44415
|
+
dataUsageLink: 'Uso de Datos',
|
|
44416
|
+
termsLink: 'Términos y Condiciones',
|
|
44417
|
+
thirdPartyLink: 'Software y Servicios de Terceros',
|
|
44418
|
+
legalNoticeLink: 'Aviso Legal',
|
|
44419
|
+
soonHint: 'Próximamente',
|
|
44420
|
+
},
|
|
44421
|
+
en: {
|
|
44422
|
+
pageTitle: 'Security',
|
|
44423
|
+
pageDescription: 'Passwords, MFA, and active sessions',
|
|
44424
|
+
blocked: 'Device blocked',
|
|
44425
|
+
tokenActionFailed: 'The email link is invalid or has expired.',
|
|
44426
|
+
loginAttemptTitle: 'Confirm sign-in',
|
|
44427
|
+
loginAttemptBody: "We detected a sign-in on this device. If it was you, there's nothing to do. If you don't recognize it, block it.",
|
|
44428
|
+
loginAttemptMine: 'Yes, it was me',
|
|
44429
|
+
loginAttemptBlock: 'Not me — block it',
|
|
44430
|
+
loginAttemptClose: 'Close',
|
|
44431
|
+
loginAttemptDone: 'Device blocked. Reset your password to secure your account.',
|
|
44432
|
+
loginAttemptUnknownDevice: 'Unknown device',
|
|
44433
|
+
sessionsTitle: 'Active sessions',
|
|
44434
|
+
sessionsTitleCount: 'Active sessions ({n})',
|
|
44435
|
+
sessionsLoading: 'Loading sessions…',
|
|
44436
|
+
sessionsEmpty: 'No active sessions.',
|
|
44437
|
+
sessionsThis: 'This session',
|
|
44438
|
+
sessionsRevoke: 'End',
|
|
44439
|
+
sessionsRevokeAll: 'End all others',
|
|
44440
|
+
sessionsViewAll: 'View all',
|
|
44441
|
+
sessionsModalTitle: 'All active sessions',
|
|
44442
|
+
sessionsRevoked: 'Session ended.',
|
|
44443
|
+
sessionsAllRevoked: 'All other sessions were ended.',
|
|
44444
|
+
sessionsError: 'Could not complete the operation.',
|
|
44445
|
+
revokeAllConfirmTitle: 'End all other sessions',
|
|
44446
|
+
revokeAllConfirmMessage: "You're about to end every session except this one. You'll need to sign in again on the other devices.",
|
|
44447
|
+
revokeAllConfirm: 'End all',
|
|
44448
|
+
cancel: 'Cancel',
|
|
44449
|
+
quickActionsTitle: 'Other options',
|
|
44450
|
+
changePassword: 'Change password',
|
|
44451
|
+
changePasswordDesc: 'Update your access password',
|
|
44452
|
+
changeEmail: 'Change email',
|
|
44453
|
+
changeEmailDesc: 'Update the email address on your account',
|
|
44454
|
+
mfa: 'Two-step verification (MFA)',
|
|
44455
|
+
mfaDesc: 'Add a second factor to your sign-in',
|
|
44456
|
+
sessions: 'Active sessions',
|
|
44457
|
+
sessionsDesc: 'Review and sign out from other devices',
|
|
44458
|
+
policiesTitle: 'Related policies',
|
|
44459
|
+
privacyLink: 'Privacy Policy',
|
|
44460
|
+
cookiesLink: 'Cookie Policy',
|
|
44461
|
+
dataUsageLink: 'Data Usage',
|
|
44462
|
+
termsLink: 'Terms and Conditions',
|
|
44463
|
+
thirdPartyLink: 'Third-Party Software and Services',
|
|
44464
|
+
legalNoticeLink: 'Legal Notice',
|
|
44465
|
+
soonHint: 'Coming soon',
|
|
44466
|
+
},
|
|
44467
|
+
};
|
|
44468
|
+
|
|
44469
|
+
addIcons({ laptopOutline, phonePortraitOutline });
|
|
44470
|
+
const DEFAULT_NAMESPACE = 'Settings.Security';
|
|
44471
|
+
/**
|
|
44472
|
+
* `val-security-view` — vista Seguridad full-feature autocontenida (organism).
|
|
44473
|
+
*
|
|
44474
|
+
* Promovida desde `showcase` bajo el proceso de ADR-021. Reúne en una sola vista:
|
|
44475
|
+
* - **Sesiones activas** — única vista user-facing de "dónde tengo sesión".
|
|
44476
|
+
* Lista inline (limit 3) + modal "Ver todas" (`val-session-list-modal`) +
|
|
44477
|
+
* revoke individual / revoke-all (con confirmación).
|
|
44478
|
+
* - **Action-cards** — cambiar contraseña, cambiar email y MFA (cada una abre
|
|
44479
|
+
* el modal correspondiente de la lib).
|
|
44480
|
+
* - **Políticas legales** — links resueltos vía `LegalLinkService` (apps
|
|
44481
|
+
* satélite apuntan al main site).
|
|
44482
|
+
*
|
|
44483
|
+
* Deep-links del email (gateados por `config.handleDeepLinks`, default `true`):
|
|
44484
|
+
* - `?token=...&action=...` → modal `val-login-attempt-modal` (autorizar/bloquear
|
|
44485
|
+
* el nuevo acceso).
|
|
44486
|
+
* - `?mfaCode=...` → abre el modal MFA con el código pre-cargado.
|
|
44487
|
+
*
|
|
44488
|
+
* NO renderiza ion-content — vive dentro de val-page-wrapper.
|
|
44489
|
+
*
|
|
44490
|
+
* Auto-registra sus defaults i18n (es/en) en el constructor si el consumer no
|
|
44491
|
+
* proveyó el namespace configurado (default `Settings.Security`).
|
|
44492
|
+
*/
|
|
44493
|
+
class SecurityViewComponent {
|
|
44494
|
+
/** Namespace i18n resuelto (capturado para llamadas no-reactivas). */
|
|
44495
|
+
get ns() {
|
|
44496
|
+
return this.resolvedConfig().i18nNamespace;
|
|
44497
|
+
}
|
|
44498
|
+
constructor() {
|
|
44499
|
+
this.nav = inject(NavigationService);
|
|
44500
|
+
this.i18n = inject(I18nService);
|
|
44501
|
+
this.deviceService = inject(DeviceService);
|
|
44502
|
+
this.legalLink = inject(LegalLinkService);
|
|
44503
|
+
this.toast = inject(ToastService);
|
|
44504
|
+
this.errors = inject(ValtechErrorService);
|
|
44505
|
+
this.router = inject(Router, { optional: true });
|
|
44506
|
+
this.route = inject(ActivatedRoute, { optional: true });
|
|
44507
|
+
this.sessionService = inject(SessionService);
|
|
44508
|
+
this.modalService = inject(ModalService);
|
|
44509
|
+
this.confirmDialog = inject(ConfirmationDialogService);
|
|
44510
|
+
this.resolvedConfig = computed(() => {
|
|
44511
|
+
const fromRoute = (this.route?.snapshot.data['securityConfig'] ?? {});
|
|
44512
|
+
const merged = { ...fromRoute, ...(this.config ?? {}) };
|
|
44513
|
+
return {
|
|
44514
|
+
showSessions: merged.showSessions ?? true,
|
|
44515
|
+
showChangePassword: merged.showChangePassword ?? true,
|
|
44516
|
+
showChangeEmail: merged.showChangeEmail ?? true,
|
|
44517
|
+
showMfa: merged.showMfa ?? true,
|
|
44518
|
+
showPolicies: merged.showPolicies ?? true,
|
|
44519
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
44520
|
+
handleDeepLinks: merged.handleDeepLinks ?? true,
|
|
44521
|
+
homeRoute: merged.homeRoute ?? '/app/home',
|
|
44522
|
+
onMfaCompleted: merged.onMfaCompleted,
|
|
44523
|
+
};
|
|
44524
|
+
});
|
|
44525
|
+
/**
|
|
44526
|
+
* Lista de dispositivos del user. No se renderiza — solo alimenta el modal
|
|
44527
|
+
* de confirmación de inicio de sesión (`handleActionTokenFromUrl`) para
|
|
44528
|
+
* resolver un label legible desde el `targetId` del token.
|
|
44529
|
+
*/
|
|
44530
|
+
this.devices = signal([]);
|
|
44531
|
+
this.sessions = signal([]);
|
|
44532
|
+
this.sessionsLoading = signal(true);
|
|
44533
|
+
this.isChangeEmailModalOpen = signal(false);
|
|
44534
|
+
this.isChangePasswordModalOpen = signal(false);
|
|
44535
|
+
this.isMfaModalOpen = signal(false);
|
|
44536
|
+
/** Código MFA del deep-link `/verify` — pre-carga el modal en confirmación. */
|
|
44537
|
+
this.mfaPrefillCode = signal(undefined);
|
|
44538
|
+
this.pageTitle = computed(() => this.tt('pageTitle'));
|
|
44539
|
+
this.pageDescription = computed(() => this.tt('pageDescription'));
|
|
44540
|
+
this.quickActionsTitle = computed(() => this.tt('quickActionsTitle'));
|
|
44541
|
+
this.policiesTitle = computed(() => this.tt('policiesTitle'));
|
|
44542
|
+
this.hasActionCards = computed(() => this.actionCards().length > 0);
|
|
44543
|
+
this.actionCards = computed(() => {
|
|
44544
|
+
this.i18n.lang();
|
|
44545
|
+
const cfg = this.resolvedConfig();
|
|
44546
|
+
const cards = [];
|
|
44547
|
+
if (cfg.showChangePassword) {
|
|
44548
|
+
cards.push({
|
|
44549
|
+
token: 'action-change-password',
|
|
44550
|
+
title: this.tt('changePassword'),
|
|
44551
|
+
description: this.tt('changePasswordDesc'),
|
|
44552
|
+
icon: { ionicon: 'key-outline', color: 'dark', backgroundColor: 'light' },
|
|
44553
|
+
showChevron: true,
|
|
44554
|
+
shadowed: true,
|
|
44555
|
+
});
|
|
44556
|
+
}
|
|
44557
|
+
if (cfg.showChangeEmail) {
|
|
44558
|
+
cards.push({
|
|
44559
|
+
token: 'action-change-email',
|
|
44560
|
+
title: this.tt('changeEmail'),
|
|
44561
|
+
description: this.tt('changeEmailDesc'),
|
|
44562
|
+
icon: { ionicon: 'mail-outline', color: 'dark', backgroundColor: 'light' },
|
|
44563
|
+
showChevron: true,
|
|
44564
|
+
shadowed: true,
|
|
44565
|
+
});
|
|
44566
|
+
}
|
|
44567
|
+
if (cfg.showMfa) {
|
|
44568
|
+
cards.push({
|
|
44569
|
+
token: 'action-mfa',
|
|
44570
|
+
title: this.tt('mfa'),
|
|
44571
|
+
description: this.tt('mfaDesc'),
|
|
44572
|
+
icon: { ionicon: 'shield-checkmark-outline', color: 'dark', backgroundColor: 'light' },
|
|
44573
|
+
showChevron: true,
|
|
44574
|
+
shadowed: true,
|
|
44575
|
+
});
|
|
44576
|
+
}
|
|
44577
|
+
return cards;
|
|
44578
|
+
});
|
|
44579
|
+
/**
|
|
44580
|
+
* Fuente única de links legales del producto. Se resuelven vía
|
|
44581
|
+
* `LegalLinkService` — apps satélite apuntan al main site, el main site los
|
|
44582
|
+
* deja relativos.
|
|
44583
|
+
*/
|
|
44584
|
+
this.policyLinksProps = computed(() => {
|
|
44585
|
+
const locale = this.i18n.lang();
|
|
44586
|
+
const link = (path, labelKey) => ({
|
|
44587
|
+
label: this.tt(labelKey),
|
|
44588
|
+
href: this.legalLink.resolve(path, { locale }),
|
|
44589
|
+
external: this.legalLink.isExternal(path),
|
|
44590
|
+
});
|
|
44591
|
+
return {
|
|
44592
|
+
links: [
|
|
44593
|
+
link('/legal/terms', 'termsLink'),
|
|
44594
|
+
link('/legal/privacy', 'privacyLink'),
|
|
44595
|
+
link('/legal/cookies', 'cookiesLink'),
|
|
44596
|
+
link('/legal/data-usage', 'dataUsageLink'),
|
|
44597
|
+
link('/legal/third-party', 'thirdPartyLink'),
|
|
44598
|
+
link('/legal/legal-notice', 'legalNoticeLink'),
|
|
44599
|
+
],
|
|
44600
|
+
showDividers: true,
|
|
44601
|
+
};
|
|
44602
|
+
});
|
|
44603
|
+
// ===========================================================================
|
|
44604
|
+
// Sessions
|
|
44605
|
+
// ===========================================================================
|
|
44606
|
+
this.sessionsTitle = computed(() => this.tt('sessionsTitle'));
|
|
44607
|
+
this.sessionsEmptyLabel = computed(() => this.tt('sessionsEmpty'));
|
|
44608
|
+
this.sessionsThis = computed(() => this.tt('sessionsThis'));
|
|
44609
|
+
this.hasOtherSessions = computed(() => this.sessions().some(s => !s.isCurrent));
|
|
44610
|
+
/** Sesiones ordenadas: current primero, resto por createdAt desc. */
|
|
44611
|
+
this.sortedSessions = computed(() => {
|
|
44612
|
+
const all = [...this.sessions()];
|
|
44613
|
+
return all.sort((a, b) => {
|
|
44614
|
+
if (a.isCurrent && !b.isCurrent)
|
|
44615
|
+
return -1;
|
|
44616
|
+
if (!a.isCurrent && b.isCurrent)
|
|
44617
|
+
return 1;
|
|
44618
|
+
return (b.createdAt || '').localeCompare(a.createdAt || '');
|
|
44619
|
+
});
|
|
44620
|
+
});
|
|
44621
|
+
/** Limit de sesiones mostradas inline. Si total > limit, "Ver todas" abre modal. */
|
|
44622
|
+
this.VISIBLE_LIMIT = 3;
|
|
44623
|
+
this.visibleSessions = computed(() => this.sortedSessions().slice(0, this.VISIBLE_LIMIT));
|
|
44624
|
+
this.hasMoreSessions = computed(() => this.sortedSessions().length > this.VISIBLE_LIMIT);
|
|
44625
|
+
/** Titulo con count: "Sesiones activas (4)". Sin count si N=0. */
|
|
44626
|
+
this.sessionsTitleWithCount = computed(() => {
|
|
44627
|
+
const n = this.sortedSessions().length;
|
|
44628
|
+
if (n === 0)
|
|
44629
|
+
return this.sessionsTitle();
|
|
44630
|
+
return this.tt('sessionsTitleCount').replace('{n}', String(n));
|
|
44631
|
+
});
|
|
44632
|
+
this.revokeOneButtonProps = computed(() => ({
|
|
44633
|
+
text: this.tt('sessionsRevoke'),
|
|
44634
|
+
color: 'dark',
|
|
44635
|
+
fill: 'clear',
|
|
44636
|
+
size: 'small',
|
|
44637
|
+
type: 'button',
|
|
44638
|
+
token: 'session-revoke',
|
|
44639
|
+
}));
|
|
44640
|
+
this.viewAllButtonProps = computed(() => ({
|
|
44641
|
+
text: this.tt('sessionsViewAll'),
|
|
44642
|
+
color: 'dark',
|
|
44643
|
+
fill: 'clear',
|
|
44644
|
+
size: 'small',
|
|
44645
|
+
type: 'button',
|
|
44646
|
+
state: ComponentStates.ENABLED,
|
|
44647
|
+
}));
|
|
44648
|
+
/** Action-header de Sesiones: title + count + boton "Cerrar todas las demas". */
|
|
44649
|
+
this.sessionsHeader = computed(() => ({
|
|
44650
|
+
headingKind: 'title',
|
|
44651
|
+
title: {
|
|
44652
|
+
size: 'medium',
|
|
44653
|
+
color: 'dark',
|
|
44654
|
+
bold: true,
|
|
44655
|
+
content: this.sessionsTitleWithCount(),
|
|
44656
|
+
},
|
|
44657
|
+
action: {
|
|
44658
|
+
text: this.tt('sessionsRevokeAll'),
|
|
44659
|
+
color: 'dark',
|
|
44660
|
+
fill: 'outline',
|
|
44661
|
+
size: 'small',
|
|
44662
|
+
type: 'button',
|
|
44663
|
+
state: ComponentStates.ENABLED,
|
|
44664
|
+
token: 'session-revoke-all',
|
|
44665
|
+
handler: () => this.onRevokeAll(),
|
|
44666
|
+
},
|
|
44667
|
+
}));
|
|
44668
|
+
const ns = this.ns;
|
|
44669
|
+
if (!this.i18n.hasNamespace(ns)) {
|
|
44670
|
+
this.i18n.registerContent(ns, SECURITY_VIEW_I18N);
|
|
44671
|
+
}
|
|
44672
|
+
this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
|
|
44673
|
+
// Carga inicial de sesiones - recarga reactiva a cambio de locale para
|
|
44674
|
+
// que los textos y metadatos se mantengan frescos. Los devices solo se
|
|
44675
|
+
// cargan on-demand desde el flujo del modal (handleActionTokenFromUrl).
|
|
44676
|
+
effect(() => {
|
|
44677
|
+
this.i18n.lang();
|
|
44678
|
+
const showSessions = this.resolvedConfig().showSessions;
|
|
44679
|
+
untracked(() => {
|
|
44680
|
+
if (showSessions)
|
|
44681
|
+
void this.loadSessions();
|
|
44682
|
+
});
|
|
44683
|
+
}, { allowSignalWrites: true });
|
|
44684
|
+
// Pull-to-refresh estandar (Regla #3) - re-fetchea las sesiones activas.
|
|
44685
|
+
connectPageRefresh(() => this.loadSessions());
|
|
44686
|
+
// Deep-links del email - solo si estan habilitados Y hay ruta (montada como
|
|
44687
|
+
// route). En modo embebido sin ruta es no-op.
|
|
44688
|
+
if (this.resolvedConfig().handleDeepLinks && this.route) {
|
|
44689
|
+
// Si el user llego via link del email (?token=...&action=...), abre el
|
|
44690
|
+
// modal de confirmacion para autorizar/rechazar el intento.
|
|
44691
|
+
void this.handleActionTokenFromUrl();
|
|
44692
|
+
// Deep-link del email de setup MFA-email (?mfaCode=...).
|
|
44693
|
+
this.handleMfaCodeFromUrl();
|
|
44694
|
+
}
|
|
44695
|
+
}
|
|
44696
|
+
/**
|
|
44697
|
+
* Llega vía link del email de "nuevo acceso" (`?token=...`). Valida el token
|
|
44698
|
+
* (sin ejecutar) y levanta el modal de confirmación. El bloqueo solo se
|
|
44699
|
+
* ejecuta si el user confirma "no fui yo" dentro del modal.
|
|
44700
|
+
*/
|
|
44701
|
+
async handleActionTokenFromUrl() {
|
|
44702
|
+
const token = this.route?.snapshot.queryParamMap.get('token');
|
|
44703
|
+
if (!token)
|
|
44704
|
+
return;
|
|
44705
|
+
// Limpia ?token/?action de la URL ya - el modal es la fuente de verdad;
|
|
44706
|
+
// un refresh no debe re-abrirlo ni re-validar.
|
|
44707
|
+
void this.router?.navigate([], {
|
|
44708
|
+
relativeTo: this.route ?? undefined,
|
|
44709
|
+
queryParams: { token: null, action: null },
|
|
44710
|
+
queryParamsHandling: 'merge',
|
|
44711
|
+
replaceUrl: true,
|
|
44712
|
+
});
|
|
44713
|
+
let validation;
|
|
44714
|
+
try {
|
|
44715
|
+
validation = await firstValueFrom(this.deviceService.validateAction(token));
|
|
44716
|
+
}
|
|
44717
|
+
catch (err) {
|
|
44718
|
+
this.errors.handle(err, {
|
|
44719
|
+
context: 'security.validateActionToken',
|
|
44720
|
+
fallbackKey: 'tokenActionFailed',
|
|
44721
|
+
i18nNamespace: this.ns,
|
|
44722
|
+
});
|
|
44723
|
+
return;
|
|
44724
|
+
}
|
|
44725
|
+
if (!validation.valid) {
|
|
44726
|
+
this.toastTokenInvalid();
|
|
44727
|
+
return;
|
|
44728
|
+
}
|
|
44729
|
+
// Resuelve el dispositivo objetivo desde la lista cargada para mostrarlo.
|
|
44730
|
+
await this.loadDevices();
|
|
44731
|
+
const target = this.devices().find(d => d.deviceId === validation.targetId);
|
|
44732
|
+
const isMobile = target?.platform === 'ios' || target?.platform === 'android';
|
|
44733
|
+
const onBlock = async () => {
|
|
44734
|
+
await firstValueFrom(this.deviceService.executeAction(token));
|
|
44735
|
+
await this.loadDevices();
|
|
44736
|
+
};
|
|
44737
|
+
const result = await this.modalService.open({
|
|
44738
|
+
component: LoginAttemptModalComponent,
|
|
44739
|
+
componentProps: {
|
|
44740
|
+
title: this.tt('loginAttemptTitle'),
|
|
44741
|
+
body: this.tt('loginAttemptBody'),
|
|
44742
|
+
deviceLabel: target ? this.describeDevice(target) : this.tt('loginAttemptUnknownDevice'),
|
|
44743
|
+
metaLabel: target ? this.formatDate(target.lastActive) : '',
|
|
44744
|
+
mineLabel: this.tt('loginAttemptMine'),
|
|
44745
|
+
blockLabel: this.tt('loginAttemptBlock'),
|
|
44746
|
+
closeLabel: this.tt('loginAttemptClose'),
|
|
44747
|
+
doneLabel: this.tt('loginAttemptDone'),
|
|
44748
|
+
isMobile,
|
|
44749
|
+
onBlock,
|
|
44750
|
+
},
|
|
44751
|
+
});
|
|
44752
|
+
if (result.role === 'blocked') {
|
|
44753
|
+
this.toastOk(this.tt('blocked'));
|
|
44754
|
+
}
|
|
44755
|
+
else if (result.role === 'error') {
|
|
44756
|
+
this.toastTokenInvalid();
|
|
44757
|
+
}
|
|
44758
|
+
}
|
|
44759
|
+
toastTokenInvalid() {
|
|
44760
|
+
this.toast.show({
|
|
44761
|
+
message: this.tt('tokenActionFailed'),
|
|
44762
|
+
duration: 3500,
|
|
44763
|
+
color: 'dark',
|
|
44764
|
+
position: 'top',
|
|
44765
|
+
});
|
|
44766
|
+
}
|
|
44767
|
+
async loadDevices() {
|
|
44768
|
+
try {
|
|
44769
|
+
const list = await firstValueFrom(this.deviceService.listDevices());
|
|
44770
|
+
this.devices.set(list || []);
|
|
44771
|
+
}
|
|
44772
|
+
catch {
|
|
44773
|
+
this.devices.set([]);
|
|
44774
|
+
}
|
|
44775
|
+
}
|
|
44776
|
+
onActionClick(action) {
|
|
44777
|
+
if (action.token === 'action-change-password') {
|
|
44778
|
+
this.isChangePasswordModalOpen.set(true);
|
|
44779
|
+
return;
|
|
44780
|
+
}
|
|
44781
|
+
if (action.token === 'action-change-email') {
|
|
44782
|
+
this.isChangeEmailModalOpen.set(true);
|
|
44783
|
+
return;
|
|
44784
|
+
}
|
|
44785
|
+
if (action.token === 'action-mfa') {
|
|
44786
|
+
this.mfaPrefillCode.set(undefined);
|
|
44787
|
+
this.isMfaModalOpen.set(true);
|
|
44788
|
+
}
|
|
44789
|
+
}
|
|
44790
|
+
onMfaModalDismissed() {
|
|
44791
|
+
this.isMfaModalOpen.set(false);
|
|
44792
|
+
this.mfaPrefillCode.set(undefined);
|
|
44793
|
+
}
|
|
44794
|
+
/**
|
|
44795
|
+
* El user llegó vía link del email, confirmó MFA en el modal. Cerramos el
|
|
44796
|
+
* modal y, si hay hook `onMfaCompleted`, lo invocamos; si no, navegamos al
|
|
44797
|
+
* `homeRoute` configurado — quedarse en Settings → Security daría la
|
|
44798
|
+
* sensación de "tarea inconclusa" cuando el flow ya terminó.
|
|
44799
|
+
*/
|
|
44800
|
+
onMfaEnabledViaDeeplink() {
|
|
44801
|
+
this.isMfaModalOpen.set(false);
|
|
44802
|
+
this.mfaPrefillCode.set(undefined);
|
|
44803
|
+
const cfg = this.resolvedConfig();
|
|
44804
|
+
if (cfg.onMfaCompleted) {
|
|
44805
|
+
cfg.onMfaCompleted();
|
|
44806
|
+
}
|
|
44807
|
+
else {
|
|
44808
|
+
void this.router?.navigateByUrl(cfg.homeRoute);
|
|
44809
|
+
}
|
|
44810
|
+
}
|
|
44811
|
+
/**
|
|
44812
|
+
* Deep-link `/verify?...&verify-code=` → redirige a esta vista con `?mfaCode=`.
|
|
44813
|
+
* Abre el modal MFA con el código pre-cargado en el paso de confirmación.
|
|
44814
|
+
*/
|
|
44815
|
+
handleMfaCodeFromUrl() {
|
|
44816
|
+
const code = this.route?.snapshot.queryParamMap.get('mfaCode');
|
|
44817
|
+
if (!code)
|
|
44818
|
+
return;
|
|
44819
|
+
// Limpia el query param - un refresh no debe re-abrir el modal.
|
|
44820
|
+
void this.router?.navigate([], {
|
|
44821
|
+
relativeTo: this.route ?? undefined,
|
|
44822
|
+
queryParams: { mfaCode: null },
|
|
44823
|
+
queryParamsHandling: 'merge',
|
|
44824
|
+
replaceUrl: true,
|
|
44825
|
+
});
|
|
44826
|
+
this.mfaPrefillCode.set(code);
|
|
44827
|
+
this.isMfaModalOpen.set(true);
|
|
44828
|
+
}
|
|
44829
|
+
describeDevice(d) {
|
|
44830
|
+
const parts = [d.platform, d.browser, d.location].filter(Boolean);
|
|
44831
|
+
return parts.join(' · ') || d.deviceId;
|
|
44832
|
+
}
|
|
44833
|
+
formatDate(iso) {
|
|
44834
|
+
if (!iso)
|
|
44835
|
+
return '';
|
|
44836
|
+
try {
|
|
44837
|
+
return new Date(iso).toLocaleString(this.i18n.lang());
|
|
44838
|
+
}
|
|
44839
|
+
catch {
|
|
44840
|
+
return iso;
|
|
44841
|
+
}
|
|
44842
|
+
}
|
|
44843
|
+
toastOk(msg) {
|
|
44844
|
+
this.toast.show({
|
|
44845
|
+
message: msg,
|
|
44846
|
+
duration: 2200,
|
|
44847
|
+
color: 'dark',
|
|
44848
|
+
position: 'top',
|
|
44849
|
+
});
|
|
44850
|
+
}
|
|
44851
|
+
// ===========================================================================
|
|
44852
|
+
// Sessions - métodos
|
|
44853
|
+
// ===========================================================================
|
|
44854
|
+
/** Icono según device: phone si UA mobile, sino laptop. */
|
|
44855
|
+
deviceIcon(s) {
|
|
44856
|
+
const ua = (s.deviceInfo || '').toLowerCase();
|
|
44857
|
+
const isMobile = /iphone|ipad|ipod|android|mobile/.test(ua);
|
|
44858
|
+
return isMobile ? 'phone-portrait-outline' : 'laptop-outline';
|
|
44859
|
+
}
|
|
44860
|
+
formatSessionMeta(s) {
|
|
44861
|
+
const parts = [];
|
|
44862
|
+
if (s.location)
|
|
44863
|
+
parts.push(s.location);
|
|
44864
|
+
else if (s.ip)
|
|
44865
|
+
parts.push(s.ip);
|
|
44866
|
+
if (s.createdAt)
|
|
44867
|
+
parts.push(new Date(s.createdAt).toLocaleString());
|
|
44868
|
+
return parts.join(' · ');
|
|
44869
|
+
}
|
|
44870
|
+
async onRevokeOne(sessionId) {
|
|
44871
|
+
try {
|
|
44872
|
+
await firstValueFrom(this.sessionService.revokeSession(sessionId));
|
|
44873
|
+
this.toast.show({
|
|
44874
|
+
message: this.tt('sessionsRevoked'),
|
|
44875
|
+
duration: 2500,
|
|
44876
|
+
color: 'dark',
|
|
44877
|
+
position: 'top',
|
|
44878
|
+
});
|
|
44879
|
+
await this.loadSessions();
|
|
44880
|
+
}
|
|
44881
|
+
catch (err) {
|
|
44882
|
+
this.errors.handle(err, {
|
|
44883
|
+
context: 'security.revokeSession',
|
|
44884
|
+
fallbackKey: 'sessionsError',
|
|
44885
|
+
i18nNamespace: this.ns,
|
|
44886
|
+
});
|
|
44887
|
+
}
|
|
44888
|
+
}
|
|
44889
|
+
async onRevokeAll() {
|
|
44890
|
+
const result = await this.confirmDialog.confirmDestructive({
|
|
44891
|
+
title: this.tt('revokeAllConfirmTitle'),
|
|
44892
|
+
message: this.tt('revokeAllConfirmMessage'),
|
|
44893
|
+
confirmButton: { text: this.tt('revokeAllConfirm') },
|
|
44894
|
+
cancelButton: { text: this.tt('cancel') },
|
|
44895
|
+
});
|
|
44896
|
+
if (!result.confirmed)
|
|
44897
|
+
return;
|
|
44898
|
+
try {
|
|
44899
|
+
await firstValueFrom(this.sessionService.revokeAllSessions());
|
|
44900
|
+
this.toast.show({
|
|
44901
|
+
message: this.tt('sessionsAllRevoked'),
|
|
44902
|
+
duration: 2500,
|
|
44903
|
+
color: 'dark',
|
|
44904
|
+
position: 'top',
|
|
44905
|
+
});
|
|
44906
|
+
await this.loadSessions();
|
|
44907
|
+
}
|
|
44908
|
+
catch (err) {
|
|
44909
|
+
this.errors.handle(err, {
|
|
44910
|
+
context: 'security.revokeAllSessions',
|
|
44911
|
+
fallbackKey: 'sessionsError',
|
|
44912
|
+
i18nNamespace: this.ns,
|
|
44913
|
+
});
|
|
44914
|
+
}
|
|
44915
|
+
}
|
|
44916
|
+
/** Abre modal con la lista completa. Cada revoke pasa por `onRevoke`. */
|
|
44917
|
+
async openSessionsModal() {
|
|
44918
|
+
const onRevoke = async (sessionId) => {
|
|
44919
|
+
try {
|
|
44920
|
+
await firstValueFrom(this.sessionService.revokeSession(sessionId));
|
|
44921
|
+
this.toast.show({
|
|
44922
|
+
message: this.tt('sessionsRevoked'),
|
|
44923
|
+
duration: 2500,
|
|
44924
|
+
color: 'dark',
|
|
44925
|
+
position: 'top',
|
|
44926
|
+
});
|
|
44927
|
+
}
|
|
44928
|
+
catch (err) {
|
|
44929
|
+
this.errors.handle(err, {
|
|
44930
|
+
context: 'security.revokeSession.modal',
|
|
44931
|
+
fallbackKey: 'sessionsError',
|
|
44932
|
+
i18nNamespace: this.ns,
|
|
44933
|
+
});
|
|
44934
|
+
throw err;
|
|
44935
|
+
}
|
|
44936
|
+
await this.loadSessions();
|
|
44937
|
+
return this.sortedSessions();
|
|
44938
|
+
};
|
|
44939
|
+
await this.modalService.openSheet({
|
|
44940
|
+
component: SessionListModalComponent,
|
|
44941
|
+
componentProps: {
|
|
44942
|
+
sessions: this.sortedSessions(),
|
|
44943
|
+
title: this.tt('sessionsModalTitle'),
|
|
44944
|
+
closeLabel: this.tt('loginAttemptClose'),
|
|
44945
|
+
thisLabel: this.sessionsThis(),
|
|
44946
|
+
revokeLabel: this.tt('sessionsRevoke'),
|
|
44947
|
+
onRevoke,
|
|
44948
|
+
},
|
|
44949
|
+
initialBreakpoint: 1,
|
|
44950
|
+
breakpoints: [0, 1],
|
|
44951
|
+
showHandle: false,
|
|
44952
|
+
});
|
|
44953
|
+
}
|
|
44954
|
+
async loadSessions() {
|
|
44955
|
+
this.sessionsLoading.set(true);
|
|
44956
|
+
try {
|
|
44957
|
+
const list = await firstValueFrom(this.sessionService.listSessions());
|
|
44958
|
+
this.sessions.set(list);
|
|
44959
|
+
}
|
|
44960
|
+
catch (err) {
|
|
44961
|
+
console.error('[Security] listSessions failed:', err);
|
|
44962
|
+
this.sessions.set([]);
|
|
44963
|
+
}
|
|
44964
|
+
finally {
|
|
44965
|
+
this.sessionsLoading.set(false);
|
|
44966
|
+
}
|
|
44967
|
+
}
|
|
44968
|
+
/** Lee del namespace configurado. */
|
|
44969
|
+
tt(key) {
|
|
44970
|
+
return this.i18n.t(key, this.ns);
|
|
44971
|
+
}
|
|
44972
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SecurityViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
44973
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SecurityViewComponent, isStandalone: true, selector: "val-security-view", inputs: { config: "config" }, ngImport: i0, template: `
|
|
44974
|
+
<div class="page">
|
|
44975
|
+
<header class="page-header">
|
|
44976
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
|
|
44977
|
+
<val-title
|
|
44978
|
+
[props]="{
|
|
44979
|
+
size: 'large',
|
|
44980
|
+
color: 'dark',
|
|
44981
|
+
bold: false,
|
|
44982
|
+
content: pageDescription(),
|
|
44983
|
+
}"
|
|
44984
|
+
/>
|
|
44985
|
+
</header>
|
|
44986
|
+
|
|
44987
|
+
@if (resolvedConfig().showSessions) {
|
|
44988
|
+
<!-- 1. Active sessions - unica vista user-facing de donde tengo sesion. -->
|
|
44989
|
+
<section class="settings-section">
|
|
44990
|
+
@if (hasOtherSessions()) {
|
|
44991
|
+
<val-action-header [props]="sessionsHeader()" />
|
|
44992
|
+
} @else {
|
|
44993
|
+
<val-title
|
|
44994
|
+
[props]="{
|
|
44995
|
+
size: 'medium',
|
|
44996
|
+
color: 'dark',
|
|
44997
|
+
bold: true,
|
|
44998
|
+
content: sessionsTitleWithCount(),
|
|
44999
|
+
}"
|
|
45000
|
+
/>
|
|
45001
|
+
}
|
|
45002
|
+
|
|
45003
|
+
<div class="section-body">
|
|
45004
|
+
@if (sessionsLoading()) {
|
|
45005
|
+
<val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
|
|
45006
|
+
} @else if (sessions().length === 0) {
|
|
45007
|
+
<val-text
|
|
45008
|
+
[props]="{
|
|
45009
|
+
content: sessionsEmptyLabel(),
|
|
45010
|
+
size: 'small',
|
|
45011
|
+
color: 'medium',
|
|
45012
|
+
bold: false,
|
|
45013
|
+
}"
|
|
45014
|
+
/>
|
|
45015
|
+
} @else {
|
|
45016
|
+
<ul class="sessions">
|
|
45017
|
+
@for (s of visibleSessions(); track s.sessionId) {
|
|
45018
|
+
<li class="session" [class.session--current]="s.isCurrent">
|
|
45019
|
+
<ion-icon class="session__icon" [name]="deviceIcon(s)" aria-hidden="true" />
|
|
45020
|
+
<div class="session__body">
|
|
45021
|
+
<val-text
|
|
45022
|
+
[props]="{
|
|
45023
|
+
content: s.deviceInfo || s.deviceId || '—',
|
|
45024
|
+
size: 'medium',
|
|
45025
|
+
color: 'dark',
|
|
45026
|
+
bold: true,
|
|
45027
|
+
}"
|
|
45028
|
+
/>
|
|
45029
|
+
<val-text
|
|
45030
|
+
[props]="{
|
|
45031
|
+
content: formatSessionMeta(s),
|
|
45032
|
+
size: 'small',
|
|
45033
|
+
color: 'medium',
|
|
45034
|
+
bold: false,
|
|
45035
|
+
}"
|
|
45036
|
+
/>
|
|
45037
|
+
</div>
|
|
45038
|
+
<div class="session__end">
|
|
45039
|
+
@if (s.isCurrent) {
|
|
45040
|
+
<span class="session__badge">{{ sessionsThis() }}</span>
|
|
45041
|
+
} @else {
|
|
45042
|
+
<val-button [props]="revokeOneButtonProps()" (click)="onRevokeOne(s.sessionId)" />
|
|
45043
|
+
}
|
|
45044
|
+
</div>
|
|
45045
|
+
</li>
|
|
45046
|
+
}
|
|
45047
|
+
</ul>
|
|
45048
|
+
@if (hasMoreSessions()) {
|
|
45049
|
+
<div class="row-actions row-actions--center">
|
|
45050
|
+
<val-button [props]="viewAllButtonProps()" (click)="openSessionsModal()" />
|
|
45051
|
+
</div>
|
|
45052
|
+
}
|
|
45053
|
+
}
|
|
45054
|
+
</div>
|
|
45055
|
+
</section>
|
|
45056
|
+
}
|
|
45057
|
+
|
|
45058
|
+
@if (hasActionCards()) {
|
|
45059
|
+
<!-- 2. Quick actions: change password, change email, MFA -->
|
|
45060
|
+
<section class="settings-section">
|
|
45061
|
+
<h2>{{ quickActionsTitle() }}</h2>
|
|
45062
|
+
<ion-grid class="cards-grid">
|
|
45063
|
+
<ion-row>
|
|
45064
|
+
@for (action of actionCards(); track action.token) {
|
|
45065
|
+
<ion-col size="12" size-sm="6">
|
|
45066
|
+
<val-action-card [props]="action" (onClick)="onActionClick(action)" />
|
|
45067
|
+
</ion-col>
|
|
45068
|
+
}
|
|
45069
|
+
</ion-row>
|
|
45070
|
+
</ion-grid>
|
|
45071
|
+
</section>
|
|
45072
|
+
}
|
|
45073
|
+
|
|
45074
|
+
@if (resolvedConfig().showPolicies) {
|
|
45075
|
+
<!-- 3. Related policies -->
|
|
45076
|
+
<section class="settings-section">
|
|
45077
|
+
<h2>{{ policiesTitle() }}</h2>
|
|
45078
|
+
<val-page-links [props]="policyLinksProps()" />
|
|
45079
|
+
</section>
|
|
45080
|
+
}
|
|
45081
|
+
|
|
45082
|
+
<val-change-email-modal
|
|
45083
|
+
[isOpen]="isChangeEmailModalOpen()"
|
|
45084
|
+
(changed)="isChangeEmailModalOpen.set(false)"
|
|
45085
|
+
(dismissed)="isChangeEmailModalOpen.set(false)"
|
|
45086
|
+
/>
|
|
45087
|
+
|
|
45088
|
+
<val-change-password-modal
|
|
45089
|
+
[isOpen]="isChangePasswordModalOpen()"
|
|
45090
|
+
(changed)="isChangePasswordModalOpen.set(false)"
|
|
45091
|
+
(dismissed)="isChangePasswordModalOpen.set(false)"
|
|
45092
|
+
/>
|
|
45093
|
+
|
|
45094
|
+
<val-mfa-modal
|
|
45095
|
+
[isOpen]="isMfaModalOpen()"
|
|
45096
|
+
[prefillCode]="mfaPrefillCode()"
|
|
45097
|
+
(dismissed)="onMfaModalDismissed()"
|
|
45098
|
+
(enabledViaDeeplink)="onMfaEnabledViaDeeplink()"
|
|
45099
|
+
/>
|
|
45100
|
+
</div>
|
|
45101
|
+
`, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}h2{font-size:16px;font-weight:600;margin:0 0 4px}.cards-grid{margin:0 -4px;padding:0}.cards-grid ion-col{padding:4px;display:flex;flex-direction:column}.cards-grid ion-col val-action-card{flex:1;display:flex;flex-direction:column}.section-body{margin-top:16px;display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.row-actions--center{display:flex;justify-content:center;margin-top:4px}.sessions{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:10px}.session{display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:14px;background:var(--ion-color-light, rgba(0, 0, 0, .04));border:1px solid transparent;transition:background .15s ease,border-color .15s ease}.session--current{background:#2dd36f1a;border-color:#2dd36f4d}.session__icon{font-size:26px;color:var(--ion-color-dark, #1a1a1a);flex-shrink:0}.session--current .session__icon{color:var(--ion-color-success-shade, #28ba62)}.session__body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.session__end{flex-shrink:0}.session__badge{display:inline-block;padding:4px 10px;font-size:11px;font-weight:700;letter-spacing:.02em;text-transform:uppercase;color:var(--ion-color-success-shade, #28ba62);background:#2dd36f2e;border-radius:999px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: IonRow, selector: "ion-row" }, { kind: "component", type: IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: ActionCardComponent, selector: "val-action-card", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: ActionHeaderComponent, selector: "val-action-header", inputs: ["props"] }, { kind: "component", type: PageLinksComponent, selector: "val-page-links", inputs: ["props"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }, { kind: "component", type: ChangeEmailModalComponent, selector: "val-change-email-modal", inputs: ["isOpen"], outputs: ["changed", "dismissed"] }, { kind: "component", type: ChangePasswordModalComponent, selector: "val-change-password-modal", inputs: ["isOpen"], outputs: ["changed", "dismissed"] }, { kind: "component", type: MfaModalComponent, selector: "val-mfa-modal", inputs: ["isOpen", "prefillCode"], outputs: ["changed", "enabledViaDeeplink", "dismissed"] }] }); }
|
|
45102
|
+
}
|
|
45103
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SecurityViewComponent, decorators: [{
|
|
45104
|
+
type: Component,
|
|
45105
|
+
args: [{ selector: 'val-security-view', standalone: true, imports: [
|
|
45106
|
+
CommonModule,
|
|
45107
|
+
IonGrid,
|
|
45108
|
+
IonRow,
|
|
45109
|
+
IonCol,
|
|
45110
|
+
IonIcon,
|
|
45111
|
+
DisplayComponent,
|
|
45112
|
+
TitleComponent,
|
|
45113
|
+
TextComponent,
|
|
45114
|
+
ButtonComponent,
|
|
45115
|
+
ActionCardComponent,
|
|
45116
|
+
ActionHeaderComponent,
|
|
45117
|
+
PageLinksComponent,
|
|
45118
|
+
SkeletonLayoutComponent,
|
|
45119
|
+
ChangeEmailModalComponent,
|
|
45120
|
+
ChangePasswordModalComponent,
|
|
45121
|
+
MfaModalComponent,
|
|
45122
|
+
], template: `
|
|
45123
|
+
<div class="page">
|
|
45124
|
+
<header class="page-header">
|
|
45125
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
|
|
45126
|
+
<val-title
|
|
45127
|
+
[props]="{
|
|
45128
|
+
size: 'large',
|
|
45129
|
+
color: 'dark',
|
|
45130
|
+
bold: false,
|
|
45131
|
+
content: pageDescription(),
|
|
45132
|
+
}"
|
|
45133
|
+
/>
|
|
45134
|
+
</header>
|
|
45135
|
+
|
|
45136
|
+
@if (resolvedConfig().showSessions) {
|
|
45137
|
+
<!-- 1. Active sessions - unica vista user-facing de donde tengo sesion. -->
|
|
45138
|
+
<section class="settings-section">
|
|
45139
|
+
@if (hasOtherSessions()) {
|
|
45140
|
+
<val-action-header [props]="sessionsHeader()" />
|
|
45141
|
+
} @else {
|
|
45142
|
+
<val-title
|
|
45143
|
+
[props]="{
|
|
45144
|
+
size: 'medium',
|
|
45145
|
+
color: 'dark',
|
|
45146
|
+
bold: true,
|
|
45147
|
+
content: sessionsTitleWithCount(),
|
|
45148
|
+
}"
|
|
45149
|
+
/>
|
|
45150
|
+
}
|
|
45151
|
+
|
|
45152
|
+
<div class="section-body">
|
|
45153
|
+
@if (sessionsLoading()) {
|
|
45154
|
+
<val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
|
|
45155
|
+
} @else if (sessions().length === 0) {
|
|
45156
|
+
<val-text
|
|
45157
|
+
[props]="{
|
|
45158
|
+
content: sessionsEmptyLabel(),
|
|
45159
|
+
size: 'small',
|
|
45160
|
+
color: 'medium',
|
|
45161
|
+
bold: false,
|
|
45162
|
+
}"
|
|
45163
|
+
/>
|
|
45164
|
+
} @else {
|
|
45165
|
+
<ul class="sessions">
|
|
45166
|
+
@for (s of visibleSessions(); track s.sessionId) {
|
|
45167
|
+
<li class="session" [class.session--current]="s.isCurrent">
|
|
45168
|
+
<ion-icon class="session__icon" [name]="deviceIcon(s)" aria-hidden="true" />
|
|
45169
|
+
<div class="session__body">
|
|
45170
|
+
<val-text
|
|
45171
|
+
[props]="{
|
|
45172
|
+
content: s.deviceInfo || s.deviceId || '—',
|
|
45173
|
+
size: 'medium',
|
|
45174
|
+
color: 'dark',
|
|
45175
|
+
bold: true,
|
|
45176
|
+
}"
|
|
45177
|
+
/>
|
|
45178
|
+
<val-text
|
|
45179
|
+
[props]="{
|
|
45180
|
+
content: formatSessionMeta(s),
|
|
45181
|
+
size: 'small',
|
|
45182
|
+
color: 'medium',
|
|
45183
|
+
bold: false,
|
|
45184
|
+
}"
|
|
45185
|
+
/>
|
|
45186
|
+
</div>
|
|
45187
|
+
<div class="session__end">
|
|
45188
|
+
@if (s.isCurrent) {
|
|
45189
|
+
<span class="session__badge">{{ sessionsThis() }}</span>
|
|
45190
|
+
} @else {
|
|
45191
|
+
<val-button [props]="revokeOneButtonProps()" (click)="onRevokeOne(s.sessionId)" />
|
|
45192
|
+
}
|
|
45193
|
+
</div>
|
|
45194
|
+
</li>
|
|
45195
|
+
}
|
|
45196
|
+
</ul>
|
|
45197
|
+
@if (hasMoreSessions()) {
|
|
45198
|
+
<div class="row-actions row-actions--center">
|
|
45199
|
+
<val-button [props]="viewAllButtonProps()" (click)="openSessionsModal()" />
|
|
45200
|
+
</div>
|
|
45201
|
+
}
|
|
45202
|
+
}
|
|
45203
|
+
</div>
|
|
45204
|
+
</section>
|
|
45205
|
+
}
|
|
45206
|
+
|
|
45207
|
+
@if (hasActionCards()) {
|
|
45208
|
+
<!-- 2. Quick actions: change password, change email, MFA -->
|
|
45209
|
+
<section class="settings-section">
|
|
45210
|
+
<h2>{{ quickActionsTitle() }}</h2>
|
|
45211
|
+
<ion-grid class="cards-grid">
|
|
45212
|
+
<ion-row>
|
|
45213
|
+
@for (action of actionCards(); track action.token) {
|
|
45214
|
+
<ion-col size="12" size-sm="6">
|
|
45215
|
+
<val-action-card [props]="action" (onClick)="onActionClick(action)" />
|
|
45216
|
+
</ion-col>
|
|
45217
|
+
}
|
|
45218
|
+
</ion-row>
|
|
45219
|
+
</ion-grid>
|
|
45220
|
+
</section>
|
|
45221
|
+
}
|
|
45222
|
+
|
|
45223
|
+
@if (resolvedConfig().showPolicies) {
|
|
45224
|
+
<!-- 3. Related policies -->
|
|
45225
|
+
<section class="settings-section">
|
|
45226
|
+
<h2>{{ policiesTitle() }}</h2>
|
|
45227
|
+
<val-page-links [props]="policyLinksProps()" />
|
|
45228
|
+
</section>
|
|
45229
|
+
}
|
|
45230
|
+
|
|
45231
|
+
<val-change-email-modal
|
|
45232
|
+
[isOpen]="isChangeEmailModalOpen()"
|
|
45233
|
+
(changed)="isChangeEmailModalOpen.set(false)"
|
|
45234
|
+
(dismissed)="isChangeEmailModalOpen.set(false)"
|
|
45235
|
+
/>
|
|
45236
|
+
|
|
45237
|
+
<val-change-password-modal
|
|
45238
|
+
[isOpen]="isChangePasswordModalOpen()"
|
|
45239
|
+
(changed)="isChangePasswordModalOpen.set(false)"
|
|
45240
|
+
(dismissed)="isChangePasswordModalOpen.set(false)"
|
|
45241
|
+
/>
|
|
45242
|
+
|
|
45243
|
+
<val-mfa-modal
|
|
45244
|
+
[isOpen]="isMfaModalOpen()"
|
|
45245
|
+
[prefillCode]="mfaPrefillCode()"
|
|
45246
|
+
(dismissed)="onMfaModalDismissed()"
|
|
45247
|
+
(enabledViaDeeplink)="onMfaEnabledViaDeeplink()"
|
|
45248
|
+
/>
|
|
45249
|
+
</div>
|
|
45250
|
+
`, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}h2{font-size:16px;font-weight:600;margin:0 0 4px}.cards-grid{margin:0 -4px;padding:0}.cards-grid ion-col{padding:4px;display:flex;flex-direction:column}.cards-grid ion-col val-action-card{flex:1;display:flex;flex-direction:column}.section-body{margin-top:16px;display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.row-actions--center{display:flex;justify-content:center;margin-top:4px}.sessions{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:10px}.session{display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:14px;background:var(--ion-color-light, rgba(0, 0, 0, .04));border:1px solid transparent;transition:background .15s ease,border-color .15s ease}.session--current{background:#2dd36f1a;border-color:#2dd36f4d}.session__icon{font-size:26px;color:var(--ion-color-dark, #1a1a1a);flex-shrink:0}.session--current .session__icon{color:var(--ion-color-success-shade, #28ba62)}.session__body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.session__end{flex-shrink:0}.session__badge{display:inline-block;padding:4px 10px;font-size:11px;font-weight:700;letter-spacing:.02em;text-transform:uppercase;color:var(--ion-color-success-shade, #28ba62);background:#2dd36f2e;border-radius:999px}\n"] }]
|
|
45251
|
+
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
45252
|
+
type: Input
|
|
45253
|
+
}] } });
|
|
45254
|
+
|
|
45255
|
+
/**
|
|
45256
|
+
* Helper para montar la vista Seguridad (`val-security-view`) como ruta en una
|
|
45257
|
+
* app del factory. El `config` se pasa por route `data` (`securityConfig`) y el
|
|
45258
|
+
* componente lo lee como fallback de su `@Input() config`.
|
|
45259
|
+
*
|
|
45260
|
+
* @example
|
|
45261
|
+
* ```ts
|
|
45262
|
+
* // settings.routes.ts
|
|
45263
|
+
* export const settingsRoutes: Routes = [
|
|
45264
|
+
* ...provideValtechSecurityRoutes(),
|
|
45265
|
+
* { path: 'preferences', loadComponent: () => ... },
|
|
45266
|
+
* ];
|
|
45267
|
+
*
|
|
45268
|
+
* // con config acotada:
|
|
45269
|
+
* ...provideValtechSecurityRoutes({ config: { showPolicies: false } }),
|
|
45270
|
+
* ...provideValtechSecurityRoutes({ config: { homeRoute: '/app/dashboard' } }),
|
|
45271
|
+
* ```
|
|
45272
|
+
*/
|
|
45273
|
+
function provideValtechSecurityRoutes(opts) {
|
|
45274
|
+
return [
|
|
45275
|
+
{
|
|
45276
|
+
path: opts?.path ?? 'security',
|
|
45277
|
+
component: SecurityViewComponent,
|
|
45278
|
+
data: { securityConfig: opts?.config },
|
|
45279
|
+
},
|
|
45280
|
+
];
|
|
45281
|
+
}
|
|
45282
|
+
|
|
45283
|
+
/** Built-in label sets for the three platform locales. */
|
|
45284
|
+
const CALLOUT_LABELS = {
|
|
45285
|
+
es: {
|
|
45286
|
+
NOTE: 'Nota',
|
|
45287
|
+
TIP: 'Tip',
|
|
45288
|
+
INFO: 'Info',
|
|
45289
|
+
IMPORTANT: 'Importante',
|
|
45290
|
+
WARNING: 'Atención',
|
|
45291
|
+
CAUTION: 'Precaución',
|
|
45292
|
+
},
|
|
45293
|
+
en: {
|
|
45294
|
+
NOTE: 'Note',
|
|
45295
|
+
TIP: 'Tip',
|
|
45296
|
+
INFO: 'Info',
|
|
45297
|
+
IMPORTANT: 'Important',
|
|
45298
|
+
WARNING: 'Warning',
|
|
45299
|
+
CAUTION: 'Caution',
|
|
45300
|
+
},
|
|
45301
|
+
pt: {
|
|
45302
|
+
NOTE: 'Nota',
|
|
45303
|
+
TIP: 'Dica',
|
|
45304
|
+
INFO: 'Info',
|
|
45305
|
+
IMPORTANT: 'Importante',
|
|
45306
|
+
WARNING: 'Atenção',
|
|
45307
|
+
CAUTION: 'Cuidado',
|
|
45308
|
+
},
|
|
45309
|
+
};
|
|
45310
|
+
/**
|
|
45311
|
+
* Per-callout-kind color (Ionic semantic color). Fixed across locales since
|
|
45312
|
+
* red = danger no matter what language you speak.
|
|
45313
|
+
*/
|
|
45314
|
+
const CALLOUT_COLORS = {
|
|
45315
|
+
NOTE: 'primary',
|
|
45316
|
+
TIP: 'success',
|
|
45317
|
+
INFO: 'tertiary',
|
|
45318
|
+
IMPORTANT: 'warning',
|
|
45319
|
+
WARNING: 'warning',
|
|
45320
|
+
CAUTION: 'danger',
|
|
45321
|
+
};
|
|
45322
|
+
const BOX_DRAWING = /[┌┐└┘├┤┬┴┼─│╔╗╚╝═║]/;
|
|
45323
|
+
const CALLOUT_RE = /^>\s*\[!(NOTE|TIP|INFO|WARNING|CAUTION|IMPORTANT)\]\s*(.*)$/i;
|
|
45324
|
+
const FENCE_RE = /^```([\w-]*)\s*$/;
|
|
45325
|
+
const HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
45326
|
+
const SEPARATOR_RE = /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
|
|
45327
|
+
const UNORDERED_RE = /^\s*[-*+]\s+(.+)$/;
|
|
45328
|
+
const CHECKLIST_RE = /^\s*[-*+]\s+\[([ xX])\]\s+(.+)$/;
|
|
45329
|
+
const ORDERED_RE = /^\s*\d+\.\s+(.+)$/;
|
|
45330
|
+
const TABLE_DIVIDER_RE = /^\s*\|?[\s:|-]+\|?\s*$/;
|
|
45331
|
+
const TABLE_ROW_RE = /^\s*\|(.+)\|\s*$/;
|
|
45332
|
+
/**
|
|
45333
|
+
* Pure Markdown → ArticleMetadata parser. No Angular deps — usable from Node scripts
|
|
45334
|
+
* (build-time generation) and from the Angular `MarkdownArticleParserService` wrapper.
|
|
45335
|
+
*
|
|
45336
|
+
* Supported syntax:
|
|
45337
|
+
* - Headings (#, ##, ### …) → title / subtitle
|
|
45338
|
+
* - Paragraphs → paragraph (with `processLinks` + `allowPartialBold`)
|
|
45339
|
+
* - Lists (-, *, 1.) → unordered / ordered list (- [ ] / - [x] for checklist)
|
|
45340
|
+
* - Blockquotes (>) → quote, with GitHub callouts `> [!NOTE|TIP|INFO|WARNING|CAUTION|IMPORTANT]` mapped to notes
|
|
45341
|
+
* - Code fences (```lang) → code
|
|
45342
|
+
* - Box-drawing ASCII art (┌┐└┘│─) auto-wrapped as code
|
|
45343
|
+
* - Tables → flattened into paragraphs with bold keys
|
|
45344
|
+
* - Horizontal rules (---, ***, ___) → separator
|
|
45345
|
+
*/
|
|
45346
|
+
function parseMarkdownArticle(markdown, options) {
|
|
45347
|
+
const lines = normalize(markdown).split('\n');
|
|
45348
|
+
const elements = [];
|
|
45349
|
+
const labels = options?.calloutLabels ??
|
|
45350
|
+
(options?.locale ? CALLOUT_LABELS[options.locale] : CALLOUT_LABELS.es);
|
|
45351
|
+
let i = 0;
|
|
45352
|
+
while (i < lines.length) {
|
|
45353
|
+
const line = lines[i];
|
|
45354
|
+
if (line.trim() === '') {
|
|
45355
|
+
i++;
|
|
45356
|
+
continue;
|
|
45357
|
+
}
|
|
45358
|
+
const fence = line.match(FENCE_RE);
|
|
45359
|
+
if (fence) {
|
|
45360
|
+
const lang = fence[1] || undefined;
|
|
45361
|
+
const body = [];
|
|
45362
|
+
i++;
|
|
45363
|
+
while (i < lines.length && !FENCE_RE.test(lines[i])) {
|
|
45364
|
+
body.push(lines[i]);
|
|
45365
|
+
i++;
|
|
45366
|
+
}
|
|
45367
|
+
i++;
|
|
45368
|
+
elements.push({
|
|
45369
|
+
type: 'code',
|
|
45370
|
+
props: { code: body.join('\n'), language: lang, theme: 'dark' },
|
|
45371
|
+
});
|
|
45372
|
+
continue;
|
|
45373
|
+
}
|
|
45374
|
+
if (BOX_DRAWING.test(line)) {
|
|
45375
|
+
const body = [];
|
|
45376
|
+
while (i < lines.length && (lines[i].trim() === '' || BOX_DRAWING.test(lines[i]))) {
|
|
45377
|
+
body.push(lines[i]);
|
|
45378
|
+
i++;
|
|
45379
|
+
}
|
|
45380
|
+
while (body.length && body[body.length - 1].trim() === '')
|
|
45381
|
+
body.pop();
|
|
45382
|
+
elements.push({
|
|
45383
|
+
type: 'code',
|
|
45384
|
+
props: { code: body.join('\n'), language: 'text', theme: 'dark' },
|
|
45385
|
+
});
|
|
45386
|
+
continue;
|
|
45387
|
+
}
|
|
45388
|
+
if (SEPARATOR_RE.test(line)) {
|
|
45389
|
+
elements.push({ type: 'separator', props: { style: 'line' } });
|
|
45390
|
+
i++;
|
|
45391
|
+
continue;
|
|
45392
|
+
}
|
|
45393
|
+
const heading = line.match(HEADING_RE);
|
|
45394
|
+
if (heading) {
|
|
45395
|
+
elements.push(makeHeading(heading[1].length, heading[2].trim()));
|
|
45396
|
+
i++;
|
|
45397
|
+
continue;
|
|
45398
|
+
}
|
|
45399
|
+
if (line.startsWith('>')) {
|
|
45400
|
+
const block = [];
|
|
45401
|
+
while (i < lines.length && lines[i].startsWith('>')) {
|
|
45402
|
+
block.push(lines[i]);
|
|
45403
|
+
i++;
|
|
45404
|
+
}
|
|
45405
|
+
elements.push(makeQuoteOrCallout(block, labels));
|
|
45406
|
+
continue;
|
|
45407
|
+
}
|
|
45408
|
+
if (TABLE_ROW_RE.test(line)) {
|
|
45409
|
+
const rows = [];
|
|
45410
|
+
while (i < lines.length && TABLE_ROW_RE.test(lines[i])) {
|
|
45411
|
+
rows.push(lines[i]);
|
|
45412
|
+
i++;
|
|
45413
|
+
}
|
|
45414
|
+
elements.push(...makeTable(rows));
|
|
45415
|
+
continue;
|
|
45416
|
+
}
|
|
45417
|
+
if (CHECKLIST_RE.test(line) || UNORDERED_RE.test(line) || ORDERED_RE.test(line)) {
|
|
45418
|
+
const listType = CHECKLIST_RE.test(line)
|
|
45419
|
+
? 'checklist'
|
|
45420
|
+
: ORDERED_RE.test(line)
|
|
45421
|
+
? 'ordered'
|
|
45422
|
+
: 'unordered';
|
|
45423
|
+
const items = [];
|
|
45424
|
+
while (i < lines.length && lines[i].trim() !== '') {
|
|
45425
|
+
const cur = lines[i];
|
|
45426
|
+
const check = cur.match(CHECKLIST_RE);
|
|
45427
|
+
const unord = cur.match(UNORDERED_RE);
|
|
45428
|
+
const ord = cur.match(ORDERED_RE);
|
|
45429
|
+
if (check)
|
|
45430
|
+
items.push({ text: processInlineBold(check[2].trim()) });
|
|
45431
|
+
else if (ord && listType === 'ordered')
|
|
45432
|
+
items.push({ text: processInlineBold(ord[1].trim()) });
|
|
45433
|
+
else if (unord && listType !== 'ordered')
|
|
45434
|
+
items.push({ text: processInlineBold(unord[1].trim()) });
|
|
45435
|
+
else
|
|
45436
|
+
break;
|
|
45437
|
+
i++;
|
|
45438
|
+
}
|
|
45439
|
+
elements.push({ type: 'list', props: { items, listType } });
|
|
45440
|
+
continue;
|
|
45441
|
+
}
|
|
45442
|
+
const paragraph = [];
|
|
45443
|
+
while (i < lines.length && lines[i].trim() !== '' && !startsNewBlock(lines[i])) {
|
|
45444
|
+
paragraph.push(lines[i]);
|
|
45445
|
+
i++;
|
|
45446
|
+
}
|
|
45447
|
+
const text = processInlineBold(paragraph.join(' ').trim());
|
|
45448
|
+
if (text) {
|
|
45449
|
+
elements.push({
|
|
45450
|
+
type: 'paragraph',
|
|
45451
|
+
props: {
|
|
45452
|
+
content: text,
|
|
45453
|
+
size: 'medium',
|
|
45454
|
+
color: 'dark',
|
|
45455
|
+
bold: false,
|
|
45456
|
+
processLinks: true,
|
|
45457
|
+
allowPartialBold: true,
|
|
45458
|
+
},
|
|
45459
|
+
});
|
|
45460
|
+
}
|
|
45461
|
+
}
|
|
45462
|
+
// Strip parser-only options before merging into ArticleMetadata
|
|
45463
|
+
const { locale: _l, calloutLabels: _c, ...metadataOverrides } = options ?? {};
|
|
45464
|
+
return {
|
|
45465
|
+
elements,
|
|
45466
|
+
maxWidth: '900px',
|
|
45467
|
+
centered: true,
|
|
45468
|
+
theme: 'auto',
|
|
45469
|
+
...metadataOverrides,
|
|
45470
|
+
};
|
|
45471
|
+
}
|
|
45472
|
+
function normalize(md) {
|
|
45473
|
+
return md.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
45474
|
+
}
|
|
45475
|
+
function startsNewBlock(line) {
|
|
45476
|
+
return (HEADING_RE.test(line) ||
|
|
45477
|
+
FENCE_RE.test(line) ||
|
|
45478
|
+
SEPARATOR_RE.test(line) ||
|
|
45479
|
+
TABLE_ROW_RE.test(line) ||
|
|
45480
|
+
UNORDERED_RE.test(line) ||
|
|
45481
|
+
ORDERED_RE.test(line) ||
|
|
45482
|
+
CHECKLIST_RE.test(line) ||
|
|
45483
|
+
line.startsWith('>') ||
|
|
45484
|
+
BOX_DRAWING.test(line));
|
|
45485
|
+
}
|
|
45486
|
+
function processInlineBold(text) {
|
|
45487
|
+
return text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
45488
|
+
}
|
|
45489
|
+
function makeHeading(level, content) {
|
|
45490
|
+
if (level === 1) {
|
|
45491
|
+
return {
|
|
45492
|
+
type: 'title',
|
|
45493
|
+
props: { content, size: 'xlarge', color: 'dark', bold: true },
|
|
45494
|
+
};
|
|
45495
|
+
}
|
|
45496
|
+
const size = level === 2 ? 'large' : level === 3 ? 'medium' : 'small';
|
|
45497
|
+
return {
|
|
45498
|
+
type: 'subtitle',
|
|
45499
|
+
props: { content, size, color: 'dark', bold: true },
|
|
45500
|
+
};
|
|
45501
|
+
}
|
|
45502
|
+
function makeQuoteOrCallout(block, labels) {
|
|
45503
|
+
const first = block[0];
|
|
45504
|
+
const callout = first.match(CALLOUT_RE);
|
|
45505
|
+
const lines = block.map(l => l.replace(/^>\s?/, ''));
|
|
45506
|
+
if (callout) {
|
|
45507
|
+
const type = callout[1].toUpperCase();
|
|
45508
|
+
const firstLineRest = callout[2] || '';
|
|
45509
|
+
const rest = lines.slice(1).join(' ').trim();
|
|
45510
|
+
const text = processInlineBold([firstLineRest, rest].filter(Boolean).join(' ').trim());
|
|
45511
|
+
return makeNote(type, text, labels);
|
|
45512
|
+
}
|
|
45513
|
+
const text = processInlineBold(lines.join(' ').trim());
|
|
45514
|
+
return {
|
|
45515
|
+
type: 'quote',
|
|
45516
|
+
props: {
|
|
45517
|
+
content: text,
|
|
45518
|
+
size: 'medium',
|
|
45519
|
+
color: 'medium',
|
|
45520
|
+
bold: false,
|
|
45521
|
+
showQuoteMark: true,
|
|
45522
|
+
alignment: 'left',
|
|
45523
|
+
},
|
|
45524
|
+
};
|
|
45525
|
+
}
|
|
45526
|
+
function makeNote(kind, text, labels) {
|
|
45527
|
+
return {
|
|
45528
|
+
type: 'note',
|
|
45529
|
+
props: {
|
|
45530
|
+
text,
|
|
45531
|
+
prefix: `${labels[kind]}:`,
|
|
45532
|
+
color: CALLOUT_COLORS[kind],
|
|
45533
|
+
textColor: 'dark',
|
|
45534
|
+
size: 'medium',
|
|
45535
|
+
rounded: true,
|
|
45536
|
+
allowPartialBold: true,
|
|
45537
|
+
processLinks: true,
|
|
45538
|
+
},
|
|
45539
|
+
};
|
|
45540
|
+
}
|
|
45541
|
+
function makeTable(rows) {
|
|
45542
|
+
const parsed = rows
|
|
45543
|
+
.filter(r => !TABLE_DIVIDER_RE.test(r))
|
|
45544
|
+
.map(r => r
|
|
45545
|
+
.trim()
|
|
45546
|
+
.replace(/^\|/, '')
|
|
45547
|
+
.replace(/\|$/, '')
|
|
45548
|
+
.split('|')
|
|
45549
|
+
.map(c => c.trim()));
|
|
45550
|
+
if (parsed.length === 0)
|
|
45551
|
+
return [];
|
|
45552
|
+
const header = parsed[0];
|
|
45553
|
+
const dataRows = parsed.slice(1);
|
|
45554
|
+
if (dataRows.length === 0) {
|
|
45555
|
+
return [
|
|
45556
|
+
{
|
|
45557
|
+
type: 'paragraph',
|
|
45558
|
+
props: {
|
|
45559
|
+
content: header.join(' · '),
|
|
45560
|
+
size: 'medium',
|
|
45561
|
+
color: 'dark',
|
|
45562
|
+
bold: false,
|
|
45563
|
+
processLinks: true,
|
|
45564
|
+
allowPartialBold: true,
|
|
45565
|
+
},
|
|
45566
|
+
},
|
|
45567
|
+
];
|
|
45568
|
+
}
|
|
45569
|
+
return dataRows.map(row => {
|
|
45570
|
+
const pairs = row.map((cell, idx) => {
|
|
45571
|
+
const key = header[idx] ?? '';
|
|
45572
|
+
return key ? `<strong>${key}:</strong> ${cell}` : cell;
|
|
45573
|
+
});
|
|
45574
|
+
return {
|
|
45575
|
+
type: 'paragraph',
|
|
45576
|
+
props: {
|
|
45577
|
+
content: pairs.join(' · '),
|
|
45578
|
+
size: 'medium',
|
|
45579
|
+
color: 'dark',
|
|
45580
|
+
bold: false,
|
|
45581
|
+
processLinks: true,
|
|
45582
|
+
allowPartialBold: true,
|
|
45583
|
+
},
|
|
45584
|
+
};
|
|
45585
|
+
});
|
|
45586
|
+
}
|
|
45587
|
+
|
|
45588
|
+
/**
|
|
45589
|
+
* `val-faq`
|
|
45590
|
+
*
|
|
45591
|
+
* Organism de preguntas frecuentes. Q&A categorizado con accordion colapsable
|
|
45592
|
+
* y buscador en vivo. Pensado para vistas FAQ públicas — el caller puede
|
|
45593
|
+
* reusar la misma data (`FaqMetadata.categories`) para emitir el structured
|
|
45594
|
+
* data `FAQPage` (JSON-LD) y ganar rich results en buscadores.
|
|
45595
|
+
*
|
|
45596
|
+
* Las respuestas (`FaqItem.answer`) son **Markdown** — se parsean con
|
|
45597
|
+
* `parseMarkdownArticle` y se renderizan vía `val-article`. Soportan links,
|
|
45598
|
+
* listas, negritas, tablas, etc.
|
|
45599
|
+
*
|
|
45600
|
+
* @example
|
|
45601
|
+
* ```html
|
|
45602
|
+
* <val-faq [props]="{ categories: faqCategories, searchable: true }" />
|
|
45603
|
+
* ```
|
|
45604
|
+
*/
|
|
45605
|
+
class FaqComponent {
|
|
45606
|
+
constructor() {
|
|
45607
|
+
this.i18n = inject(I18nService);
|
|
45608
|
+
this.props_ = signal({ categories: [] });
|
|
45609
|
+
/** Término de búsqueda actual. */
|
|
45610
|
+
this.query = signal('');
|
|
45611
|
+
/**
|
|
45612
|
+
* Cache de respuestas parseadas (Markdown → ArticleMetadata). Se reconstruye
|
|
45613
|
+
* solo cuando cambian las categorías — el parseo NO corre por keystroke.
|
|
45614
|
+
* Keyed por id de item (los ids son únicos en toda la FAQ).
|
|
45615
|
+
*/
|
|
45616
|
+
this.parsedAnswers = computed(() => {
|
|
45617
|
+
const locale = (this.i18n.lang() ?? 'es');
|
|
45618
|
+
const map = new Map();
|
|
45619
|
+
for (const cat of this.props_().categories ?? []) {
|
|
45620
|
+
for (const item of cat.items) {
|
|
45621
|
+
map.set(item.id, parseMarkdownArticle(item.answer, { locale }));
|
|
45622
|
+
}
|
|
45623
|
+
}
|
|
45624
|
+
return map;
|
|
45625
|
+
});
|
|
45626
|
+
/**
|
|
45627
|
+
* Categorías visibles tras el filtro. Una categoría desaparece si ninguna
|
|
45628
|
+
* de sus preguntas matchea. Match sobre pregunta + respuesta (raw markdown),
|
|
45629
|
+
* case-insensitive.
|
|
45630
|
+
*/
|
|
45631
|
+
this.visibleCategories = computed(() => {
|
|
45632
|
+
const q = this.query().trim().toLowerCase();
|
|
45633
|
+
const cats = this.props_().categories ?? [];
|
|
45634
|
+
if (!q)
|
|
45635
|
+
return cats;
|
|
45636
|
+
return cats
|
|
45637
|
+
.map(cat => ({
|
|
45638
|
+
...cat,
|
|
45639
|
+
items: cat.items.filter(it => it.question.toLowerCase().includes(q) || it.answer.toLowerCase().includes(q)),
|
|
45640
|
+
}))
|
|
45641
|
+
.filter(cat => cat.items.length > 0);
|
|
45642
|
+
});
|
|
45643
|
+
/** `true` si se debe mostrar el label de cada categoría. */
|
|
45644
|
+
this.showCategoryLabel = computed(() => {
|
|
45645
|
+
const p = this.props_();
|
|
45646
|
+
if (p.hideSingleCategoryLabel === false)
|
|
45647
|
+
return true;
|
|
45648
|
+
return (p.categories?.length ?? 0) > 1;
|
|
45649
|
+
});
|
|
45650
|
+
}
|
|
45651
|
+
set props(value) {
|
|
45652
|
+
if (value)
|
|
45653
|
+
this.props_.set(value);
|
|
45654
|
+
}
|
|
45655
|
+
get props() {
|
|
45656
|
+
return this.props_();
|
|
45657
|
+
}
|
|
45658
|
+
/** Respuesta parseada de un item — desde el cache. */
|
|
45659
|
+
answerOf(item) {
|
|
45660
|
+
return this.parsedAnswers().get(item.id) ?? { elements: [] };
|
|
45661
|
+
}
|
|
45662
|
+
onSearch(term) {
|
|
45663
|
+
this.query.set(term ?? '');
|
|
43741
45664
|
}
|
|
43742
45665
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FaqComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
43743
45666
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FaqComponent, isStandalone: true, selector: "val-faq", inputs: { props: "props" }, ngImport: i0, template: `
|
|
@@ -44957,489 +46880,144 @@ class AuthBackgroundComponent {
|
|
|
44957
46880
|
[attr.stroke-width]="wave.thickness"
|
|
44958
46881
|
[class.travel-left]="wave.travelDirection === 'left'"
|
|
44959
46882
|
[class.travel-right]="wave.travelDirection === 'right'"
|
|
44960
|
-
[style.opacity]="wave.opacity"
|
|
44961
|
-
[style.--pulse-duration]="wave.pulseDuration + 's'"
|
|
44962
|
-
[style.--pulse-delay]="wave.pulseDelay + 's'"
|
|
44963
|
-
[style.--travel-duration]="wave.travelDuration + 's'"
|
|
44964
|
-
[style.--travel-delay]="wave.travelDelay + 's'"
|
|
44965
|
-
/>
|
|
44966
|
-
}
|
|
44967
|
-
</svg>
|
|
44968
|
-
</div>
|
|
44969
|
-
|
|
44970
|
-
<div class="content-layer">
|
|
44971
|
-
<ng-content></ng-content>
|
|
44972
|
-
</div>
|
|
44973
|
-
</section>
|
|
44974
|
-
`, isInline: true, styles: [":host{display:block;--auth-bg-start: #ffffff;--auth-bg-mid: #f8f9fa;--auth-bg-end: #f0f0f0;--auth-glass-bg: rgba(255, 255, 255, .3);--auth-grain-opacity: .035;--auth-wave-color: var(--ion-color-primary, #6366f1)}:host-context(body.dark),:host-context(.dark),:host-context(.ion-palette-dark),:host-context(html.ion-palette-dark),:host-context([data-theme=\"dark\"]){--auth-bg-start: #0f0f1a;--auth-bg-mid: #1a1a2e;--auth-bg-end: #16213e;--auth-glass-bg: rgba(0, 0, 0, .3);--auth-grain-opacity: .05;--auth-wave-color: var(--ion-color-primary, #818cf8)}.auth-background{position:relative;display:flex;align-items:center;padding:0;background:linear-gradient(135deg,var(--auth-bg-start) 0%,var(--auth-bg-mid) 50%,var(--auth-bg-end) 100%);overflow:hidden}.grain{position:absolute;inset:0;pointer-events:none;z-index:0;opacity:var(--auth-grain-opacity);background-image:url(\"data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.5' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E\")}.glass-layer{position:absolute;inset:0;pointer-events:none;z-index:1;background:var(--auth-glass-bg);backdrop-filter:blur(3px) saturate(120%);-webkit-backdrop-filter:blur(3px) saturate(120%)}.wave-bg{position:absolute;inset:0;pointer-events:none;z-index:0;transition:filter .3s ease}:host-context(body.dark) .wave-bg,:host-context(.dark) .wave-bg,:host-context(.ion-palette-dark) .wave-bg,:host-context(html.ion-palette-dark) .wave-bg,:host-context([data-theme=\"dark\"]) .wave-bg{filter:invert(1) opacity(.6)}.waves{width:100%;height:100%;overflow:visible}.waves path{fill:none;transform-box:fill-box;transform-origin:center;will-change:transform;animation:wave-pulse var(--pulse-duration, 3s) ease-in-out var(--pulse-delay, 0s) infinite alternate,wave-travel-right var(--travel-duration, 12s) linear var(--travel-delay, 0s) infinite}.waves path.travel-left{animation:wave-pulse var(--pulse-duration, 3s) ease-in-out var(--pulse-delay, 0s) infinite alternate,wave-travel-left var(--travel-duration, 12s) linear var(--travel-delay, 0s) infinite}@keyframes wave-pulse{0%{transform:scaleY(.85)}50%{transform:scaleY(1.25)}to{transform:scaleY(.85)}}@keyframes wave-travel-right{0%{transform:translate(-300px)}to{transform:translate(300px)}}@keyframes wave-travel-left{0%{transform:translate(300px)}to{transform:translate(-300px)}}@media (prefers-reduced-motion: reduce){.waves path{animation:none}}.content-layer{position:relative;z-index:2;width:100%;height:100%;display:flex;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] }); }
|
|
44975
|
-
}
|
|
44976
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthBackgroundComponent, decorators: [{
|
|
44977
|
-
type: Component,
|
|
44978
|
-
args: [{ selector: 'val-auth-background', standalone: true, imports: [CommonModule], template: `
|
|
44979
|
-
<section class="auth-background" [style.min-height]="config().minHeight">
|
|
44980
|
-
@if (config().showGrain) {
|
|
44981
|
-
<div class="grain"></div>
|
|
44982
|
-
}
|
|
44983
|
-
@if (config().showGlass) {
|
|
44984
|
-
<div class="glass-layer"></div>
|
|
44985
|
-
}
|
|
44986
|
-
<div class="wave-bg">
|
|
44987
|
-
<svg class="waves" viewBox="0 0 1440 800" preserveAspectRatio="none">
|
|
44988
|
-
@for (wave of waves(); track wave.id) {
|
|
44989
|
-
<path
|
|
44990
|
-
[attr.d]="wave.path"
|
|
44991
|
-
[attr.stroke]="wave.color"
|
|
44992
|
-
[attr.stroke-width]="wave.thickness"
|
|
44993
|
-
[class.travel-left]="wave.travelDirection === 'left'"
|
|
44994
|
-
[class.travel-right]="wave.travelDirection === 'right'"
|
|
44995
|
-
[style.opacity]="wave.opacity"
|
|
44996
|
-
[style.--pulse-duration]="wave.pulseDuration + 's'"
|
|
44997
|
-
[style.--pulse-delay]="wave.pulseDelay + 's'"
|
|
44998
|
-
[style.--travel-duration]="wave.travelDuration + 's'"
|
|
44999
|
-
[style.--travel-delay]="wave.travelDelay + 's'"
|
|
45000
|
-
/>
|
|
45001
|
-
}
|
|
45002
|
-
</svg>
|
|
45003
|
-
</div>
|
|
45004
|
-
|
|
45005
|
-
<div class="content-layer">
|
|
45006
|
-
<ng-content></ng-content>
|
|
45007
|
-
</div>
|
|
45008
|
-
</section>
|
|
45009
|
-
`, styles: [":host{display:block;--auth-bg-start: #ffffff;--auth-bg-mid: #f8f9fa;--auth-bg-end: #f0f0f0;--auth-glass-bg: rgba(255, 255, 255, .3);--auth-grain-opacity: .035;--auth-wave-color: var(--ion-color-primary, #6366f1)}:host-context(body.dark),:host-context(.dark),:host-context(.ion-palette-dark),:host-context(html.ion-palette-dark),:host-context([data-theme=\"dark\"]){--auth-bg-start: #0f0f1a;--auth-bg-mid: #1a1a2e;--auth-bg-end: #16213e;--auth-glass-bg: rgba(0, 0, 0, .3);--auth-grain-opacity: .05;--auth-wave-color: var(--ion-color-primary, #818cf8)}.auth-background{position:relative;display:flex;align-items:center;padding:0;background:linear-gradient(135deg,var(--auth-bg-start) 0%,var(--auth-bg-mid) 50%,var(--auth-bg-end) 100%);overflow:hidden}.grain{position:absolute;inset:0;pointer-events:none;z-index:0;opacity:var(--auth-grain-opacity);background-image:url(\"data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.5' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E\")}.glass-layer{position:absolute;inset:0;pointer-events:none;z-index:1;background:var(--auth-glass-bg);backdrop-filter:blur(3px) saturate(120%);-webkit-backdrop-filter:blur(3px) saturate(120%)}.wave-bg{position:absolute;inset:0;pointer-events:none;z-index:0;transition:filter .3s ease}:host-context(body.dark) .wave-bg,:host-context(.dark) .wave-bg,:host-context(.ion-palette-dark) .wave-bg,:host-context(html.ion-palette-dark) .wave-bg,:host-context([data-theme=\"dark\"]) .wave-bg{filter:invert(1) opacity(.6)}.waves{width:100%;height:100%;overflow:visible}.waves path{fill:none;transform-box:fill-box;transform-origin:center;will-change:transform;animation:wave-pulse var(--pulse-duration, 3s) ease-in-out var(--pulse-delay, 0s) infinite alternate,wave-travel-right var(--travel-duration, 12s) linear var(--travel-delay, 0s) infinite}.waves path.travel-left{animation:wave-pulse var(--pulse-duration, 3s) ease-in-out var(--pulse-delay, 0s) infinite alternate,wave-travel-left var(--travel-duration, 12s) linear var(--travel-delay, 0s) infinite}@keyframes wave-pulse{0%{transform:scaleY(.85)}50%{transform:scaleY(1.25)}to{transform:scaleY(.85)}}@keyframes wave-travel-right{0%{transform:translate(-300px)}to{transform:translate(300px)}}@keyframes wave-travel-left{0%{transform:translate(300px)}to{transform:translate(-300px)}}@media (prefers-reduced-motion: reduce){.waves path{animation:none}}.content-layer{position:relative;z-index:2;width:100%;height:100%;display:flex;align-items:center}\n"] }]
|
|
45010
|
-
}], ctorParameters: () => [], propDecorators: { props: [{
|
|
45011
|
-
type: Input
|
|
45012
|
-
}] } });
|
|
45013
|
-
|
|
45014
|
-
/**
|
|
45015
|
-
* Clave para persistir el idioma en localStorage
|
|
45016
|
-
*/
|
|
45017
|
-
const LANG_STORAGE_KEY = 'app_lang';
|
|
45018
|
-
/**
|
|
45019
|
-
* Idioma por defecto
|
|
45020
|
-
*/
|
|
45021
|
-
const DEFAULT_LANG = 'es';
|
|
45022
|
-
/**
|
|
45023
|
-
* Servicio mínimo de localización.
|
|
45024
|
-
*
|
|
45025
|
-
* - Lee el idioma desde localStorage al iniciar
|
|
45026
|
-
* - Proporciona método para cambiar idioma (recarga la página)
|
|
45027
|
-
* - Sin observables ni subscripciones
|
|
45028
|
-
*
|
|
45029
|
-
* @example
|
|
45030
|
-
* // En un componente
|
|
45031
|
-
* locale = inject(LocaleService);
|
|
45032
|
-
*
|
|
45033
|
-
* // Obtener idioma actual
|
|
45034
|
-
* console.log(this.locale.lang); // 'es' o 'en'
|
|
45035
|
-
*
|
|
45036
|
-
* // Cambiar idioma (recarga la página)
|
|
45037
|
-
* this.locale.setLang('en');
|
|
45038
|
-
*/
|
|
45039
|
-
class LocaleService {
|
|
45040
|
-
constructor(platformId) {
|
|
45041
|
-
this.isBrowser = isPlatformBrowser(platformId);
|
|
45042
|
-
this.lang = this.getStoredLang();
|
|
45043
|
-
}
|
|
45044
|
-
/**
|
|
45045
|
-
* Cambia el idioma de la aplicación.
|
|
45046
|
-
* Guarda en localStorage y recarga la página para aplicar el cambio.
|
|
45047
|
-
*
|
|
45048
|
-
* @param lang Nuevo idioma ('es' o 'en')
|
|
45049
|
-
*/
|
|
45050
|
-
setLang(lang) {
|
|
45051
|
-
if (lang === this.lang || !this.isBrowser) {
|
|
45052
|
-
return;
|
|
45053
|
-
}
|
|
45054
|
-
localStorage.setItem(LANG_STORAGE_KEY, lang);
|
|
45055
|
-
window.location.reload();
|
|
45056
|
-
}
|
|
45057
|
-
/**
|
|
45058
|
-
* Obtiene el idioma almacenado en localStorage.
|
|
45059
|
-
* Si no existe o no es válido, retorna el idioma por defecto.
|
|
45060
|
-
* En SSR (sin localStorage) retorna el default.
|
|
45061
|
-
*/
|
|
45062
|
-
getStoredLang() {
|
|
45063
|
-
if (!this.isBrowser) {
|
|
45064
|
-
return DEFAULT_LANG;
|
|
45065
|
-
}
|
|
45066
|
-
const stored = localStorage.getItem(LANG_STORAGE_KEY);
|
|
45067
|
-
if (stored === 'es' || stored === 'en') {
|
|
45068
|
-
return stored;
|
|
45069
|
-
}
|
|
45070
|
-
return DEFAULT_LANG;
|
|
45071
|
-
}
|
|
45072
|
-
/**
|
|
45073
|
-
* Verifica si el idioma actual es español
|
|
45074
|
-
*/
|
|
45075
|
-
get isSpanish() {
|
|
45076
|
-
return this.lang === 'es';
|
|
45077
|
-
}
|
|
45078
|
-
/**
|
|
45079
|
-
* Verifica si el idioma actual es inglés
|
|
45080
|
-
*/
|
|
45081
|
-
get isEnglish() {
|
|
45082
|
-
return this.lang === 'en';
|
|
45083
|
-
}
|
|
45084
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocaleService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
45085
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocaleService, providedIn: 'root' }); }
|
|
45086
|
-
}
|
|
45087
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocaleService, decorators: [{
|
|
45088
|
-
type: Injectable,
|
|
45089
|
-
args: [{ providedIn: 'root' }]
|
|
45090
|
-
}], ctorParameters: () => [{ type: Object, decorators: [{
|
|
45091
|
-
type: Inject,
|
|
45092
|
-
args: [PLATFORM_ID]
|
|
45093
|
-
}] }] });
|
|
45094
|
-
|
|
45095
|
-
// Types for valtech-components services
|
|
45096
|
-
// ValtechConfig and LangProvider have been removed in v3.0.0
|
|
45097
|
-
// Use LocaleService for language management instead
|
|
45098
|
-
|
|
45099
|
-
/**
|
|
45100
|
-
* Default modal sizes.
|
|
45101
|
-
*/
|
|
45102
|
-
const MODAL_SIZES = {
|
|
45103
|
-
small: { width: '320px', height: '220px' },
|
|
45104
|
-
medium: { width: '500px', height: '400px' },
|
|
45105
|
-
large: { width: '800px', height: '80vh' },
|
|
45106
|
-
full: { width: '100%', height: '100%' },
|
|
45107
|
-
auto: { width: 'auto', height: '50vh' },
|
|
45108
|
-
};
|
|
45109
|
-
/**
|
|
45110
|
-
* Default modal buttons.
|
|
45111
|
-
*/
|
|
45112
|
-
const DEFAULT_MODAL_CONFIRM_BUTTON = {
|
|
45113
|
-
text: 'Confirm',
|
|
45114
|
-
role: 'confirm',
|
|
45115
|
-
color: 'primary',
|
|
45116
|
-
fill: 'solid',
|
|
45117
|
-
};
|
|
45118
|
-
const DEFAULT_MODAL_CANCEL_BUTTON = {
|
|
45119
|
-
text: 'Cancel',
|
|
45120
|
-
role: 'cancel',
|
|
45121
|
-
color: 'medium',
|
|
45122
|
-
fill: 'clear',
|
|
45123
|
-
};
|
|
45124
|
-
|
|
45125
|
-
/**
|
|
45126
|
-
* Internal component for simple content modals.
|
|
45127
|
-
* Used by ModalService.openSimple()
|
|
45128
|
-
*
|
|
45129
|
-
* Header + título + close button delegados a `val-modal-shell` (estándar
|
|
45130
|
-
* canónico — ver Regla #5 en `frontend/CLAUDE.md`). El cuerpo (innerHTML) +
|
|
45131
|
-
* footer con botones quedan locales.
|
|
45132
|
-
*/
|
|
45133
|
-
class SimpleModalContentComponent {
|
|
45134
|
-
constructor() {
|
|
45135
|
-
this.title = '';
|
|
45136
|
-
this.content = '';
|
|
45137
|
-
this.buttons = [];
|
|
45138
|
-
this.showCloseButton = true;
|
|
45139
|
-
}
|
|
45140
|
-
async onButtonClick(button) {
|
|
45141
|
-
if (button.handler) {
|
|
45142
|
-
const result = await button.handler();
|
|
45143
|
-
// If handler returns false, don't dismiss
|
|
45144
|
-
if (result === false) {
|
|
45145
|
-
return;
|
|
45146
|
-
}
|
|
45147
|
-
}
|
|
45148
|
-
this._modalRef?.dismiss(undefined, button.role || 'confirm');
|
|
45149
|
-
}
|
|
45150
|
-
onDismiss(role) {
|
|
45151
|
-
this._modalRef?.dismiss(undefined, role);
|
|
45152
|
-
}
|
|
45153
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SimpleModalContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
45154
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SimpleModalContentComponent, isStandalone: true, selector: "val-simple-modal-content", inputs: { title: "title", content: "content", buttons: "buttons", showCloseButton: "showCloseButton", _modalRef: "_modalRef" }, ngImport: i0, template: `
|
|
45155
|
-
<val-modal-shell [title]="title" [showClose]="showCloseButton" (close)="onDismiss('close')">
|
|
45156
|
-
<div [innerHTML]="content"></div>
|
|
45157
|
-
</val-modal-shell>
|
|
45158
|
-
|
|
45159
|
-
@if (buttons && buttons.length > 0) {
|
|
45160
|
-
<ion-footer>
|
|
45161
|
-
<ion-toolbar>
|
|
45162
|
-
<div class="button-container">
|
|
45163
|
-
@for (button of buttons; track button.text) {
|
|
45164
|
-
<ion-button
|
|
45165
|
-
[color]="button.color || 'medium'"
|
|
45166
|
-
[fill]="button.fill || 'solid'"
|
|
45167
|
-
[size]="button.size || 'default'"
|
|
45168
|
-
[disabled]="button.disabled || button.loading"
|
|
45169
|
-
[class]="button.cssClass"
|
|
45170
|
-
(click)="onButtonClick(button)"
|
|
45171
|
-
>
|
|
45172
|
-
@if (button.loading) {
|
|
45173
|
-
<ion-spinner name="circular"></ion-spinner>
|
|
45174
|
-
} @else {
|
|
45175
|
-
{{ button.text }}
|
|
45176
|
-
}
|
|
45177
|
-
</ion-button>
|
|
45178
|
-
}
|
|
45179
|
-
</div>
|
|
45180
|
-
</ion-toolbar>
|
|
45181
|
-
</ion-footer>
|
|
45182
|
-
}
|
|
45183
|
-
`, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}ion-footer{flex-shrink:0}.button-container{display:flex;justify-content:flex-end;gap:8px;padding:8px 0}ion-footer ion-toolbar{--padding-start: 16px;--padding-end: 16px}:host ::ng-deep ul,:host ::ng-deep ol{padding-left:20px;margin:8px 0}:host ::ng-deep li{margin:4px 0}:host ::ng-deep p{margin:8px 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { 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: IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: ModalShellComponent, selector: "val-modal-shell", inputs: ["title", "subtitle", "closeLabel", "showClose"], outputs: ["close"] }] }); }
|
|
46883
|
+
[style.opacity]="wave.opacity"
|
|
46884
|
+
[style.--pulse-duration]="wave.pulseDuration + 's'"
|
|
46885
|
+
[style.--pulse-delay]="wave.pulseDelay + 's'"
|
|
46886
|
+
[style.--travel-duration]="wave.travelDuration + 's'"
|
|
46887
|
+
[style.--travel-delay]="wave.travelDelay + 's'"
|
|
46888
|
+
/>
|
|
46889
|
+
}
|
|
46890
|
+
</svg>
|
|
46891
|
+
</div>
|
|
46892
|
+
|
|
46893
|
+
<div class="content-layer">
|
|
46894
|
+
<ng-content></ng-content>
|
|
46895
|
+
</div>
|
|
46896
|
+
</section>
|
|
46897
|
+
`, isInline: true, styles: [":host{display:block;--auth-bg-start: #ffffff;--auth-bg-mid: #f8f9fa;--auth-bg-end: #f0f0f0;--auth-glass-bg: rgba(255, 255, 255, .3);--auth-grain-opacity: .035;--auth-wave-color: var(--ion-color-primary, #6366f1)}:host-context(body.dark),:host-context(.dark),:host-context(.ion-palette-dark),:host-context(html.ion-palette-dark),:host-context([data-theme=\"dark\"]){--auth-bg-start: #0f0f1a;--auth-bg-mid: #1a1a2e;--auth-bg-end: #16213e;--auth-glass-bg: rgba(0, 0, 0, .3);--auth-grain-opacity: .05;--auth-wave-color: var(--ion-color-primary, #818cf8)}.auth-background{position:relative;display:flex;align-items:center;padding:0;background:linear-gradient(135deg,var(--auth-bg-start) 0%,var(--auth-bg-mid) 50%,var(--auth-bg-end) 100%);overflow:hidden}.grain{position:absolute;inset:0;pointer-events:none;z-index:0;opacity:var(--auth-grain-opacity);background-image:url(\"data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.5' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E\")}.glass-layer{position:absolute;inset:0;pointer-events:none;z-index:1;background:var(--auth-glass-bg);backdrop-filter:blur(3px) saturate(120%);-webkit-backdrop-filter:blur(3px) saturate(120%)}.wave-bg{position:absolute;inset:0;pointer-events:none;z-index:0;transition:filter .3s ease}:host-context(body.dark) .wave-bg,:host-context(.dark) .wave-bg,:host-context(.ion-palette-dark) .wave-bg,:host-context(html.ion-palette-dark) .wave-bg,:host-context([data-theme=\"dark\"]) .wave-bg{filter:invert(1) opacity(.6)}.waves{width:100%;height:100%;overflow:visible}.waves path{fill:none;transform-box:fill-box;transform-origin:center;will-change:transform;animation:wave-pulse var(--pulse-duration, 3s) ease-in-out var(--pulse-delay, 0s) infinite alternate,wave-travel-right var(--travel-duration, 12s) linear var(--travel-delay, 0s) infinite}.waves path.travel-left{animation:wave-pulse var(--pulse-duration, 3s) ease-in-out var(--pulse-delay, 0s) infinite alternate,wave-travel-left var(--travel-duration, 12s) linear var(--travel-delay, 0s) infinite}@keyframes wave-pulse{0%{transform:scaleY(.85)}50%{transform:scaleY(1.25)}to{transform:scaleY(.85)}}@keyframes wave-travel-right{0%{transform:translate(-300px)}to{transform:translate(300px)}}@keyframes wave-travel-left{0%{transform:translate(300px)}to{transform:translate(-300px)}}@media (prefers-reduced-motion: reduce){.waves path{animation:none}}.content-layer{position:relative;z-index:2;width:100%;height:100%;display:flex;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] }); }
|
|
45184
46898
|
}
|
|
45185
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type:
|
|
46899
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthBackgroundComponent, decorators: [{
|
|
45186
46900
|
type: Component,
|
|
45187
|
-
args: [{ selector: 'val-
|
|
45188
|
-
<
|
|
45189
|
-
|
|
45190
|
-
|
|
46901
|
+
args: [{ selector: 'val-auth-background', standalone: true, imports: [CommonModule], template: `
|
|
46902
|
+
<section class="auth-background" [style.min-height]="config().minHeight">
|
|
46903
|
+
@if (config().showGrain) {
|
|
46904
|
+
<div class="grain"></div>
|
|
46905
|
+
}
|
|
46906
|
+
@if (config().showGlass) {
|
|
46907
|
+
<div class="glass-layer"></div>
|
|
46908
|
+
}
|
|
46909
|
+
<div class="wave-bg">
|
|
46910
|
+
<svg class="waves" viewBox="0 0 1440 800" preserveAspectRatio="none">
|
|
46911
|
+
@for (wave of waves(); track wave.id) {
|
|
46912
|
+
<path
|
|
46913
|
+
[attr.d]="wave.path"
|
|
46914
|
+
[attr.stroke]="wave.color"
|
|
46915
|
+
[attr.stroke-width]="wave.thickness"
|
|
46916
|
+
[class.travel-left]="wave.travelDirection === 'left'"
|
|
46917
|
+
[class.travel-right]="wave.travelDirection === 'right'"
|
|
46918
|
+
[style.opacity]="wave.opacity"
|
|
46919
|
+
[style.--pulse-duration]="wave.pulseDuration + 's'"
|
|
46920
|
+
[style.--pulse-delay]="wave.pulseDelay + 's'"
|
|
46921
|
+
[style.--travel-duration]="wave.travelDuration + 's'"
|
|
46922
|
+
[style.--travel-delay]="wave.travelDelay + 's'"
|
|
46923
|
+
/>
|
|
46924
|
+
}
|
|
46925
|
+
</svg>
|
|
46926
|
+
</div>
|
|
45191
46927
|
|
|
45192
|
-
|
|
45193
|
-
|
|
45194
|
-
|
|
45195
|
-
|
|
45196
|
-
|
|
45197
|
-
|
|
45198
|
-
[color]="button.color || 'medium'"
|
|
45199
|
-
[fill]="button.fill || 'solid'"
|
|
45200
|
-
[size]="button.size || 'default'"
|
|
45201
|
-
[disabled]="button.disabled || button.loading"
|
|
45202
|
-
[class]="button.cssClass"
|
|
45203
|
-
(click)="onButtonClick(button)"
|
|
45204
|
-
>
|
|
45205
|
-
@if (button.loading) {
|
|
45206
|
-
<ion-spinner name="circular"></ion-spinner>
|
|
45207
|
-
} @else {
|
|
45208
|
-
{{ button.text }}
|
|
45209
|
-
}
|
|
45210
|
-
</ion-button>
|
|
45211
|
-
}
|
|
45212
|
-
</div>
|
|
45213
|
-
</ion-toolbar>
|
|
45214
|
-
</ion-footer>
|
|
45215
|
-
}
|
|
45216
|
-
`, styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}ion-footer{flex-shrink:0}.button-container{display:flex;justify-content:flex-end;gap:8px;padding:8px 0}ion-footer ion-toolbar{--padding-start: 16px;--padding-end: 16px}:host ::ng-deep ul,:host ::ng-deep ol{padding-left:20px;margin:8px 0}:host ::ng-deep li{margin:4px 0}:host ::ng-deep p{margin:8px 0}\n"] }]
|
|
45217
|
-
}], propDecorators: { title: [{
|
|
45218
|
-
type: Input
|
|
45219
|
-
}], content: [{
|
|
45220
|
-
type: Input
|
|
45221
|
-
}], buttons: [{
|
|
45222
|
-
type: Input
|
|
45223
|
-
}], showCloseButton: [{
|
|
45224
|
-
type: Input
|
|
45225
|
-
}], _modalRef: [{
|
|
46928
|
+
<div class="content-layer">
|
|
46929
|
+
<ng-content></ng-content>
|
|
46930
|
+
</div>
|
|
46931
|
+
</section>
|
|
46932
|
+
`, styles: [":host{display:block;--auth-bg-start: #ffffff;--auth-bg-mid: #f8f9fa;--auth-bg-end: #f0f0f0;--auth-glass-bg: rgba(255, 255, 255, .3);--auth-grain-opacity: .035;--auth-wave-color: var(--ion-color-primary, #6366f1)}:host-context(body.dark),:host-context(.dark),:host-context(.ion-palette-dark),:host-context(html.ion-palette-dark),:host-context([data-theme=\"dark\"]){--auth-bg-start: #0f0f1a;--auth-bg-mid: #1a1a2e;--auth-bg-end: #16213e;--auth-glass-bg: rgba(0, 0, 0, .3);--auth-grain-opacity: .05;--auth-wave-color: var(--ion-color-primary, #818cf8)}.auth-background{position:relative;display:flex;align-items:center;padding:0;background:linear-gradient(135deg,var(--auth-bg-start) 0%,var(--auth-bg-mid) 50%,var(--auth-bg-end) 100%);overflow:hidden}.grain{position:absolute;inset:0;pointer-events:none;z-index:0;opacity:var(--auth-grain-opacity);background-image:url(\"data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.5' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E\")}.glass-layer{position:absolute;inset:0;pointer-events:none;z-index:1;background:var(--auth-glass-bg);backdrop-filter:blur(3px) saturate(120%);-webkit-backdrop-filter:blur(3px) saturate(120%)}.wave-bg{position:absolute;inset:0;pointer-events:none;z-index:0;transition:filter .3s ease}:host-context(body.dark) .wave-bg,:host-context(.dark) .wave-bg,:host-context(.ion-palette-dark) .wave-bg,:host-context(html.ion-palette-dark) .wave-bg,:host-context([data-theme=\"dark\"]) .wave-bg{filter:invert(1) opacity(.6)}.waves{width:100%;height:100%;overflow:visible}.waves path{fill:none;transform-box:fill-box;transform-origin:center;will-change:transform;animation:wave-pulse var(--pulse-duration, 3s) ease-in-out var(--pulse-delay, 0s) infinite alternate,wave-travel-right var(--travel-duration, 12s) linear var(--travel-delay, 0s) infinite}.waves path.travel-left{animation:wave-pulse var(--pulse-duration, 3s) ease-in-out var(--pulse-delay, 0s) infinite alternate,wave-travel-left var(--travel-duration, 12s) linear var(--travel-delay, 0s) infinite}@keyframes wave-pulse{0%{transform:scaleY(.85)}50%{transform:scaleY(1.25)}to{transform:scaleY(.85)}}@keyframes wave-travel-right{0%{transform:translate(-300px)}to{transform:translate(300px)}}@keyframes wave-travel-left{0%{transform:translate(300px)}to{transform:translate(-300px)}}@media (prefers-reduced-motion: reduce){.waves path{animation:none}}.content-layer{position:relative;z-index:2;width:100%;height:100%;display:flex;align-items:center}\n"] }]
|
|
46933
|
+
}], ctorParameters: () => [], propDecorators: { props: [{
|
|
45226
46934
|
type: Input
|
|
45227
46935
|
}] } });
|
|
45228
46936
|
|
|
45229
46937
|
/**
|
|
45230
|
-
*
|
|
45231
|
-
|
|
45232
|
-
|
|
46938
|
+
* Clave para persistir el idioma en localStorage
|
|
46939
|
+
*/
|
|
46940
|
+
const LANG_STORAGE_KEY = 'app_lang';
|
|
46941
|
+
/**
|
|
46942
|
+
* Idioma por defecto
|
|
46943
|
+
*/
|
|
46944
|
+
const DEFAULT_LANG = 'es';
|
|
46945
|
+
/**
|
|
46946
|
+
* Servicio mínimo de localización.
|
|
45233
46947
|
*
|
|
45234
|
-
*
|
|
45235
|
-
*
|
|
45236
|
-
*
|
|
45237
|
-
* component: MyFormComponent,
|
|
45238
|
-
* componentProps: { userId: 123 },
|
|
45239
|
-
* header: { title: 'Edit User', showCloseButton: true },
|
|
45240
|
-
* size: 'medium'
|
|
45241
|
-
* });
|
|
46948
|
+
* - Lee el idioma desde localStorage al iniciar
|
|
46949
|
+
* - Proporciona método para cambiar idioma (recarga la página)
|
|
46950
|
+
* - Sin observables ni subscripciones
|
|
45242
46951
|
*
|
|
45243
|
-
*
|
|
45244
|
-
*
|
|
45245
|
-
*
|
|
45246
|
-
* ```
|
|
46952
|
+
* @example
|
|
46953
|
+
* // En un componente
|
|
46954
|
+
* locale = inject(LocaleService);
|
|
45247
46955
|
*
|
|
45248
|
-
*
|
|
45249
|
-
*
|
|
45250
|
-
* await modalService.openSimple({
|
|
45251
|
-
* title: 'Terms of Service',
|
|
45252
|
-
* content: '<p>Terms content here...</p>',
|
|
45253
|
-
* size: 'large',
|
|
45254
|
-
* showCloseButton: true
|
|
45255
|
-
* });
|
|
45256
|
-
* ```
|
|
46956
|
+
* // Obtener idioma actual
|
|
46957
|
+
* console.log(this.locale.lang); // 'es' o 'en'
|
|
45257
46958
|
*
|
|
45258
|
-
*
|
|
45259
|
-
*
|
|
45260
|
-
* const result = await modalService.openSheet({
|
|
45261
|
-
* component: FilterComponent,
|
|
45262
|
-
* breakpoints: {
|
|
45263
|
-
* initialBreakpoint: 0.5,
|
|
45264
|
-
* breakpoints: [0, 0.5, 1],
|
|
45265
|
-
* showHandle: true
|
|
45266
|
-
* }
|
|
45267
|
-
* });
|
|
45268
|
-
* ```
|
|
46959
|
+
* // Cambiar idioma (recarga la página)
|
|
46960
|
+
* this.locale.setLang('en');
|
|
45269
46961
|
*/
|
|
45270
|
-
class
|
|
45271
|
-
constructor(
|
|
45272
|
-
this.
|
|
46962
|
+
class LocaleService {
|
|
46963
|
+
constructor(platformId) {
|
|
46964
|
+
this.isBrowser = isPlatformBrowser(platformId);
|
|
46965
|
+
this.lang = this.getStoredLang();
|
|
45273
46966
|
}
|
|
45274
46967
|
/**
|
|
45275
|
-
*
|
|
45276
|
-
*
|
|
45277
|
-
*
|
|
46968
|
+
* Cambia el idioma de la aplicación.
|
|
46969
|
+
* Guarda en localStorage y recarga la página para aplicar el cambio.
|
|
46970
|
+
*
|
|
46971
|
+
* @param lang Nuevo idioma ('es' o 'en')
|
|
45278
46972
|
*/
|
|
45279
|
-
|
|
45280
|
-
|
|
45281
|
-
|
|
45282
|
-
// `--width`/`--height` here collapses the sheet (e.g. `auto` → shrink to
|
|
45283
|
-
// content) or fights the breakpoint height. So when it's a sheet we skip
|
|
45284
|
-
// the size preset entirely.
|
|
45285
|
-
const isSheet = !!options.breakpoints?.breakpoints?.length;
|
|
45286
|
-
const size = isSheet ? undefined : MODAL_SIZES[options.size || 'medium'];
|
|
45287
|
-
const cssClasses = this.buildCssClasses(options);
|
|
45288
|
-
const modal = await this.modalController.create({
|
|
45289
|
-
component: options.component,
|
|
45290
|
-
componentProps: {
|
|
45291
|
-
...options.componentProps,
|
|
45292
|
-
// Pass modal reference for dismiss from within component
|
|
45293
|
-
_modalRef: {
|
|
45294
|
-
dismiss: (data, role) => modal.dismiss(data, role),
|
|
45295
|
-
},
|
|
45296
|
-
},
|
|
45297
|
-
id: options.id,
|
|
45298
|
-
showBackdrop: options.showBackdrop ?? true,
|
|
45299
|
-
backdropDismiss: options.backdropDismiss ?? true,
|
|
45300
|
-
keyboardClose: options.keyboardClose ?? true,
|
|
45301
|
-
cssClass: cssClasses,
|
|
45302
|
-
mode: options.mode,
|
|
45303
|
-
animated: options.animated ?? true,
|
|
45304
|
-
canDismiss: this.normalizeCanDismiss(options.canDismiss ?? true),
|
|
45305
|
-
presentingElement: options.presentingElement,
|
|
45306
|
-
// Sheet modal options
|
|
45307
|
-
initialBreakpoint: options.breakpoints?.initialBreakpoint,
|
|
45308
|
-
breakpoints: options.breakpoints?.breakpoints,
|
|
45309
|
-
handle: options.breakpoints?.showHandle,
|
|
45310
|
-
handleBehavior: options.breakpoints?.handleBehavior,
|
|
45311
|
-
});
|
|
45312
|
-
// Apply custom size styles. Skipped for sheet modals — their dimensions
|
|
45313
|
-
// come from the breakpoints (Ionic spans the sheet full-width and uses the
|
|
45314
|
-
// breakpoint values for height).
|
|
45315
|
-
if (!isSheet && (options.width || options.height || size)) {
|
|
45316
|
-
const modalEl = modal;
|
|
45317
|
-
modalEl.style.setProperty('--width', options.width || size?.width || 'auto');
|
|
45318
|
-
modalEl.style.setProperty('--height', options.height || size?.height || '50vh');
|
|
46973
|
+
setLang(lang) {
|
|
46974
|
+
if (lang === this.lang || !this.isBrowser) {
|
|
46975
|
+
return;
|
|
45319
46976
|
}
|
|
45320
|
-
|
|
45321
|
-
|
|
45322
|
-
return { data, role };
|
|
45323
|
-
}
|
|
45324
|
-
/**
|
|
45325
|
-
* Opens a simple content modal without a custom component.
|
|
45326
|
-
* Uses Ionic's alert-like styling but with modal capabilities.
|
|
45327
|
-
* @param options - Simple modal configuration
|
|
45328
|
-
* @returns Promise resolving to the modal result
|
|
45329
|
-
*/
|
|
45330
|
-
async openSimple(options) {
|
|
45331
|
-
return this.open({
|
|
45332
|
-
component: SimpleModalContentComponent,
|
|
45333
|
-
componentProps: {
|
|
45334
|
-
title: options.title,
|
|
45335
|
-
content: options.content,
|
|
45336
|
-
buttons: options.buttons || [DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON],
|
|
45337
|
-
showCloseButton: options.showCloseButton ?? true,
|
|
45338
|
-
},
|
|
45339
|
-
size: options.size || 'medium',
|
|
45340
|
-
backdropDismiss: options.backdropDismiss ?? true,
|
|
45341
|
-
cssClass: ['simple-modal', options.cssClass].filter(Boolean).join(' '),
|
|
45342
|
-
});
|
|
45343
|
-
}
|
|
45344
|
-
/**
|
|
45345
|
-
* Opens a confirmation modal.
|
|
45346
|
-
* @param title - Modal title
|
|
45347
|
-
* @param message - Modal message
|
|
45348
|
-
* @param confirmButton - Confirm button config (optional)
|
|
45349
|
-
* @param cancelButton - Cancel button config (optional)
|
|
45350
|
-
* @returns Promise resolving to true if confirmed
|
|
45351
|
-
*/
|
|
45352
|
-
async confirm(title, message, confirmButton, cancelButton) {
|
|
45353
|
-
const result = await this.openSimple({
|
|
45354
|
-
title,
|
|
45355
|
-
content: message,
|
|
45356
|
-
size: 'small',
|
|
45357
|
-
showCloseButton: false,
|
|
45358
|
-
backdropDismiss: false,
|
|
45359
|
-
buttons: [
|
|
45360
|
-
{ ...DEFAULT_MODAL_CANCEL_BUTTON, ...cancelButton },
|
|
45361
|
-
{ ...DEFAULT_MODAL_CONFIRM_BUTTON, ...confirmButton },
|
|
45362
|
-
],
|
|
45363
|
-
});
|
|
45364
|
-
return result.role === 'confirm';
|
|
45365
|
-
}
|
|
45366
|
-
/**
|
|
45367
|
-
* Opens a sheet modal (iOS-style bottom sheet).
|
|
45368
|
-
* @param options - Modal configuration with sheet-specific options
|
|
45369
|
-
* @returns Promise resolving to the modal result
|
|
45370
|
-
*/
|
|
45371
|
-
async openSheet(options) {
|
|
45372
|
-
return this.open({
|
|
45373
|
-
...options,
|
|
45374
|
-
breakpoints: {
|
|
45375
|
-
initialBreakpoint: options.initialBreakpoint ?? 0.5,
|
|
45376
|
-
breakpoints: options.breakpoints ?? [0, 0.5, 1],
|
|
45377
|
-
showHandle: options.showHandle ?? true,
|
|
45378
|
-
},
|
|
45379
|
-
cssClass: ['sheet-modal', options.cssClass].filter(Boolean).join(' '),
|
|
45380
|
-
});
|
|
46977
|
+
localStorage.setItem(LANG_STORAGE_KEY, lang);
|
|
46978
|
+
window.location.reload();
|
|
45381
46979
|
}
|
|
45382
46980
|
/**
|
|
45383
|
-
*
|
|
45384
|
-
*
|
|
45385
|
-
*
|
|
46981
|
+
* Obtiene el idioma almacenado en localStorage.
|
|
46982
|
+
* Si no existe o no es válido, retorna el idioma por defecto.
|
|
46983
|
+
* En SSR (sin localStorage) retorna el default.
|
|
45386
46984
|
*/
|
|
45387
|
-
|
|
45388
|
-
|
|
45389
|
-
|
|
45390
|
-
|
|
45391
|
-
|
|
45392
|
-
|
|
46985
|
+
getStoredLang() {
|
|
46986
|
+
if (!this.isBrowser) {
|
|
46987
|
+
return DEFAULT_LANG;
|
|
46988
|
+
}
|
|
46989
|
+
const stored = localStorage.getItem(LANG_STORAGE_KEY);
|
|
46990
|
+
if (stored === 'es' || stored === 'en') {
|
|
46991
|
+
return stored;
|
|
46992
|
+
}
|
|
46993
|
+
return DEFAULT_LANG;
|
|
45393
46994
|
}
|
|
45394
46995
|
/**
|
|
45395
|
-
*
|
|
45396
|
-
* @param data - Optional data to return
|
|
45397
|
-
* @param role - Optional role
|
|
46996
|
+
* Verifica si el idioma actual es español
|
|
45398
46997
|
*/
|
|
45399
|
-
|
|
45400
|
-
return this.
|
|
46998
|
+
get isSpanish() {
|
|
46999
|
+
return this.lang === 'es';
|
|
45401
47000
|
}
|
|
45402
47001
|
/**
|
|
45403
|
-
*
|
|
47002
|
+
* Verifica si el idioma actual es inglés
|
|
45404
47003
|
*/
|
|
45405
|
-
|
|
45406
|
-
return this.
|
|
45407
|
-
}
|
|
45408
|
-
normalizeCanDismiss(canDismiss) {
|
|
45409
|
-
if (typeof canDismiss === 'boolean') {
|
|
45410
|
-
return canDismiss;
|
|
45411
|
-
}
|
|
45412
|
-
// Wrap the function to ensure it returns a Promise
|
|
45413
|
-
return async (data, role) => {
|
|
45414
|
-
const result = canDismiss();
|
|
45415
|
-
return Promise.resolve(result);
|
|
45416
|
-
};
|
|
45417
|
-
}
|
|
45418
|
-
buildCssClasses(options) {
|
|
45419
|
-
const classes = ['val-modal'];
|
|
45420
|
-
if (options.size) {
|
|
45421
|
-
classes.push(`modal-${options.size}`);
|
|
45422
|
-
}
|
|
45423
|
-
if (options.header) {
|
|
45424
|
-
classes.push('modal-with-header');
|
|
45425
|
-
}
|
|
45426
|
-
if (options.footer) {
|
|
45427
|
-
classes.push('modal-with-footer');
|
|
45428
|
-
}
|
|
45429
|
-
if (options.cssClass) {
|
|
45430
|
-
classes.push(options.cssClass);
|
|
45431
|
-
}
|
|
45432
|
-
return classes.join(' ');
|
|
47004
|
+
get isEnglish() {
|
|
47005
|
+
return this.lang === 'en';
|
|
45433
47006
|
}
|
|
45434
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type:
|
|
45435
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type:
|
|
47007
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocaleService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
47008
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocaleService, providedIn: 'root' }); }
|
|
45436
47009
|
}
|
|
45437
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type:
|
|
47010
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocaleService, decorators: [{
|
|
45438
47011
|
type: Injectable,
|
|
45439
|
-
args: [{
|
|
45440
|
-
|
|
45441
|
-
|
|
45442
|
-
|
|
47012
|
+
args: [{ providedIn: 'root' }]
|
|
47013
|
+
}], ctorParameters: () => [{ type: Object, decorators: [{
|
|
47014
|
+
type: Inject,
|
|
47015
|
+
args: [PLATFORM_ID]
|
|
47016
|
+
}] }] });
|
|
47017
|
+
|
|
47018
|
+
// Types for valtech-components services
|
|
47019
|
+
// ValtechConfig and LangProvider have been removed in v3.0.0
|
|
47020
|
+
// Use LocaleService for language management instead
|
|
45443
47021
|
|
|
45444
47022
|
class MetaService {
|
|
45445
47023
|
constructor() {
|
|
@@ -45738,177 +47316,6 @@ function beautifyLegalArticle(article) {
|
|
|
45738
47316
|
};
|
|
45739
47317
|
}
|
|
45740
47318
|
|
|
45741
|
-
/**
|
|
45742
|
-
* Standard cross-app site links (env-aware)
|
|
45743
|
-
*
|
|
45744
|
-
* En el factory MyValtech cada producto satélite (showcase, sigify, …) enlaza a
|
|
45745
|
-
* `web` —la landing de marca— para soporte, contacto y contenido legal. `web`
|
|
45746
|
-
* es el dueño canónico de esas rutas. Este módulo centraliza:
|
|
45747
|
-
*
|
|
45748
|
-
* 1. El mapa ambiente → base URL de `web` (`VALTECH_WEB_BASE_URLS`).
|
|
45749
|
-
* 2. El catálogo de paths estándar que viven en `web` (`VALTECH_SITE_PATHS`).
|
|
45750
|
-
*
|
|
45751
|
-
* El ambiente se decide en BUILD TIME (campo `valtechEnv` en el `environment*.ts`
|
|
45752
|
-
* de cada app), igual que `apiUrl`. Es el mecanismo más confiable: detectar por
|
|
45753
|
-
* hostname en runtime se rompe con dominios custom / `web.app` / variantes de
|
|
45754
|
-
* localhost.
|
|
45755
|
-
*
|
|
45756
|
-
* La resolución real (join baseUrl + path, abrir en tab nueva, `?lang=`) la hace
|
|
45757
|
-
* `LegalLinkService`; este módulo solo le entrega el `baseUrl` correcto por env.
|
|
45758
|
-
*
|
|
45759
|
-
* @see provideValtechSite — wiring de una sola línea para apps satélite.
|
|
45760
|
-
*/
|
|
45761
|
-
/**
|
|
45762
|
-
* Base URL de `web` (landing de marca) por ambiente.
|
|
45763
|
-
*
|
|
45764
|
-
* - `local` → `ng serve` de `web` (puerto 8100).
|
|
45765
|
-
* - `dev` → proyecto Firebase `myvaltech-dev`.
|
|
45766
|
-
* - `prod` → dominio custom de producción.
|
|
45767
|
-
*
|
|
45768
|
-
* Sin trailing slash (lo asume `LegalLinkService`).
|
|
45769
|
-
*/
|
|
45770
|
-
const VALTECH_WEB_BASE_URLS = {
|
|
45771
|
-
local: 'http://localhost:8100',
|
|
45772
|
-
dev: 'https://myvaltech-dev.web.app',
|
|
45773
|
-
prod: 'https://myvaltech.com',
|
|
45774
|
-
};
|
|
45775
|
-
/**
|
|
45776
|
-
* Resuelve la base URL de `web` para un ambiente.
|
|
45777
|
-
*
|
|
45778
|
-
* Fallback a `prod` cuando el env es desconocido o falta: ante la duda, apuntar
|
|
45779
|
-
* a producción es lo seguro (nunca enlaza a un host local/dev inexistente).
|
|
45780
|
-
*/
|
|
45781
|
-
function resolveWebBaseUrl(env) {
|
|
45782
|
-
if (env && env in VALTECH_WEB_BASE_URLS) {
|
|
45783
|
-
return VALTECH_WEB_BASE_URLS[env];
|
|
45784
|
-
}
|
|
45785
|
-
return VALTECH_WEB_BASE_URLS.prod;
|
|
45786
|
-
}
|
|
45787
|
-
/**
|
|
45788
|
-
* Catálogo de paths estándar que viven en `web`. Las apps resuelven estos
|
|
45789
|
-
* contra la base URL del ambiente vía `LegalLinkService.resolve(path)`.
|
|
45790
|
-
*
|
|
45791
|
-
* Mantener alineado con las rutas reales de `web/src/app/app.routes.ts` y con
|
|
45792
|
-
* `VALTECH_COMPANY_LINKS` (footer).
|
|
45793
|
-
*/
|
|
45794
|
-
const VALTECH_SITE_PATHS = {
|
|
45795
|
-
home: '/',
|
|
45796
|
-
support: '/contact',
|
|
45797
|
-
contact: '/contact',
|
|
45798
|
-
faq: '/faq',
|
|
45799
|
-
about: '/about',
|
|
45800
|
-
terms: '/legal/terms',
|
|
45801
|
-
privacy: '/legal/privacy',
|
|
45802
|
-
cookies: '/legal/cookies',
|
|
45803
|
-
legalNotice: '/legal/legal-notice',
|
|
45804
|
-
};
|
|
45805
|
-
|
|
45806
|
-
const VALTECH_LEGAL_CONFIG = new InjectionToken('VALTECH_LEGAL_CONFIG');
|
|
45807
|
-
/**
|
|
45808
|
-
* Resolves legal/site paths against a configurable main-site base URL.
|
|
45809
|
-
*
|
|
45810
|
-
* - **Main site mode**: no `provideValtechLegal` called → `resolve('/legal/terms')` returns `/legal/terms` (relative).
|
|
45811
|
-
* - **Satellite mode**: `provideValtechLegal({ baseUrl: 'https://myvaltech.com' })` → `resolve('/legal/terms')` returns `'https://myvaltech.com/legal/terms'` and `isExternal('/legal/terms')` returns `true`.
|
|
45812
|
-
*
|
|
45813
|
-
* Absolute URLs passed in are returned unchanged (no double-prefix).
|
|
45814
|
-
*/
|
|
45815
|
-
class LegalLinkService {
|
|
45816
|
-
constructor() {
|
|
45817
|
-
this.config = inject(VALTECH_LEGAL_CONFIG, { optional: true }) ?? {};
|
|
45818
|
-
}
|
|
45819
|
-
/** Effective base URL (null if running as main site). */
|
|
45820
|
-
get baseUrl() {
|
|
45821
|
-
return this.config.baseUrl?.replace(/\/$/, '') ?? null;
|
|
45822
|
-
}
|
|
45823
|
-
/** Whether resolved external links should open in a new tab. */
|
|
45824
|
-
get openInNewTab() {
|
|
45825
|
-
return this.config.openInNewTab ?? Boolean(this.config.baseUrl);
|
|
45826
|
-
}
|
|
45827
|
-
/**
|
|
45828
|
-
* Returns the URL to use for a given internal path. Absolute URLs pass through.
|
|
45829
|
-
*
|
|
45830
|
-
* When `locale` is provided AND the resolved URL would be cross-origin, appends
|
|
45831
|
-
* `?lang={locale}` so the main site can render the legal doc in the user's
|
|
45832
|
-
* active language (instead of its default). Locale is omitted for same-origin
|
|
45833
|
-
* resolves since the main site reads locale from its own `LocaleService`.
|
|
45834
|
-
*
|
|
45835
|
-
* @example
|
|
45836
|
-
* resolve('/legal/terms')
|
|
45837
|
-
* // main site: '/legal/terms'
|
|
45838
|
-
* // satellite: 'https://myvaltech.com/legal/terms'
|
|
45839
|
-
*
|
|
45840
|
-
* resolve('/legal/terms', { locale: 'pt' })
|
|
45841
|
-
* // main site: '/legal/terms' (locale stays implicit)
|
|
45842
|
-
* // satellite: 'https://myvaltech.com/legal/terms?lang=pt'
|
|
45843
|
-
*
|
|
45844
|
-
* resolve('https://x.com/y') // unchanged
|
|
45845
|
-
*/
|
|
45846
|
-
resolve(path, options) {
|
|
45847
|
-
if (!path)
|
|
45848
|
-
return path;
|
|
45849
|
-
if (/^https?:\/\//i.test(path))
|
|
45850
|
-
return path;
|
|
45851
|
-
const base = this.baseUrl;
|
|
45852
|
-
if (!base)
|
|
45853
|
-
return path;
|
|
45854
|
-
const url = `${base}${path.startsWith('/') ? '' : '/'}${path}`;
|
|
45855
|
-
if (options?.locale) {
|
|
45856
|
-
const sep = url.includes('?') ? '&' : '?';
|
|
45857
|
-
return `${url}${sep}lang=${encodeURIComponent(options.locale)}`;
|
|
45858
|
-
}
|
|
45859
|
-
return url;
|
|
45860
|
-
}
|
|
45861
|
-
/** `true` if the path would be resolved to a cross-origin URL. */
|
|
45862
|
-
isExternal(path) {
|
|
45863
|
-
if (/^https?:\/\//i.test(path))
|
|
45864
|
-
return true;
|
|
45865
|
-
return this.baseUrl !== null;
|
|
45866
|
-
}
|
|
45867
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LegalLinkService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
45868
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LegalLinkService, providedIn: 'root' }); }
|
|
45869
|
-
}
|
|
45870
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LegalLinkService, decorators: [{
|
|
45871
|
-
type: Injectable,
|
|
45872
|
-
args: [{ providedIn: 'root' }]
|
|
45873
|
-
}] });
|
|
45874
|
-
/**
|
|
45875
|
-
* Wires `LegalLinkService` for satellite apps. Omit in the main site (the one
|
|
45876
|
-
* that hosts the canonical /legal/* routes) so links stay relative.
|
|
45877
|
-
*
|
|
45878
|
-
* @example
|
|
45879
|
-
* // main.ts of showcase / sigify / etc.
|
|
45880
|
-
* provideValtechLegal({
|
|
45881
|
-
* baseUrl: 'https://myvaltech.com',
|
|
45882
|
-
* openInNewTab: true,
|
|
45883
|
-
* }),
|
|
45884
|
-
*/
|
|
45885
|
-
function provideValtechLegal(config) {
|
|
45886
|
-
return { provide: VALTECH_LEGAL_CONFIG, useValue: config };
|
|
45887
|
-
}
|
|
45888
|
-
/**
|
|
45889
|
-
* Wiring de una sola línea para que una app satélite apunte sus links de sitio
|
|
45890
|
-
* (legal, soporte, contacto, …) a `web` —la landing de marca— en el ambiente
|
|
45891
|
-
* correcto. Resuelve el `baseUrl` desde `VALTECH_WEB_BASE_URLS` según el `env`
|
|
45892
|
-
* baked en build-time (`environment.valtechEnv`) y delega en `provideValtechLegal`.
|
|
45893
|
-
*
|
|
45894
|
-
* Ambiente desconocido/ausente → fallback a `prod` (ver `resolveWebBaseUrl`).
|
|
45895
|
-
* El main site (`web`) NO llama esto — sus links quedan relativos.
|
|
45896
|
-
*
|
|
45897
|
-
* @example
|
|
45898
|
-
* // main.ts de showcase / sigify / …
|
|
45899
|
-
* import { environment } from './environments/environment';
|
|
45900
|
-
* provideValtechSite(environment.valtechEnv), // 'local' | 'dev' | 'prod'
|
|
45901
|
-
*
|
|
45902
|
-
* @see resolveWebBaseUrl
|
|
45903
|
-
* @see VALTECH_SITE_PATHS — catálogo de paths estándar que viven en `web`.
|
|
45904
|
-
*/
|
|
45905
|
-
function provideValtechSite(env, options) {
|
|
45906
|
-
return provideValtechLegal({
|
|
45907
|
-
baseUrl: resolveWebBaseUrl(env),
|
|
45908
|
-
openInNewTab: options?.openInNewTab ?? true,
|
|
45909
|
-
});
|
|
45910
|
-
}
|
|
45911
|
-
|
|
45912
47319
|
class OrgService {
|
|
45913
47320
|
constructor(config, http) {
|
|
45914
47321
|
this.config = config;
|
|
@@ -54293,5 +55700,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
54293
55700
|
* Generated bundle index. Do not edit.
|
|
54294
55701
|
*/
|
|
54295
55702
|
|
|
54296
|
-
export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgService, OrgSwitchService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SegmentControlComponent, SelectSearchComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
55703
|
+
export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgService, OrgSwitchService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
54297
55704
|
//# sourceMappingURL=valtech-components.mjs.map
|