valtech-components 2.0.1009 → 2.0.1010
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/components/molecules/date-input/date-input.component.mjs +38 -18
- package/esm2022/lib/components/organisms/about-view/about-view.component.mjs +436 -0
- package/esm2022/lib/components/organisms/about-view/about-view.i18n.mjs +61 -0
- package/esm2022/lib/components/organisms/about-view/about.routes.mjs +36 -0
- package/esm2022/lib/components/organisms/about-view/types.mjs +19 -0
- package/esm2022/lib/components/organisms/api-keys-modal/api-keys-modal.component.mjs +385 -0
- package/esm2022/lib/components/organisms/api-keys-modal/api-keys-modal.i18n.mjs +63 -0
- package/esm2022/lib/components/organisms/member-import-modal/member-import-modal.component.mjs +313 -0
- package/esm2022/lib/components/organisms/member-import-modal/member-import-modal.i18n.mjs +63 -0
- package/esm2022/lib/components/organisms/organization-view/organization-view.component.mjs +141 -3
- package/esm2022/lib/components/organisms/organization-view/organization-view.i18n.mjs +21 -1
- package/esm2022/lib/services/apikeys/api-keys.service.mjs +46 -0
- package/esm2022/lib/services/apikeys/types.mjs +7 -0
- package/esm2022/lib/services/auth/auth.service.mjs +14 -1
- package/esm2022/lib/services/auth/types.mjs +1 -1
- package/esm2022/lib/services/org/org.service.mjs +8 -1
- package/esm2022/lib/services/org/types.mjs +1 -1
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +7 -1
- package/fesm2022/valtech-components.mjs +1899 -327
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/date-input/date-input.component.d.ts +16 -0
- package/lib/components/organisms/about-view/about-view.component.d.ts +88 -0
- package/lib/components/organisms/about-view/about-view.i18n.d.ts +14 -0
- package/lib/components/organisms/about-view/about.routes.d.ts +31 -0
- package/lib/components/organisms/about-view/types.d.ts +76 -0
- package/lib/components/organisms/api-keys-modal/api-keys-modal.component.d.ts +44 -0
- package/lib/components/organisms/api-keys-modal/api-keys-modal.i18n.d.ts +6 -0
- package/lib/components/organisms/member-import-modal/member-import-modal.component.d.ts +47 -0
- package/lib/components/organisms/member-import-modal/member-import-modal.i18n.d.ts +6 -0
- package/lib/components/organisms/organization-view/organization-view.component.d.ts +6 -0
- package/lib/services/apikeys/api-keys.service.d.ts +25 -0
- package/lib/services/apikeys/types.d.ts +46 -0
- package/lib/services/auth/auth.service.d.ts +7 -1
- package/lib/services/auth/types.d.ts +23 -0
- package/lib/services/org/org.service.d.ts +6 -1
- package/lib/services/org/types.d.ts +30 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +8 -1
|
@@ -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, IonInput, IonSelect, IonSelectOption, IonPopover, IonList, IonItem, IonRadioGroup, IonRadio, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonRippleEffect, IonAccordion, IonAccordionGroup, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonChip, IonNote, IonDatetimeButton, 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, laptopOutline, swapHorizontalOutline, notificationsOffOutline, alarmOutline, chatboxEllipsesOutline, checkmarkDoneOutline, cogOutline, 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, swapHorizontalOutline, notificationsOffOutline, alarmOutline, chatboxEllipsesOutline, checkmarkDoneOutline, cogOutline, shareSocialOutline, helpCircleOutline, 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.1010';
|
|
58
58
|
|
|
59
59
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
60
60
|
let isRefreshing = false;
|
|
@@ -8658,6 +8658,19 @@ class AuthService {
|
|
|
8658
8658
|
.post(`${this.baseUrl}/reset-password`, request)
|
|
8659
8659
|
.pipe(catchError(error => this.handleAuthError(error)));
|
|
8660
8660
|
}
|
|
8661
|
+
/**
|
|
8662
|
+
* Activa una cuenta pre-aprovisionada por una organización (ADR-023): define
|
|
8663
|
+
* la contraseña con el token del enlace de email. En éxito el backend devuelve
|
|
8664
|
+
* tokens de auto-signin → el usuario queda logueado (mismo flujo que verify-email).
|
|
8665
|
+
*/
|
|
8666
|
+
activateAccount(request) {
|
|
8667
|
+
this.stateService.clearError();
|
|
8668
|
+
return this.http.post(`${this.baseUrl}/activate`, request).pipe(tap(response => {
|
|
8669
|
+
if (response.activated && response.accessToken) {
|
|
8670
|
+
this.handleSuccessfulAuth(response);
|
|
8671
|
+
}
|
|
8672
|
+
}), catchError(error => this.handleAuthError(error)));
|
|
8673
|
+
}
|
|
8661
8674
|
/**
|
|
8662
8675
|
* Cambia la contraseña del usuario autenticado.
|
|
8663
8676
|
* Requiere la contraseña actual para verificación.
|
|
@@ -15848,6 +15861,24 @@ class DateInputComponent {
|
|
|
15848
15861
|
get presentation() {
|
|
15849
15862
|
return this.resolvedProps?.presentation || 'date';
|
|
15850
15863
|
}
|
|
15864
|
+
/** Locale del datetime — honra el prop del consumer (default es-ES). */
|
|
15865
|
+
get datetimeLocale() {
|
|
15866
|
+
return this.resolvedProps?.locale || 'es-ES';
|
|
15867
|
+
}
|
|
15868
|
+
/** Primer día de la semana — honra el prop del consumer (default lunes). */
|
|
15869
|
+
get firstDayOfWeek() {
|
|
15870
|
+
return this.resolvedProps?.firstDayOfWeek ?? 1;
|
|
15871
|
+
}
|
|
15872
|
+
/**
|
|
15873
|
+
* Opciones de formato para `ion-datetime` (binding, NO string). Honra el prop
|
|
15874
|
+
* del consumer y cae a un default sensato (date medium / time short).
|
|
15875
|
+
*/
|
|
15876
|
+
get datetimeFormatOptions() {
|
|
15877
|
+
return (this.resolvedProps?.formatOptions || {
|
|
15878
|
+
date: { dateStyle: 'medium' },
|
|
15879
|
+
time: { timeStyle: 'short' },
|
|
15880
|
+
});
|
|
15881
|
+
}
|
|
15851
15882
|
/** Texto del trigger cuando NO hay valor (placeholder explícito o default i18n). */
|
|
15852
15883
|
get placeholderText() {
|
|
15853
15884
|
if (this.resolvedProps?.placeholder)
|
|
@@ -15870,7 +15901,7 @@ class DateInputComponent {
|
|
|
15870
15901
|
if (isNaN(d.getTime()))
|
|
15871
15902
|
return String(raw);
|
|
15872
15903
|
this.i18n.lang(); // reactividad
|
|
15873
|
-
const locale =
|
|
15904
|
+
const locale = this.datetimeLocale;
|
|
15874
15905
|
try {
|
|
15875
15906
|
switch (this.presentation) {
|
|
15876
15907
|
case 'time':
|
|
@@ -15955,27 +15986,28 @@ class DateInputComponent {
|
|
|
15955
15986
|
{{ displayValue || placeholderText }}
|
|
15956
15987
|
</ion-button>
|
|
15957
15988
|
</div>
|
|
15958
|
-
<ion-modal
|
|
15989
|
+
<ion-modal
|
|
15990
|
+
[trigger]="triggerId"
|
|
15991
|
+
[keepContentsMounted]="true"
|
|
15992
|
+
class="val-date-modal"
|
|
15993
|
+
>
|
|
15959
15994
|
<ng-template>
|
|
15960
15995
|
<ion-datetime
|
|
15961
15996
|
[formControl]="resolvedProps.control"
|
|
15962
15997
|
[id]="resolvedProps.token"
|
|
15963
15998
|
[presentation]="presentation"
|
|
15964
|
-
locale="
|
|
15965
|
-
[firstDayOfWeek]="
|
|
15999
|
+
[locale]="datetimeLocale"
|
|
16000
|
+
[firstDayOfWeek]="firstDayOfWeek"
|
|
15966
16001
|
[showDefaultButtons]="true"
|
|
15967
16002
|
[doneText]="doneText"
|
|
15968
16003
|
[cancelText]="cancelText"
|
|
15969
|
-
formatOptions="
|
|
15970
|
-
date: { dateStyle: 'medium' },
|
|
15971
|
-
time: { timeStyle: 'short' }
|
|
15972
|
-
}"
|
|
16004
|
+
[formatOptions]="datetimeFormatOptions"
|
|
15973
16005
|
>
|
|
15974
16006
|
<span slot="title">{{ resolvedProps.hint }}</span>
|
|
15975
16007
|
</ion-datetime>
|
|
15976
16008
|
</ng-template>
|
|
15977
16009
|
</ion-modal>
|
|
15978
|
-
`, isInline: true, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-padding: 16px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:var(--val-form-field-gap, .5rem) 0}@media (min-width: 768px){.input{margin:var(--val-form-field-gap, .75rem) 0}}.submit-actions--sticky{position:sticky;bottom:0;z-index:1;background:var(--ion-background-color);border-top:1px solid var(--ion-color-step-150, rgba(0, 0, 0, .08));padding:.75rem 0 calc(.75rem + env(safe-area-inset-bottom))}.field-description{display:block;font-size:.75rem;color:var(--ion-color-dark);margin-bottom:.25rem;line-height:1.4}.button-container{display:flex;flex-direction:column;align-items:flex-start}.action{margin-top:.25rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$7.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: IonDatetime, selector: "ion-datetime", inputs: ["cancelText", "clearText", "color", "dayValues", "disabled", "doneText", "firstDayOfWeek", "formatOptions", "highlightedDates", "hourCycle", "hourValues", "isDateEnabled", "locale", "max", "min", "minuteValues", "mode", "monthValues", "multiple", "name", "preferWheel", "presentation", "readonly", "showAdjacentDays", "showClearButton", "showDefaultButtons", "showDefaultTimeLabel", "showDefaultTitle", "size", "titleSelectedDatesFormatter", "value", "yearValues"] }, { kind: "component", type: IonModal, selector: "ion-modal" }] }); }
|
|
16010
|
+
`, isInline: true, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-padding: 16px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:var(--val-form-field-gap, .5rem) 0}@media (min-width: 768px){.input{margin:var(--val-form-field-gap, .75rem) 0}}.submit-actions--sticky{position:sticky;bottom:0;z-index:1;background:var(--ion-background-color);border-top:1px solid var(--ion-color-step-150, rgba(0, 0, 0, .08));padding:.75rem 0 calc(.75rem + env(safe-area-inset-bottom))}.field-description{display:block;font-size:.75rem;color:var(--ion-color-dark);margin-bottom:.25rem;line-height:1.4}.button-container{display:flex;flex-direction:column;align-items:flex-start}.action{margin-top:.25rem}ion-modal.val-date-modal{--width: fit-content;--min-width: pxToRem(320);--height: fit-content;--border-radius: pxToRem(16);--box-shadow: 0 pxToRem(12) pxToRem(48) rgba(0, 0, 0, .25)}ion-modal.val-date-modal ion-datetime{margin:0 auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$7.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: IonDatetime, selector: "ion-datetime", inputs: ["cancelText", "clearText", "color", "dayValues", "disabled", "doneText", "firstDayOfWeek", "formatOptions", "highlightedDates", "hourCycle", "hourValues", "isDateEnabled", "locale", "max", "min", "minuteValues", "mode", "monthValues", "multiple", "name", "preferWheel", "presentation", "readonly", "showAdjacentDays", "showClearButton", "showDefaultButtons", "showDefaultTimeLabel", "showDefaultTitle", "size", "titleSelectedDatesFormatter", "value", "yearValues"] }, { kind: "component", type: IonModal, selector: "ion-modal" }] }); }
|
|
15979
16011
|
}
|
|
15980
16012
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DateInputComponent, decorators: [{
|
|
15981
16013
|
type: Component,
|
|
@@ -15994,27 +16026,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
15994
16026
|
{{ displayValue || placeholderText }}
|
|
15995
16027
|
</ion-button>
|
|
15996
16028
|
</div>
|
|
15997
|
-
<ion-modal
|
|
16029
|
+
<ion-modal
|
|
16030
|
+
[trigger]="triggerId"
|
|
16031
|
+
[keepContentsMounted]="true"
|
|
16032
|
+
class="val-date-modal"
|
|
16033
|
+
>
|
|
15998
16034
|
<ng-template>
|
|
15999
16035
|
<ion-datetime
|
|
16000
16036
|
[formControl]="resolvedProps.control"
|
|
16001
16037
|
[id]="resolvedProps.token"
|
|
16002
16038
|
[presentation]="presentation"
|
|
16003
|
-
locale="
|
|
16004
|
-
[firstDayOfWeek]="
|
|
16039
|
+
[locale]="datetimeLocale"
|
|
16040
|
+
[firstDayOfWeek]="firstDayOfWeek"
|
|
16005
16041
|
[showDefaultButtons]="true"
|
|
16006
16042
|
[doneText]="doneText"
|
|
16007
16043
|
[cancelText]="cancelText"
|
|
16008
|
-
formatOptions="
|
|
16009
|
-
date: { dateStyle: 'medium' },
|
|
16010
|
-
time: { timeStyle: 'short' }
|
|
16011
|
-
}"
|
|
16044
|
+
[formatOptions]="datetimeFormatOptions"
|
|
16012
16045
|
>
|
|
16013
16046
|
<span slot="title">{{ resolvedProps.hint }}</span>
|
|
16014
16047
|
</ion-datetime>
|
|
16015
16048
|
</ng-template>
|
|
16016
16049
|
</ion-modal>
|
|
16017
|
-
`, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-padding: 16px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:var(--val-form-field-gap, .5rem) 0}@media (min-width: 768px){.input{margin:var(--val-form-field-gap, .75rem) 0}}.submit-actions--sticky{position:sticky;bottom:0;z-index:1;background:var(--ion-background-color);border-top:1px solid var(--ion-color-step-150, rgba(0, 0, 0, .08));padding:.75rem 0 calc(.75rem + env(safe-area-inset-bottom))}.field-description{display:block;font-size:.75rem;color:var(--ion-color-dark);margin-bottom:.25rem;line-height:1.4}.button-container{display:flex;flex-direction:column;align-items:flex-start}.action{margin-top:.25rem}\n"] }]
|
|
16050
|
+
`, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-padding: 16px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:var(--val-form-field-gap, .5rem) 0}@media (min-width: 768px){.input{margin:var(--val-form-field-gap, .75rem) 0}}.submit-actions--sticky{position:sticky;bottom:0;z-index:1;background:var(--ion-background-color);border-top:1px solid var(--ion-color-step-150, rgba(0, 0, 0, .08));padding:.75rem 0 calc(.75rem + env(safe-area-inset-bottom))}.field-description{display:block;font-size:.75rem;color:var(--ion-color-dark);margin-bottom:.25rem;line-height:1.4}.button-container{display:flex;flex-direction:column;align-items:flex-start}.action{margin-top:.25rem}ion-modal.val-date-modal{--width: fit-content;--min-width: pxToRem(320);--height: fit-content;--border-radius: pxToRem(16);--box-shadow: 0 pxToRem(12) pxToRem(48) rgba(0, 0, 0, .25)}ion-modal.val-date-modal ion-datetime{margin:0 auto}\n"] }]
|
|
16018
16051
|
}], ctorParameters: () => [], propDecorators: { preset: [{
|
|
16019
16052
|
type: Input
|
|
16020
16053
|
}], props: [{
|
|
@@ -41737,7 +41770,7 @@ const PROFILE_VIEW_I18N = {
|
|
|
41737
41770
|
const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
|
|
41738
41771
|
const HANDLE_MIN = 3;
|
|
41739
41772
|
const HANDLE_MAX = 20;
|
|
41740
|
-
const DEFAULT_NAMESPACE$
|
|
41773
|
+
const DEFAULT_NAMESPACE$i = 'Settings.Profile';
|
|
41741
41774
|
/**
|
|
41742
41775
|
* `val-profile-view` — vista Perfil full-feature autocontenida (organism).
|
|
41743
41776
|
*
|
|
@@ -41787,7 +41820,7 @@ class ProfileViewComponent {
|
|
|
41787
41820
|
showPhone: merged.showPhone ?? true,
|
|
41788
41821
|
showShareCta: merged.showShareCta ?? true,
|
|
41789
41822
|
profileBaseUrl: merged.profileBaseUrl ?? '',
|
|
41790
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
41823
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$i,
|
|
41791
41824
|
onSaved: merged.onSaved,
|
|
41792
41825
|
onAvatarUploaded: merged.onAvatarUploaded,
|
|
41793
41826
|
};
|
|
@@ -42718,7 +42751,7 @@ const PREFERENCES_VIEW_I18N = {
|
|
|
42718
42751
|
},
|
|
42719
42752
|
};
|
|
42720
42753
|
|
|
42721
|
-
const DEFAULT_NAMESPACE$
|
|
42754
|
+
const DEFAULT_NAMESPACE$h = 'Settings.General';
|
|
42722
42755
|
const DEFAULT_LANGUAGES = ['es', 'en'];
|
|
42723
42756
|
/**
|
|
42724
42757
|
* `val-preferences-view` — vista de preferencias de UI cross-app (tema +
|
|
@@ -42755,7 +42788,7 @@ class PreferencesViewComponent {
|
|
|
42755
42788
|
showLanguage: merged.showLanguage ?? true,
|
|
42756
42789
|
showFontSize: merged.showFontSize ?? true,
|
|
42757
42790
|
supportedLanguages: merged.supportedLanguages ?? DEFAULT_LANGUAGES,
|
|
42758
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
42791
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$h,
|
|
42759
42792
|
onThemeChanged: merged.onThemeChanged,
|
|
42760
42793
|
onLanguageChanged: merged.onLanguageChanged,
|
|
42761
42794
|
onFontSizeChanged: merged.onFontSizeChanged,
|
|
@@ -44511,7 +44544,7 @@ const SECURITY_VIEW_I18N = {
|
|
|
44511
44544
|
};
|
|
44512
44545
|
|
|
44513
44546
|
addIcons({ laptopOutline, phonePortraitOutline });
|
|
44514
|
-
const DEFAULT_NAMESPACE$
|
|
44547
|
+
const DEFAULT_NAMESPACE$g = 'Settings.Security';
|
|
44515
44548
|
/**
|
|
44516
44549
|
* `val-security-view` — vista Seguridad full-feature autocontenida (organism).
|
|
44517
44550
|
*
|
|
@@ -44560,7 +44593,7 @@ class SecurityViewComponent {
|
|
|
44560
44593
|
showChangeEmail: merged.showChangeEmail ?? true,
|
|
44561
44594
|
showMfa: merged.showMfa ?? true,
|
|
44562
44595
|
showPolicies: merged.showPolicies ?? true,
|
|
44563
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
44596
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$g,
|
|
44564
44597
|
handleDeepLinks: merged.handleDeepLinks ?? true,
|
|
44565
44598
|
homeRoute: merged.homeRoute ?? '/app/home',
|
|
44566
44599
|
onMfaCompleted: merged.onMfaCompleted,
|
|
@@ -45362,6 +45395,13 @@ class OrgService {
|
|
|
45362
45395
|
inviteUser(orgId, req) {
|
|
45363
45396
|
return this.http.post(`${this.baseUrl}/${orgId}/invite`, req);
|
|
45364
45397
|
}
|
|
45398
|
+
/**
|
|
45399
|
+
* Crea/asigna miembros en masa (ADR-023). Resultado por-fila (partial success):
|
|
45400
|
+
* `created` (pre-aprovisionado), `assigned` (ya existía), `skipped`, `error`.
|
|
45401
|
+
*/
|
|
45402
|
+
importMembers(orgId, req) {
|
|
45403
|
+
return this.http.post(`${this.baseUrl}/${orgId}/members/import`, req);
|
|
45404
|
+
}
|
|
45365
45405
|
leaveOrg(orgId) {
|
|
45366
45406
|
return this.http.post(`${this.baseUrl}/${orgId}/leave`, {});
|
|
45367
45407
|
}
|
|
@@ -45466,7 +45506,7 @@ const CREATE_ORG_MODAL_I18N = {
|
|
|
45466
45506
|
},
|
|
45467
45507
|
};
|
|
45468
45508
|
|
|
45469
|
-
const DEFAULT_NAMESPACE$
|
|
45509
|
+
const DEFAULT_NAMESPACE$f = 'CreateOrgModal';
|
|
45470
45510
|
/**
|
|
45471
45511
|
* `val-create-org-modal` — modal de creación de organización (organism
|
|
45472
45512
|
* compartido). Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por
|
|
@@ -45492,10 +45532,10 @@ class CreateOrgModalComponent {
|
|
|
45492
45532
|
this.toast = inject(ToastService);
|
|
45493
45533
|
this.errors = inject(ValtechErrorService);
|
|
45494
45534
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
45495
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
45535
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$f;
|
|
45496
45536
|
this._busy = signal(false);
|
|
45497
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
45498
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
45537
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$f)) {
|
|
45538
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$f, CREATE_ORG_MODAL_I18N);
|
|
45499
45539
|
}
|
|
45500
45540
|
this.formMeta = signal(this.buildFormMeta());
|
|
45501
45541
|
effect(() => {
|
|
@@ -45731,7 +45771,7 @@ const DELETE_ACCOUNT_MODAL_I18N = {
|
|
|
45731
45771
|
},
|
|
45732
45772
|
};
|
|
45733
45773
|
|
|
45734
|
-
const DEFAULT_NAMESPACE$
|
|
45774
|
+
const DEFAULT_NAMESPACE$e = 'Settings.DeleteAccount';
|
|
45735
45775
|
/**
|
|
45736
45776
|
* `val-delete-account-modal` — modal de eliminación de cuenta (organism,
|
|
45737
45777
|
* cuenta-específico). Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
@@ -45757,7 +45797,7 @@ class DeleteAccountModalComponent {
|
|
|
45757
45797
|
this.i18n = inject(I18nService);
|
|
45758
45798
|
this.toast = inject(ToastService);
|
|
45759
45799
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
45760
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
45800
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$e;
|
|
45761
45801
|
this.hasPassword = signal(null);
|
|
45762
45802
|
this.codeSent = signal(false);
|
|
45763
45803
|
this.formState = signal(ComponentStates.ENABLED);
|
|
@@ -45824,8 +45864,8 @@ class DeleteAccountModalComponent {
|
|
|
45824
45864
|
],
|
|
45825
45865
|
actions: this.submitBtn('delete-code-submit', 'confirmCta'),
|
|
45826
45866
|
}));
|
|
45827
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
45828
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
45867
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$e)) {
|
|
45868
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$e, DELETE_ACCOUNT_MODAL_I18N);
|
|
45829
45869
|
}
|
|
45830
45870
|
}
|
|
45831
45871
|
ngOnInit() {
|
|
@@ -46026,7 +46066,7 @@ const ORG_INFO_SHEET_I18N = {
|
|
|
46026
46066
|
},
|
|
46027
46067
|
};
|
|
46028
46068
|
|
|
46029
|
-
const DEFAULT_NAMESPACE$
|
|
46069
|
+
const DEFAULT_NAMESPACE$d = 'OrgInfoSheet';
|
|
46030
46070
|
/**
|
|
46031
46071
|
* `val-org-info-sheet` — sheet informativo "¿Qué son las organizaciones?".
|
|
46032
46072
|
* Extraído del `OrgInfoSheetComponent` inline de `AccountPage` bajo el proceso de
|
|
@@ -46071,15 +46111,15 @@ class OrgInfoSheetComponent {
|
|
|
46071
46111
|
text: this.closeProps.text || this.resolvedCloseLabel(),
|
|
46072
46112
|
}));
|
|
46073
46113
|
addIcons({ peopleOutline, swapHorizontalOutline, mailOutline });
|
|
46074
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
46075
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
46114
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$d)) {
|
|
46115
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$d, ORG_INFO_SHEET_I18N);
|
|
46076
46116
|
}
|
|
46077
46117
|
}
|
|
46078
46118
|
dismiss() {
|
|
46079
46119
|
this._modalRef?.dismiss(null, 'cancel');
|
|
46080
46120
|
}
|
|
46081
46121
|
tt(key) {
|
|
46082
|
-
return this.i18n.t(key, DEFAULT_NAMESPACE$
|
|
46122
|
+
return this.i18n.t(key, DEFAULT_NAMESPACE$d);
|
|
46083
46123
|
}
|
|
46084
46124
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgInfoSheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
46085
46125
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: OrgInfoSheetComponent, isStandalone: true, selector: "val-org-info-sheet", inputs: { title: "title", subtitle: "subtitle", body: "body", closeLabel: "closeLabel", closeProps: "closeProps", _modalRef: "_modalRef" }, ngImport: i0, template: `
|
|
@@ -46246,7 +46286,7 @@ const SWITCH_ORG_MODAL_I18N = {
|
|
|
46246
46286
|
},
|
|
46247
46287
|
};
|
|
46248
46288
|
|
|
46249
|
-
const DEFAULT_NAMESPACE$
|
|
46289
|
+
const DEFAULT_NAMESPACE$c = 'Settings.SwitchOrg';
|
|
46250
46290
|
addIcons({ businessOutline, checkmarkCircleOutline });
|
|
46251
46291
|
/**
|
|
46252
46292
|
* `val-switch-org-modal` — modal de cambio de organización activa (organism
|
|
@@ -46272,7 +46312,7 @@ class SwitchOrgModalComponent {
|
|
|
46272
46312
|
this.orgSwitch = inject(OrgSwitchService);
|
|
46273
46313
|
this.errors = inject(ValtechErrorService);
|
|
46274
46314
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
46275
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
46315
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$c;
|
|
46276
46316
|
this.orgs = signal([]);
|
|
46277
46317
|
this.loading = signal(false);
|
|
46278
46318
|
this.query = signal('');
|
|
@@ -46288,8 +46328,8 @@ class SwitchOrgModalComponent {
|
|
|
46288
46328
|
return list;
|
|
46289
46329
|
return list.filter(o => o.name.toLowerCase().includes(q));
|
|
46290
46330
|
});
|
|
46291
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
46292
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
46331
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$c)) {
|
|
46332
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$c, SWITCH_ORG_MODAL_I18N);
|
|
46293
46333
|
}
|
|
46294
46334
|
this.loadOrgs();
|
|
46295
46335
|
}
|
|
@@ -46607,7 +46647,7 @@ const ACCOUNT_VIEW_I18N = {
|
|
|
46607
46647
|
};
|
|
46608
46648
|
|
|
46609
46649
|
addIcons({ peopleOutline, businessOutline, chevronForwardOutline, swapHorizontalOutline, mailOutline });
|
|
46610
|
-
const DEFAULT_NAMESPACE$
|
|
46650
|
+
const DEFAULT_NAMESPACE$b = 'Settings.Account';
|
|
46611
46651
|
const DEFAULT_MANAGE_ORG_ROUTE = '/app/settings/organization';
|
|
46612
46652
|
/**
|
|
46613
46653
|
* `val-account-view` — vista Cuenta full-feature autocontenida (organism).
|
|
@@ -46648,7 +46688,7 @@ class AccountViewComponent {
|
|
|
46648
46688
|
showNewOrgCta: merged.showNewOrgCta ?? true,
|
|
46649
46689
|
showLogout: merged.showLogout ?? true,
|
|
46650
46690
|
showDeleteAccount: merged.showDeleteAccount ?? true,
|
|
46651
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
46691
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$b,
|
|
46652
46692
|
manageOrgRoute: merged.manageOrgRoute ?? DEFAULT_MANAGE_ORG_ROUTE,
|
|
46653
46693
|
onOrgSwitched: merged.onOrgSwitched,
|
|
46654
46694
|
onOrgCreated: merged.onOrgCreated,
|
|
@@ -47373,7 +47413,7 @@ const EDIT_ORG_MODAL_I18N = {
|
|
|
47373
47413
|
},
|
|
47374
47414
|
};
|
|
47375
47415
|
|
|
47376
|
-
const DEFAULT_NAMESPACE$
|
|
47416
|
+
const DEFAULT_NAMESPACE$a = 'EditOrgModal';
|
|
47377
47417
|
/**
|
|
47378
47418
|
* `val-edit-org-modal` — modal de edición de organización (nombre + descripción).
|
|
47379
47419
|
* Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por la vista de
|
|
@@ -47400,10 +47440,10 @@ class EditOrgModalComponent {
|
|
|
47400
47440
|
/** Organización a editar (pasada por `componentProps`). */
|
|
47401
47441
|
this.org = null;
|
|
47402
47442
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
47403
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
47443
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$a;
|
|
47404
47444
|
this.saving = signal(false);
|
|
47405
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
47406
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
47445
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$a)) {
|
|
47446
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$a, EDIT_ORG_MODAL_I18N);
|
|
47407
47447
|
}
|
|
47408
47448
|
this.formMeta = signal(this.buildFormMeta());
|
|
47409
47449
|
effect(() => {
|
|
@@ -47641,7 +47681,7 @@ const INVITE_MEMBER_MODAL_I18N = {
|
|
|
47641
47681
|
},
|
|
47642
47682
|
};
|
|
47643
47683
|
|
|
47644
|
-
const DEFAULT_NAMESPACE$
|
|
47684
|
+
const DEFAULT_NAMESPACE$9 = 'InviteModal';
|
|
47645
47685
|
addIcons({ personOutline, mailOutline, closeOutline, checkmarkCircleOutline });
|
|
47646
47686
|
/**
|
|
47647
47687
|
* `val-invite-member-modal` — invitación de miembros a una organización.
|
|
@@ -47672,7 +47712,7 @@ class InviteMemberModalComponent {
|
|
|
47672
47712
|
/** Ids de miembros ya existentes (excluidos de los resultados). */
|
|
47673
47713
|
this.existingMemberIds = [];
|
|
47674
47714
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
47675
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
47715
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$9;
|
|
47676
47716
|
this.query = signal('');
|
|
47677
47717
|
this.searching = signal(false);
|
|
47678
47718
|
this.searchResults = signal([]);
|
|
@@ -47719,8 +47759,8 @@ class InviteMemberModalComponent {
|
|
|
47719
47759
|
state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
47720
47760
|
}));
|
|
47721
47761
|
this.searchSubject = new Subject();
|
|
47722
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
47723
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
47762
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$9)) {
|
|
47763
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$9, INVITE_MEMBER_MODAL_I18N);
|
|
47724
47764
|
}
|
|
47725
47765
|
this.loadRoles();
|
|
47726
47766
|
this.roleSub = this.roleControl.valueChanges.subscribe(v => {
|
|
@@ -48209,7 +48249,7 @@ const MEMBER_DETAIL_MODAL_I18N = {
|
|
|
48209
48249
|
},
|
|
48210
48250
|
};
|
|
48211
48251
|
|
|
48212
|
-
const DEFAULT_NAMESPACE$
|
|
48252
|
+
const DEFAULT_NAMESPACE$8 = 'Settings.MemberDetail';
|
|
48213
48253
|
/**
|
|
48214
48254
|
* `val-member-detail-modal` — detalle de miembro de organización: perfil, rol y
|
|
48215
48255
|
* permisos. Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
@@ -48246,7 +48286,7 @@ class MemberDetailModalComponent {
|
|
|
48246
48286
|
{ id: 'admin', name: 'admin' },
|
|
48247
48287
|
];
|
|
48248
48288
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
48249
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
48289
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$8;
|
|
48250
48290
|
this.working = signal(false);
|
|
48251
48291
|
this.currentRole = signal('');
|
|
48252
48292
|
this.roleControl = new FormControl('');
|
|
@@ -48286,8 +48326,8 @@ class MemberDetailModalComponent {
|
|
|
48286
48326
|
type: 'button',
|
|
48287
48327
|
state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
48288
48328
|
}));
|
|
48289
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
48290
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
48329
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$8)) {
|
|
48330
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$8, MEMBER_DETAIL_MODAL_I18N);
|
|
48291
48331
|
}
|
|
48292
48332
|
}
|
|
48293
48333
|
ngOnInit() {
|
|
@@ -48624,7 +48664,7 @@ const TRANSFER_OWNERSHIP_MODAL_I18N = {
|
|
|
48624
48664
|
},
|
|
48625
48665
|
};
|
|
48626
48666
|
|
|
48627
|
-
const DEFAULT_NAMESPACE$
|
|
48667
|
+
const DEFAULT_NAMESPACE$7 = 'Settings.TransferModal';
|
|
48628
48668
|
/**
|
|
48629
48669
|
* `val-transfer-ownership-modal` — selector de miembro para transferir la
|
|
48630
48670
|
* propiedad de la organización. Promovido desde `showcase` bajo el proceso de
|
|
@@ -48648,7 +48688,7 @@ class TransferOwnershipModalComponent {
|
|
|
48648
48688
|
/** Lista de miembros transferibles (todos excepto el owner actual). */
|
|
48649
48689
|
this.members = [];
|
|
48650
48690
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
48651
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
48691
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$7;
|
|
48652
48692
|
this.query = signal('');
|
|
48653
48693
|
this.selectedId = signal('');
|
|
48654
48694
|
this.filtered = computed(() => {
|
|
@@ -48660,8 +48700,8 @@ class TransferOwnershipModalComponent {
|
|
|
48660
48700
|
m.userId.toLowerCase().includes(q));
|
|
48661
48701
|
});
|
|
48662
48702
|
this.noResultsLabel = computed(() => `${this.t('noResults')} "${this.query()}"`);
|
|
48663
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
48664
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
48703
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
|
|
48704
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$7, TRANSFER_OWNERSHIP_MODAL_I18N);
|
|
48665
48705
|
}
|
|
48666
48706
|
}
|
|
48667
48707
|
ngOnInit() {
|
|
@@ -48846,6 +48886,849 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
48846
48886
|
type: Input
|
|
48847
48887
|
}] } });
|
|
48848
48888
|
|
|
48889
|
+
/**
|
|
48890
|
+
* Defaults i18n (es/en) embebidos en `val-member-import-modal` (ADR-023).
|
|
48891
|
+
* Auto-registrados si el consumer no proveyó el namespace `Settings.ImportModal`.
|
|
48892
|
+
*/
|
|
48893
|
+
const MEMBER_IMPORT_MODAL_I18N = {
|
|
48894
|
+
es: {
|
|
48895
|
+
title: 'Importar miembros',
|
|
48896
|
+
hint: 'Pega un CSV con columnas: email, nombre, rol (una fila por persona).',
|
|
48897
|
+
csvPlaceholder: 'email,nombre,rol\nana@x.com,Ana,editor\nbeto@x.com,Beto,viewer',
|
|
48898
|
+
rolesAvailable: 'Roles disponibles:',
|
|
48899
|
+
onConflictLabel: 'Si el email ya existe',
|
|
48900
|
+
conflictAssign: 'Asignar el rol',
|
|
48901
|
+
conflictSkip: 'Omitir',
|
|
48902
|
+
conflictError: 'Marcar error',
|
|
48903
|
+
sendActivation: 'Enviar email de activación a las cuentas nuevas',
|
|
48904
|
+
submit: 'Importar',
|
|
48905
|
+
submitting: 'Importando...',
|
|
48906
|
+
parseError: 'No se pudo interpretar el CSV. Revisa el formato.',
|
|
48907
|
+
noRows: 'Pega al menos una fila válida (email y rol).',
|
|
48908
|
+
summary: 'Resumen',
|
|
48909
|
+
created: 'creados',
|
|
48910
|
+
assigned: 'asignados',
|
|
48911
|
+
skipped: 'omitidos',
|
|
48912
|
+
failed: 'con error',
|
|
48913
|
+
colEmail: 'Email',
|
|
48914
|
+
colStatus: 'Estado',
|
|
48915
|
+
colDetail: 'Detalle',
|
|
48916
|
+
activationSent: 'activación enviada',
|
|
48917
|
+
activationPending: 'activación pendiente',
|
|
48918
|
+
again: 'Importar otro lote',
|
|
48919
|
+
close: 'Cerrar',
|
|
48920
|
+
genericError: 'No se pudo completar la importación. Intenta de nuevo.',
|
|
48921
|
+
},
|
|
48922
|
+
en: {
|
|
48923
|
+
title: 'Import members',
|
|
48924
|
+
hint: 'Paste a CSV with columns: email, name, role (one row per person).',
|
|
48925
|
+
csvPlaceholder: 'email,name,role\nana@x.com,Ana,editor\nbeto@x.com,Beto,viewer',
|
|
48926
|
+
rolesAvailable: 'Available roles:',
|
|
48927
|
+
onConflictLabel: 'If the email already exists',
|
|
48928
|
+
conflictAssign: 'Assign the role',
|
|
48929
|
+
conflictSkip: 'Skip',
|
|
48930
|
+
conflictError: 'Mark as error',
|
|
48931
|
+
sendActivation: 'Send activation email to new accounts',
|
|
48932
|
+
submit: 'Import',
|
|
48933
|
+
submitting: 'Importing...',
|
|
48934
|
+
parseError: "Couldn't parse the CSV. Check the format.",
|
|
48935
|
+
noRows: 'Paste at least one valid row (email and role).',
|
|
48936
|
+
summary: 'Summary',
|
|
48937
|
+
created: 'created',
|
|
48938
|
+
assigned: 'assigned',
|
|
48939
|
+
skipped: 'skipped',
|
|
48940
|
+
failed: 'failed',
|
|
48941
|
+
colEmail: 'Email',
|
|
48942
|
+
colStatus: 'Status',
|
|
48943
|
+
colDetail: 'Detail',
|
|
48944
|
+
activationSent: 'activation sent',
|
|
48945
|
+
activationPending: 'activation pending',
|
|
48946
|
+
again: 'Import another batch',
|
|
48947
|
+
close: 'Close',
|
|
48948
|
+
genericError: "Couldn't complete the import. Try again.",
|
|
48949
|
+
},
|
|
48950
|
+
};
|
|
48951
|
+
|
|
48952
|
+
const DEFAULT_NAMESPACE$6 = 'Settings.ImportModal';
|
|
48953
|
+
/**
|
|
48954
|
+
* `val-member-import-modal` — panel de carga masiva de miembros (ADR-023 fase 6).
|
|
48955
|
+
*
|
|
48956
|
+
* El admin pega un CSV (`email,nombre,rol`); el modal lo parsea a filas y llama
|
|
48957
|
+
* `OrgService.importMembers`. Muestra el resultado por-fila (partial success):
|
|
48958
|
+
* `created` / `assigned` / `skipped` / `error`. Header canónico (Regla #5).
|
|
48959
|
+
*
|
|
48960
|
+
* Auto-registra defaults i18n (es/en) en `Settings.ImportModal`.
|
|
48961
|
+
*/
|
|
48962
|
+
class MemberImportModalComponent {
|
|
48963
|
+
constructor() {
|
|
48964
|
+
this.i18n = inject(I18nService);
|
|
48965
|
+
this.orgService = inject(OrgService);
|
|
48966
|
+
/** Org destino del import. */
|
|
48967
|
+
this.orgId = '';
|
|
48968
|
+
/** Nombres de roles disponibles (para el hint). Opcional. */
|
|
48969
|
+
this.availableRoles = [];
|
|
48970
|
+
/** Namespace i18n. */
|
|
48971
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$6;
|
|
48972
|
+
this.csv = '';
|
|
48973
|
+
this.onConflict = 'assignRole';
|
|
48974
|
+
this.sendActivation = true;
|
|
48975
|
+
this.submitting = signal(false);
|
|
48976
|
+
this.errorMsg = signal('');
|
|
48977
|
+
this.results = signal(null);
|
|
48978
|
+
this.rolesHint = computed(() => this.availableRoles.length ? `${this.t('rolesAvailable')} ${this.availableRoles.join(', ')}` : '');
|
|
48979
|
+
this.summaryLabel = computed(() => {
|
|
48980
|
+
const s = this.results()?.summary;
|
|
48981
|
+
if (!s)
|
|
48982
|
+
return this.t('summary');
|
|
48983
|
+
return `${this.t('summary')}: ${s.created} ${this.t('created')} · ${s.assigned} ${this.t('assigned')} · ${s.skipped} ${this.t('skipped')} · ${s.failed} ${this.t('failed')}`;
|
|
48984
|
+
});
|
|
48985
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$6)) {
|
|
48986
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$6, MEMBER_IMPORT_MODAL_I18N);
|
|
48987
|
+
}
|
|
48988
|
+
}
|
|
48989
|
+
ngOnInit() {
|
|
48990
|
+
if (!this.i18n.hasNamespace(this.i18nNamespace)) {
|
|
48991
|
+
this.i18n.registerContent(this.i18nNamespace, MEMBER_IMPORT_MODAL_I18N);
|
|
48992
|
+
}
|
|
48993
|
+
}
|
|
48994
|
+
/** Parsea el CSV pegado a filas {email,name,roleName}. Tolera header. */
|
|
48995
|
+
parseCsv() {
|
|
48996
|
+
const rows = [];
|
|
48997
|
+
for (const raw of this.csv.split(/\r?\n/)) {
|
|
48998
|
+
const line = raw.trim();
|
|
48999
|
+
if (!line)
|
|
49000
|
+
continue;
|
|
49001
|
+
const cols = line.split(',').map(c => c.trim());
|
|
49002
|
+
const email = (cols[0] ?? '').toLowerCase();
|
|
49003
|
+
// Saltar header (primera col "email") o líneas sin @.
|
|
49004
|
+
if (!email || !email.includes('@'))
|
|
49005
|
+
continue;
|
|
49006
|
+
rows.push({ email, name: cols[1] ?? '', roleName: cols[2] ?? '' });
|
|
49007
|
+
}
|
|
49008
|
+
return rows;
|
|
49009
|
+
}
|
|
49010
|
+
submit() {
|
|
49011
|
+
this.errorMsg.set('');
|
|
49012
|
+
const users = this.parseCsv();
|
|
49013
|
+
if (users.length === 0) {
|
|
49014
|
+
this.errorMsg.set(this.t('noRows'));
|
|
49015
|
+
return;
|
|
49016
|
+
}
|
|
49017
|
+
if (users.some(u => !u.roleName)) {
|
|
49018
|
+
this.errorMsg.set(this.t('parseError'));
|
|
49019
|
+
return;
|
|
49020
|
+
}
|
|
49021
|
+
this.submitting.set(true);
|
|
49022
|
+
this.orgService
|
|
49023
|
+
.importMembers(this.orgId, {
|
|
49024
|
+
users,
|
|
49025
|
+
onConflict: this.onConflict,
|
|
49026
|
+
sendActivation: this.sendActivation,
|
|
49027
|
+
})
|
|
49028
|
+
.subscribe({
|
|
49029
|
+
next: res => {
|
|
49030
|
+
this.submitting.set(false);
|
|
49031
|
+
this.results.set(res);
|
|
49032
|
+
// Refrescar lista del consumer si hubo altas/asignaciones efectivas.
|
|
49033
|
+
if (res.summary.created + res.summary.assigned > 0) {
|
|
49034
|
+
this.onSuccess?.();
|
|
49035
|
+
}
|
|
49036
|
+
},
|
|
49037
|
+
error: err => {
|
|
49038
|
+
this.submitting.set(false);
|
|
49039
|
+
this.errorMsg.set(err?.error?.message ?? this.t('genericError'));
|
|
49040
|
+
},
|
|
49041
|
+
});
|
|
49042
|
+
}
|
|
49043
|
+
statusLabel(r) {
|
|
49044
|
+
const base = this.t(r.status === 'error' ? 'failed' : r.status);
|
|
49045
|
+
if (r.status === 'created' && r.activation) {
|
|
49046
|
+
return `${base} · ${this.t(r.activation === 'sent' ? 'activationSent' : 'activationPending')}`;
|
|
49047
|
+
}
|
|
49048
|
+
return base;
|
|
49049
|
+
}
|
|
49050
|
+
reset() {
|
|
49051
|
+
this.csv = '';
|
|
49052
|
+
this.errorMsg.set('');
|
|
49053
|
+
this.results.set(null);
|
|
49054
|
+
}
|
|
49055
|
+
dismiss() {
|
|
49056
|
+
this._modalRef?.dismiss(null, 'cancel');
|
|
49057
|
+
}
|
|
49058
|
+
t(key) {
|
|
49059
|
+
return this.i18n.t(key, this.i18nNamespace);
|
|
49060
|
+
}
|
|
49061
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberImportModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
49062
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MemberImportModalComponent, isStandalone: true, selector: "val-member-import-modal", inputs: { orgId: "orgId", availableRoles: "availableRoles", onSuccess: "onSuccess", _modalRef: "_modalRef", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
|
|
49063
|
+
<ion-header>
|
|
49064
|
+
<ion-toolbar>
|
|
49065
|
+
<ion-buttons slot="end">
|
|
49066
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
49067
|
+
<strong>{{ t('close') }}</strong>
|
|
49068
|
+
</ion-button>
|
|
49069
|
+
</ion-buttons>
|
|
49070
|
+
</ion-toolbar>
|
|
49071
|
+
</ion-header>
|
|
49072
|
+
|
|
49073
|
+
<ion-content class="ion-padding">
|
|
49074
|
+
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
49075
|
+
|
|
49076
|
+
@if (!results()) {
|
|
49077
|
+
<val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
|
|
49078
|
+
|
|
49079
|
+
@if (rolesHint()) {
|
|
49080
|
+
<val-text [props]="{ content: rolesHint(), size: 'small', color: 'medium', bold: false }" />
|
|
49081
|
+
}
|
|
49082
|
+
|
|
49083
|
+
<ion-textarea
|
|
49084
|
+
class="import-csv"
|
|
49085
|
+
[autoGrow]="true"
|
|
49086
|
+
[rows]="6"
|
|
49087
|
+
[placeholder]="t('csvPlaceholder')"
|
|
49088
|
+
[(ngModel)]="csv"
|
|
49089
|
+
/>
|
|
49090
|
+
|
|
49091
|
+
<ion-select
|
|
49092
|
+
class="import-conflict"
|
|
49093
|
+
label="{{ t('onConflictLabel') }}"
|
|
49094
|
+
labelPlacement="stacked"
|
|
49095
|
+
interface="popover"
|
|
49096
|
+
[(ngModel)]="onConflict"
|
|
49097
|
+
>
|
|
49098
|
+
<ion-select-option value="assignRole">{{ t('conflictAssign') }}</ion-select-option>
|
|
49099
|
+
<ion-select-option value="skip">{{ t('conflictSkip') }}</ion-select-option>
|
|
49100
|
+
<ion-select-option value="error">{{ t('conflictError') }}</ion-select-option>
|
|
49101
|
+
</ion-select>
|
|
49102
|
+
|
|
49103
|
+
<ion-checkbox class="import-activation" labelPlacement="end" [(ngModel)]="sendActivation">
|
|
49104
|
+
{{ t('sendActivation') }}
|
|
49105
|
+
</ion-checkbox>
|
|
49106
|
+
|
|
49107
|
+
@if (errorMsg()) {
|
|
49108
|
+
<p class="import-error">{{ errorMsg() }}</p>
|
|
49109
|
+
}
|
|
49110
|
+
|
|
49111
|
+
<div class="import-actions">
|
|
49112
|
+
<ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
|
|
49113
|
+
@if (submitting()) {
|
|
49114
|
+
<ion-spinner name="dots" />
|
|
49115
|
+
{{ t('submitting') }}
|
|
49116
|
+
} @else {
|
|
49117
|
+
{{ t('submit') }}
|
|
49118
|
+
}
|
|
49119
|
+
</ion-button>
|
|
49120
|
+
</div>
|
|
49121
|
+
} @else {
|
|
49122
|
+
<!-- Resultados -->
|
|
49123
|
+
<val-title [props]="{ content: summaryLabel(), size: 'large', color: '', bold: true }" />
|
|
49124
|
+
<div class="import-results">
|
|
49125
|
+
@for (r of results()!.results; track r.email) {
|
|
49126
|
+
<div class="import-row import-row--{{ r.status }}">
|
|
49127
|
+
<span class="import-row__email">{{ r.email }}</span>
|
|
49128
|
+
<span class="import-row__status">{{ statusLabel(r) }}</span>
|
|
49129
|
+
@if (r.reason) {
|
|
49130
|
+
<span class="import-row__reason">{{ r.reason }}</span>
|
|
49131
|
+
}
|
|
49132
|
+
</div>
|
|
49133
|
+
}
|
|
49134
|
+
</div>
|
|
49135
|
+
<div class="import-actions">
|
|
49136
|
+
<ion-button expand="block" fill="outline" color="dark" (click)="reset()">
|
|
49137
|
+
{{ t('again') }}
|
|
49138
|
+
</ion-button>
|
|
49139
|
+
</div>
|
|
49140
|
+
}
|
|
49141
|
+
</ion-content>
|
|
49142
|
+
`, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.import-csv{--background: var(--ion-color-light);border-radius:10px;margin-bottom:16px;font-family:monospace;font-size:13px}.import-conflict{margin-bottom:12px}.import-activation{display:block;margin-bottom:16px;font-size:14px}.import-error{margin:0 0 12px;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.import-actions{margin-top:8px}.import-results{display:flex;flex-direction:column;gap:6px;margin:12px 0}.import-row{display:grid;grid-template-columns:1fr auto;grid-row-gap:2px;align-items:center;padding:10px 12px;border-radius:8px;border-left:3px solid var(--ion-color-medium);background:var(--ion-color-light);font-size:14px}.import-row--created{border-left-color:var(--ion-color-success, #2dd36f)}.import-row--assigned{border-left-color:var(--ion-color-primary, #7026df)}.import-row--skipped{border-left-color:var(--ion-color-medium, #92949c)}.import-row--error{border-left-color:var(--ion-color-danger, #c0392b)}.import-row__email{font-weight:600;color:var(--ion-color-dark)}.import-row__status{text-align:right;color:var(--ion-color-medium-shade, #6b6e76)}.import-row__reason{grid-column:1 / -1;font-size:12px;color:var(--ion-color-danger, #c0392b)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
|
|
49143
|
+
}
|
|
49144
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberImportModalComponent, decorators: [{
|
|
49145
|
+
type: Component,
|
|
49146
|
+
args: [{ selector: 'val-member-import-modal', standalone: true, imports: [
|
|
49147
|
+
FormsModule,
|
|
49148
|
+
IonHeader,
|
|
49149
|
+
IonToolbar,
|
|
49150
|
+
IonButtons,
|
|
49151
|
+
IonButton,
|
|
49152
|
+
IonContent,
|
|
49153
|
+
IonTextarea,
|
|
49154
|
+
IonSelect,
|
|
49155
|
+
IonSelectOption,
|
|
49156
|
+
IonCheckbox,
|
|
49157
|
+
IonSpinner,
|
|
49158
|
+
DisplayComponent,
|
|
49159
|
+
TextComponent,
|
|
49160
|
+
TitleComponent,
|
|
49161
|
+
], template: `
|
|
49162
|
+
<ion-header>
|
|
49163
|
+
<ion-toolbar>
|
|
49164
|
+
<ion-buttons slot="end">
|
|
49165
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
49166
|
+
<strong>{{ t('close') }}</strong>
|
|
49167
|
+
</ion-button>
|
|
49168
|
+
</ion-buttons>
|
|
49169
|
+
</ion-toolbar>
|
|
49170
|
+
</ion-header>
|
|
49171
|
+
|
|
49172
|
+
<ion-content class="ion-padding">
|
|
49173
|
+
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
49174
|
+
|
|
49175
|
+
@if (!results()) {
|
|
49176
|
+
<val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
|
|
49177
|
+
|
|
49178
|
+
@if (rolesHint()) {
|
|
49179
|
+
<val-text [props]="{ content: rolesHint(), size: 'small', color: 'medium', bold: false }" />
|
|
49180
|
+
}
|
|
49181
|
+
|
|
49182
|
+
<ion-textarea
|
|
49183
|
+
class="import-csv"
|
|
49184
|
+
[autoGrow]="true"
|
|
49185
|
+
[rows]="6"
|
|
49186
|
+
[placeholder]="t('csvPlaceholder')"
|
|
49187
|
+
[(ngModel)]="csv"
|
|
49188
|
+
/>
|
|
49189
|
+
|
|
49190
|
+
<ion-select
|
|
49191
|
+
class="import-conflict"
|
|
49192
|
+
label="{{ t('onConflictLabel') }}"
|
|
49193
|
+
labelPlacement="stacked"
|
|
49194
|
+
interface="popover"
|
|
49195
|
+
[(ngModel)]="onConflict"
|
|
49196
|
+
>
|
|
49197
|
+
<ion-select-option value="assignRole">{{ t('conflictAssign') }}</ion-select-option>
|
|
49198
|
+
<ion-select-option value="skip">{{ t('conflictSkip') }}</ion-select-option>
|
|
49199
|
+
<ion-select-option value="error">{{ t('conflictError') }}</ion-select-option>
|
|
49200
|
+
</ion-select>
|
|
49201
|
+
|
|
49202
|
+
<ion-checkbox class="import-activation" labelPlacement="end" [(ngModel)]="sendActivation">
|
|
49203
|
+
{{ t('sendActivation') }}
|
|
49204
|
+
</ion-checkbox>
|
|
49205
|
+
|
|
49206
|
+
@if (errorMsg()) {
|
|
49207
|
+
<p class="import-error">{{ errorMsg() }}</p>
|
|
49208
|
+
}
|
|
49209
|
+
|
|
49210
|
+
<div class="import-actions">
|
|
49211
|
+
<ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
|
|
49212
|
+
@if (submitting()) {
|
|
49213
|
+
<ion-spinner name="dots" />
|
|
49214
|
+
{{ t('submitting') }}
|
|
49215
|
+
} @else {
|
|
49216
|
+
{{ t('submit') }}
|
|
49217
|
+
}
|
|
49218
|
+
</ion-button>
|
|
49219
|
+
</div>
|
|
49220
|
+
} @else {
|
|
49221
|
+
<!-- Resultados -->
|
|
49222
|
+
<val-title [props]="{ content: summaryLabel(), size: 'large', color: '', bold: true }" />
|
|
49223
|
+
<div class="import-results">
|
|
49224
|
+
@for (r of results()!.results; track r.email) {
|
|
49225
|
+
<div class="import-row import-row--{{ r.status }}">
|
|
49226
|
+
<span class="import-row__email">{{ r.email }}</span>
|
|
49227
|
+
<span class="import-row__status">{{ statusLabel(r) }}</span>
|
|
49228
|
+
@if (r.reason) {
|
|
49229
|
+
<span class="import-row__reason">{{ r.reason }}</span>
|
|
49230
|
+
}
|
|
49231
|
+
</div>
|
|
49232
|
+
}
|
|
49233
|
+
</div>
|
|
49234
|
+
<div class="import-actions">
|
|
49235
|
+
<ion-button expand="block" fill="outline" color="dark" (click)="reset()">
|
|
49236
|
+
{{ t('again') }}
|
|
49237
|
+
</ion-button>
|
|
49238
|
+
</div>
|
|
49239
|
+
}
|
|
49240
|
+
</ion-content>
|
|
49241
|
+
`, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.import-csv{--background: var(--ion-color-light);border-radius:10px;margin-bottom:16px;font-family:monospace;font-size:13px}.import-conflict{margin-bottom:12px}.import-activation{display:block;margin-bottom:16px;font-size:14px}.import-error{margin:0 0 12px;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.import-actions{margin-top:8px}.import-results{display:flex;flex-direction:column;gap:6px;margin:12px 0}.import-row{display:grid;grid-template-columns:1fr auto;grid-row-gap:2px;align-items:center;padding:10px 12px;border-radius:8px;border-left:3px solid var(--ion-color-medium);background:var(--ion-color-light);font-size:14px}.import-row--created{border-left-color:var(--ion-color-success, #2dd36f)}.import-row--assigned{border-left-color:var(--ion-color-primary, #7026df)}.import-row--skipped{border-left-color:var(--ion-color-medium, #92949c)}.import-row--error{border-left-color:var(--ion-color-danger, #c0392b)}.import-row__email{font-weight:600;color:var(--ion-color-dark)}.import-row__status{text-align:right;color:var(--ion-color-medium-shade, #6b6e76)}.import-row__reason{grid-column:1 / -1;font-size:12px;color:var(--ion-color-danger, #c0392b)}\n"] }]
|
|
49242
|
+
}], ctorParameters: () => [], propDecorators: { orgId: [{
|
|
49243
|
+
type: Input
|
|
49244
|
+
}], availableRoles: [{
|
|
49245
|
+
type: Input
|
|
49246
|
+
}], onSuccess: [{
|
|
49247
|
+
type: Input
|
|
49248
|
+
}], _modalRef: [{
|
|
49249
|
+
type: Input
|
|
49250
|
+
}], i18nNamespace: [{
|
|
49251
|
+
type: Input
|
|
49252
|
+
}] } });
|
|
49253
|
+
|
|
49254
|
+
/**
|
|
49255
|
+
* Cliente HTTP de Client API Keys org-scopeadas (ADR-023). La org activa se
|
|
49256
|
+
* resuelve en backend desde el JWT, así que el path no la incluye.
|
|
49257
|
+
*/
|
|
49258
|
+
class ApiKeyService {
|
|
49259
|
+
constructor(config, http) {
|
|
49260
|
+
this.config = config;
|
|
49261
|
+
this.http = http;
|
|
49262
|
+
}
|
|
49263
|
+
get baseUrl() {
|
|
49264
|
+
return `${this.config.apiUrl}/org/api-keys`;
|
|
49265
|
+
}
|
|
49266
|
+
/** Lista las API keys de la org activa. */
|
|
49267
|
+
list() {
|
|
49268
|
+
return this.http.get(`${this.baseUrl}/`).pipe(map$1(r => r.keys ?? []));
|
|
49269
|
+
}
|
|
49270
|
+
/** Crea una API key. La respuesta incluye el secreto (se muestra UNA vez). */
|
|
49271
|
+
create(req) {
|
|
49272
|
+
return this.http.post(`${this.baseUrl}/`, req);
|
|
49273
|
+
}
|
|
49274
|
+
/** Revoca (elimina) una API key por id. */
|
|
49275
|
+
revoke(keyId) {
|
|
49276
|
+
return this.http.delete(`${this.baseUrl}/${keyId}`);
|
|
49277
|
+
}
|
|
49278
|
+
/** Catálogo de permisos asignables a una key. */
|
|
49279
|
+
availablePermissions() {
|
|
49280
|
+
return this.http
|
|
49281
|
+
.get(`${this.baseUrl}/permissions`)
|
|
49282
|
+
.pipe(map$1(r => r.permissions ?? []));
|
|
49283
|
+
}
|
|
49284
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeyService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$3.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
49285
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeyService, providedIn: 'root' }); }
|
|
49286
|
+
}
|
|
49287
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeyService, decorators: [{
|
|
49288
|
+
type: Injectable,
|
|
49289
|
+
args: [{ providedIn: 'root' }]
|
|
49290
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
49291
|
+
type: Inject,
|
|
49292
|
+
args: [VALTECH_AUTH_CONFIG]
|
|
49293
|
+
}] }, { type: i1$3.HttpClient }] });
|
|
49294
|
+
|
|
49295
|
+
/**
|
|
49296
|
+
* Defaults i18n (es/en) embebidos en `val-api-keys-modal` (ADR-023).
|
|
49297
|
+
* Auto-registrados si el consumer no proveyó el namespace `Settings.ApiKeysModal`.
|
|
49298
|
+
*/
|
|
49299
|
+
const API_KEYS_MODAL_I18N = {
|
|
49300
|
+
es: {
|
|
49301
|
+
title: 'Client API Keys',
|
|
49302
|
+
hint: 'Llaves M2M org-scopeadas para integraciones (p. ej. importar miembros).',
|
|
49303
|
+
empty: 'Aún no hay API keys en esta organización.',
|
|
49304
|
+
newKey: 'Nueva API key',
|
|
49305
|
+
nameLabel: 'Nombre',
|
|
49306
|
+
namePlaceholder: 'Ej: Integración RRHH',
|
|
49307
|
+
permissionsLabel: 'Permisos',
|
|
49308
|
+
expiresLabel: 'Expira en (días, opcional)',
|
|
49309
|
+
create: 'Crear key',
|
|
49310
|
+
creating: 'Creando...',
|
|
49311
|
+
cancel: 'Cancelar',
|
|
49312
|
+
revoke: 'Revocar',
|
|
49313
|
+
revokeConfirm: '¿Revocar esta API key? Las integraciones que la usen dejarán de funcionar.',
|
|
49314
|
+
secretTitle: 'Copia tu API key ahora',
|
|
49315
|
+
secretWarning: 'Este secreto se muestra una sola vez. Guárdalo en un lugar seguro.',
|
|
49316
|
+
copy: 'Copiar',
|
|
49317
|
+
copied: 'Copiada',
|
|
49318
|
+
done: 'Listo',
|
|
49319
|
+
statusActive: 'activa',
|
|
49320
|
+
lastUsed: 'Último uso',
|
|
49321
|
+
never: 'nunca',
|
|
49322
|
+
permsCount: 'permisos',
|
|
49323
|
+
noName: 'El nombre es obligatorio.',
|
|
49324
|
+
noPerms: 'Selecciona al menos un permiso.',
|
|
49325
|
+
close: 'Cerrar',
|
|
49326
|
+
genericError: 'No se pudo completar la operación. Intenta de nuevo.',
|
|
49327
|
+
},
|
|
49328
|
+
en: {
|
|
49329
|
+
title: 'Client API Keys',
|
|
49330
|
+
hint: 'Org-scoped M2M keys for integrations (e.g. member import).',
|
|
49331
|
+
empty: 'No API keys in this organization yet.',
|
|
49332
|
+
newKey: 'New API key',
|
|
49333
|
+
nameLabel: 'Name',
|
|
49334
|
+
namePlaceholder: 'E.g: HR integration',
|
|
49335
|
+
permissionsLabel: 'Permissions',
|
|
49336
|
+
expiresLabel: 'Expires in (days, optional)',
|
|
49337
|
+
create: 'Create key',
|
|
49338
|
+
creating: 'Creating...',
|
|
49339
|
+
cancel: 'Cancel',
|
|
49340
|
+
revoke: 'Revoke',
|
|
49341
|
+
revokeConfirm: 'Revoke this API key? Integrations using it will stop working.',
|
|
49342
|
+
secretTitle: 'Copy your API key now',
|
|
49343
|
+
secretWarning: 'This secret is shown only once. Store it somewhere safe.',
|
|
49344
|
+
copy: 'Copy',
|
|
49345
|
+
copied: 'Copied',
|
|
49346
|
+
done: 'Done',
|
|
49347
|
+
statusActive: 'active',
|
|
49348
|
+
lastUsed: 'Last used',
|
|
49349
|
+
never: 'never',
|
|
49350
|
+
permsCount: 'permissions',
|
|
49351
|
+
noName: 'Name is required.',
|
|
49352
|
+
noPerms: 'Select at least one permission.',
|
|
49353
|
+
close: 'Close',
|
|
49354
|
+
genericError: "Couldn't complete the operation. Try again.",
|
|
49355
|
+
},
|
|
49356
|
+
};
|
|
49357
|
+
|
|
49358
|
+
const DEFAULT_NAMESPACE$5 = 'Settings.ApiKeysModal';
|
|
49359
|
+
/**
|
|
49360
|
+
* `val-api-keys-modal` — gestión de Client API Keys org-scopeadas (ADR-023 fase 6).
|
|
49361
|
+
*
|
|
49362
|
+
* Lista las keys de la org, permite crear (mostrando el secreto UNA vez con copia)
|
|
49363
|
+
* y revocar. Pensado para habilitar integraciones M2M del import de miembros, pero
|
|
49364
|
+
* sirve a cualquier permiso del catálogo. Header canónico (Regla #5).
|
|
49365
|
+
*/
|
|
49366
|
+
class ApiKeysModalComponent {
|
|
49367
|
+
constructor() {
|
|
49368
|
+
this.i18n = inject(I18nService);
|
|
49369
|
+
this.apiKeys = inject(ApiKeyService);
|
|
49370
|
+
/** Namespace i18n. */
|
|
49371
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$5;
|
|
49372
|
+
this.keys = signal([]);
|
|
49373
|
+
this.availablePermissions = signal([]);
|
|
49374
|
+
this.loading = signal(true);
|
|
49375
|
+
this.showCreate = signal(false);
|
|
49376
|
+
this.submitting = signal(false);
|
|
49377
|
+
this.errorMsg = signal('');
|
|
49378
|
+
this.secret = signal(null);
|
|
49379
|
+
this.copied = signal(false);
|
|
49380
|
+
this.name = '';
|
|
49381
|
+
this.expiresInDays = null;
|
|
49382
|
+
this.selectedPerms = signal(new Set());
|
|
49383
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
|
|
49384
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$5, API_KEYS_MODAL_I18N);
|
|
49385
|
+
}
|
|
49386
|
+
}
|
|
49387
|
+
ngOnInit() {
|
|
49388
|
+
if (!this.i18n.hasNamespace(this.i18nNamespace)) {
|
|
49389
|
+
this.i18n.registerContent(this.i18nNamespace, API_KEYS_MODAL_I18N);
|
|
49390
|
+
}
|
|
49391
|
+
this.refresh();
|
|
49392
|
+
this.apiKeys.availablePermissions().subscribe({
|
|
49393
|
+
next: perms => this.availablePermissions.set(perms),
|
|
49394
|
+
error: () => { },
|
|
49395
|
+
});
|
|
49396
|
+
}
|
|
49397
|
+
refresh() {
|
|
49398
|
+
this.loading.set(true);
|
|
49399
|
+
this.apiKeys.list().subscribe({
|
|
49400
|
+
next: keys => {
|
|
49401
|
+
this.keys.set(keys);
|
|
49402
|
+
this.loading.set(false);
|
|
49403
|
+
},
|
|
49404
|
+
error: () => this.loading.set(false),
|
|
49405
|
+
});
|
|
49406
|
+
}
|
|
49407
|
+
openCreate() {
|
|
49408
|
+
this.errorMsg.set('');
|
|
49409
|
+
this.name = '';
|
|
49410
|
+
this.expiresInDays = null;
|
|
49411
|
+
this.selectedPerms.set(new Set());
|
|
49412
|
+
this.showCreate.set(true);
|
|
49413
|
+
}
|
|
49414
|
+
togglePerm(perm, ev) {
|
|
49415
|
+
const checked = ev.detail?.checked ?? false;
|
|
49416
|
+
const next = new Set(this.selectedPerms());
|
|
49417
|
+
if (checked) {
|
|
49418
|
+
next.add(perm);
|
|
49419
|
+
}
|
|
49420
|
+
else {
|
|
49421
|
+
next.delete(perm);
|
|
49422
|
+
}
|
|
49423
|
+
this.selectedPerms.set(next);
|
|
49424
|
+
}
|
|
49425
|
+
submit() {
|
|
49426
|
+
this.errorMsg.set('');
|
|
49427
|
+
if (!this.name.trim()) {
|
|
49428
|
+
this.errorMsg.set(this.t('noName'));
|
|
49429
|
+
return;
|
|
49430
|
+
}
|
|
49431
|
+
const permissions = Array.from(this.selectedPerms());
|
|
49432
|
+
if (permissions.length === 0) {
|
|
49433
|
+
this.errorMsg.set(this.t('noPerms'));
|
|
49434
|
+
return;
|
|
49435
|
+
}
|
|
49436
|
+
this.submitting.set(true);
|
|
49437
|
+
this.apiKeys
|
|
49438
|
+
.create({
|
|
49439
|
+
name: this.name.trim(),
|
|
49440
|
+
permissions,
|
|
49441
|
+
expiresInDays: this.expiresInDays && this.expiresInDays > 0 ? this.expiresInDays : undefined,
|
|
49442
|
+
})
|
|
49443
|
+
.subscribe({
|
|
49444
|
+
next: res => {
|
|
49445
|
+
this.submitting.set(false);
|
|
49446
|
+
this.showCreate.set(false);
|
|
49447
|
+
this.secret.set(res.key);
|
|
49448
|
+
},
|
|
49449
|
+
error: err => {
|
|
49450
|
+
this.submitting.set(false);
|
|
49451
|
+
this.errorMsg.set(err?.error?.message ?? this.t('genericError'));
|
|
49452
|
+
},
|
|
49453
|
+
});
|
|
49454
|
+
}
|
|
49455
|
+
revoke(key) {
|
|
49456
|
+
if (!confirm(this.t('revokeConfirm')))
|
|
49457
|
+
return;
|
|
49458
|
+
this.apiKeys.revoke(key.keyId).subscribe({
|
|
49459
|
+
next: () => this.refresh(),
|
|
49460
|
+
error: err => this.errorMsg.set(err?.error?.message ?? this.t('genericError')),
|
|
49461
|
+
});
|
|
49462
|
+
}
|
|
49463
|
+
async copy(secret) {
|
|
49464
|
+
try {
|
|
49465
|
+
await navigator.clipboard.writeText(secret);
|
|
49466
|
+
this.copied.set(true);
|
|
49467
|
+
}
|
|
49468
|
+
catch {
|
|
49469
|
+
// clipboard no disponible — el user puede seleccionar el texto manualmente
|
|
49470
|
+
}
|
|
49471
|
+
}
|
|
49472
|
+
finishSecret() {
|
|
49473
|
+
this.secret.set(null);
|
|
49474
|
+
this.copied.set(false);
|
|
49475
|
+
this.refresh();
|
|
49476
|
+
}
|
|
49477
|
+
dismiss() {
|
|
49478
|
+
this._modalRef?.dismiss(null, 'cancel');
|
|
49479
|
+
}
|
|
49480
|
+
t(key) {
|
|
49481
|
+
return this.i18n.t(key, this.i18nNamespace);
|
|
49482
|
+
}
|
|
49483
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeysModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
49484
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ApiKeysModalComponent, isStandalone: true, selector: "val-api-keys-modal", inputs: { _modalRef: "_modalRef", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
|
|
49485
|
+
<ion-header>
|
|
49486
|
+
<ion-toolbar>
|
|
49487
|
+
<ion-buttons slot="end">
|
|
49488
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
49489
|
+
<strong>{{ t('close') }}</strong>
|
|
49490
|
+
</ion-button>
|
|
49491
|
+
</ion-buttons>
|
|
49492
|
+
</ion-toolbar>
|
|
49493
|
+
</ion-header>
|
|
49494
|
+
|
|
49495
|
+
<ion-content class="ion-padding">
|
|
49496
|
+
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
49497
|
+
<val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
|
|
49498
|
+
|
|
49499
|
+
@if (secret(); as s) {
|
|
49500
|
+
<!-- Secreto recién creado: mostrar UNA vez -->
|
|
49501
|
+
<div class="apikey-secret">
|
|
49502
|
+
<val-title [props]="{ content: t('secretTitle'), size: 'large', color: '', bold: true }" />
|
|
49503
|
+
<p class="apikey-secret__warn">{{ t('secretWarning') }}</p>
|
|
49504
|
+
<div class="apikey-secret__box">
|
|
49505
|
+
<code>{{ s.secret }}</code>
|
|
49506
|
+
</div>
|
|
49507
|
+
<div class="apikey-actions">
|
|
49508
|
+
<ion-button expand="block" color="dark" (click)="copy(s.secret)">
|
|
49509
|
+
{{ copied() ? t('copied') : t('copy') }}
|
|
49510
|
+
</ion-button>
|
|
49511
|
+
<ion-button expand="block" fill="outline" color="dark" (click)="finishSecret()">
|
|
49512
|
+
{{ t('done') }}
|
|
49513
|
+
</ion-button>
|
|
49514
|
+
</div>
|
|
49515
|
+
</div>
|
|
49516
|
+
} @else {
|
|
49517
|
+
@if (loading()) {
|
|
49518
|
+
<ion-spinner name="dots" />
|
|
49519
|
+
} @else {
|
|
49520
|
+
<!-- Lista -->
|
|
49521
|
+
@if (keys().length === 0) {
|
|
49522
|
+
<val-text [props]="{ content: t('empty'), size: 'medium', color: 'medium', bold: false }" />
|
|
49523
|
+
} @else {
|
|
49524
|
+
<div class="apikey-list">
|
|
49525
|
+
@for (k of keys(); track k.keyId) {
|
|
49526
|
+
<div class="apikey-row">
|
|
49527
|
+
<div class="apikey-row__main">
|
|
49528
|
+
<span class="apikey-row__name">{{ k.name }}</span>
|
|
49529
|
+
<span class="apikey-row__meta">
|
|
49530
|
+
{{ k.permissions.length }} {{ t('permsCount') }} · {{ t('lastUsed') }}:
|
|
49531
|
+
{{ k.lastUsedAt || t('never') }}
|
|
49532
|
+
</span>
|
|
49533
|
+
</div>
|
|
49534
|
+
<ion-button fill="clear" color="danger" size="small" (click)="revoke(k)">
|
|
49535
|
+
{{ t('revoke') }}
|
|
49536
|
+
</ion-button>
|
|
49537
|
+
</div>
|
|
49538
|
+
}
|
|
49539
|
+
</div>
|
|
49540
|
+
}
|
|
49541
|
+
|
|
49542
|
+
@if (errorMsg()) {
|
|
49543
|
+
<p class="apikey-error">{{ errorMsg() }}</p>
|
|
49544
|
+
}
|
|
49545
|
+
|
|
49546
|
+
<!-- Crear -->
|
|
49547
|
+
@if (showCreate()) {
|
|
49548
|
+
<div class="apikey-create">
|
|
49549
|
+
<ion-input
|
|
49550
|
+
label="{{ t('nameLabel') }}"
|
|
49551
|
+
labelPlacement="stacked"
|
|
49552
|
+
[placeholder]="t('namePlaceholder')"
|
|
49553
|
+
[(ngModel)]="name"
|
|
49554
|
+
/>
|
|
49555
|
+
<p class="apikey-create__label">{{ t('permissionsLabel') }}</p>
|
|
49556
|
+
<div class="apikey-perms">
|
|
49557
|
+
@for (p of availablePermissions(); track p) {
|
|
49558
|
+
<ion-checkbox
|
|
49559
|
+
labelPlacement="end"
|
|
49560
|
+
[checked]="selectedPerms().has(p)"
|
|
49561
|
+
(ionChange)="togglePerm(p, $event)"
|
|
49562
|
+
>
|
|
49563
|
+
{{ p }}
|
|
49564
|
+
</ion-checkbox>
|
|
49565
|
+
}
|
|
49566
|
+
</div>
|
|
49567
|
+
<ion-input
|
|
49568
|
+
label="{{ t('expiresLabel') }}"
|
|
49569
|
+
labelPlacement="stacked"
|
|
49570
|
+
type="number"
|
|
49571
|
+
[(ngModel)]="expiresInDays"
|
|
49572
|
+
/>
|
|
49573
|
+
<div class="apikey-actions">
|
|
49574
|
+
<ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
|
|
49575
|
+
@if (submitting()) {
|
|
49576
|
+
<ion-spinner name="dots" /> {{ t('creating') }}
|
|
49577
|
+
} @else {
|
|
49578
|
+
{{ t('create') }}
|
|
49579
|
+
}
|
|
49580
|
+
</ion-button>
|
|
49581
|
+
<ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
|
|
49582
|
+
{{ t('cancel') }}
|
|
49583
|
+
</ion-button>
|
|
49584
|
+
</div>
|
|
49585
|
+
</div>
|
|
49586
|
+
} @else {
|
|
49587
|
+
<div class="apikey-actions">
|
|
49588
|
+
<ion-button expand="block" color="primary" (click)="openCreate()">
|
|
49589
|
+
{{ t('newKey') }}
|
|
49590
|
+
</ion-button>
|
|
49591
|
+
</div>
|
|
49592
|
+
}
|
|
49593
|
+
}
|
|
49594
|
+
}
|
|
49595
|
+
</ion-content>
|
|
49596
|
+
`, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.apikey-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.apikey-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:10px;border:1.5px solid var(--ion-color-light)}.apikey-row__main{display:flex;flex-direction:column}.apikey-row__name{font-weight:600;color:var(--ion-color-dark)}.apikey-row__meta{font-size:12px;color:var(--ion-color-medium-shade, #6b6e76)}.apikey-create{margin-top:8px;padding:16px;border-radius:12px;background:var(--ion-color-light)}.apikey-create__label{margin:12px 0 4px;font-size:13px;font-weight:600;color:var(--ion-color-dark)}.apikey-perms{display:flex;flex-direction:column;gap:6px;margin-bottom:12px;font-size:13px}.apikey-actions{margin-top:16px;display:flex;flex-direction:column;gap:8px}.apikey-error{margin:8px 0;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.apikey-secret__warn{font-size:.875rem;color:var(--ion-color-warning-shade, #b88600);margin:0 0 12px}.apikey-secret__box{padding:12px;border-radius:8px;background:var(--ion-color-dark);color:#fff;word-break:break-all;font-family:monospace;font-size:13px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
|
|
49597
|
+
}
|
|
49598
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeysModalComponent, decorators: [{
|
|
49599
|
+
type: Component,
|
|
49600
|
+
args: [{ selector: 'val-api-keys-modal', standalone: true, imports: [
|
|
49601
|
+
FormsModule,
|
|
49602
|
+
IonHeader,
|
|
49603
|
+
IonToolbar,
|
|
49604
|
+
IonButtons,
|
|
49605
|
+
IonButton,
|
|
49606
|
+
IonContent,
|
|
49607
|
+
IonInput,
|
|
49608
|
+
IonCheckbox,
|
|
49609
|
+
IonSpinner,
|
|
49610
|
+
DisplayComponent,
|
|
49611
|
+
TextComponent,
|
|
49612
|
+
TitleComponent,
|
|
49613
|
+
], template: `
|
|
49614
|
+
<ion-header>
|
|
49615
|
+
<ion-toolbar>
|
|
49616
|
+
<ion-buttons slot="end">
|
|
49617
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
49618
|
+
<strong>{{ t('close') }}</strong>
|
|
49619
|
+
</ion-button>
|
|
49620
|
+
</ion-buttons>
|
|
49621
|
+
</ion-toolbar>
|
|
49622
|
+
</ion-header>
|
|
49623
|
+
|
|
49624
|
+
<ion-content class="ion-padding">
|
|
49625
|
+
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
49626
|
+
<val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
|
|
49627
|
+
|
|
49628
|
+
@if (secret(); as s) {
|
|
49629
|
+
<!-- Secreto recién creado: mostrar UNA vez -->
|
|
49630
|
+
<div class="apikey-secret">
|
|
49631
|
+
<val-title [props]="{ content: t('secretTitle'), size: 'large', color: '', bold: true }" />
|
|
49632
|
+
<p class="apikey-secret__warn">{{ t('secretWarning') }}</p>
|
|
49633
|
+
<div class="apikey-secret__box">
|
|
49634
|
+
<code>{{ s.secret }}</code>
|
|
49635
|
+
</div>
|
|
49636
|
+
<div class="apikey-actions">
|
|
49637
|
+
<ion-button expand="block" color="dark" (click)="copy(s.secret)">
|
|
49638
|
+
{{ copied() ? t('copied') : t('copy') }}
|
|
49639
|
+
</ion-button>
|
|
49640
|
+
<ion-button expand="block" fill="outline" color="dark" (click)="finishSecret()">
|
|
49641
|
+
{{ t('done') }}
|
|
49642
|
+
</ion-button>
|
|
49643
|
+
</div>
|
|
49644
|
+
</div>
|
|
49645
|
+
} @else {
|
|
49646
|
+
@if (loading()) {
|
|
49647
|
+
<ion-spinner name="dots" />
|
|
49648
|
+
} @else {
|
|
49649
|
+
<!-- Lista -->
|
|
49650
|
+
@if (keys().length === 0) {
|
|
49651
|
+
<val-text [props]="{ content: t('empty'), size: 'medium', color: 'medium', bold: false }" />
|
|
49652
|
+
} @else {
|
|
49653
|
+
<div class="apikey-list">
|
|
49654
|
+
@for (k of keys(); track k.keyId) {
|
|
49655
|
+
<div class="apikey-row">
|
|
49656
|
+
<div class="apikey-row__main">
|
|
49657
|
+
<span class="apikey-row__name">{{ k.name }}</span>
|
|
49658
|
+
<span class="apikey-row__meta">
|
|
49659
|
+
{{ k.permissions.length }} {{ t('permsCount') }} · {{ t('lastUsed') }}:
|
|
49660
|
+
{{ k.lastUsedAt || t('never') }}
|
|
49661
|
+
</span>
|
|
49662
|
+
</div>
|
|
49663
|
+
<ion-button fill="clear" color="danger" size="small" (click)="revoke(k)">
|
|
49664
|
+
{{ t('revoke') }}
|
|
49665
|
+
</ion-button>
|
|
49666
|
+
</div>
|
|
49667
|
+
}
|
|
49668
|
+
</div>
|
|
49669
|
+
}
|
|
49670
|
+
|
|
49671
|
+
@if (errorMsg()) {
|
|
49672
|
+
<p class="apikey-error">{{ errorMsg() }}</p>
|
|
49673
|
+
}
|
|
49674
|
+
|
|
49675
|
+
<!-- Crear -->
|
|
49676
|
+
@if (showCreate()) {
|
|
49677
|
+
<div class="apikey-create">
|
|
49678
|
+
<ion-input
|
|
49679
|
+
label="{{ t('nameLabel') }}"
|
|
49680
|
+
labelPlacement="stacked"
|
|
49681
|
+
[placeholder]="t('namePlaceholder')"
|
|
49682
|
+
[(ngModel)]="name"
|
|
49683
|
+
/>
|
|
49684
|
+
<p class="apikey-create__label">{{ t('permissionsLabel') }}</p>
|
|
49685
|
+
<div class="apikey-perms">
|
|
49686
|
+
@for (p of availablePermissions(); track p) {
|
|
49687
|
+
<ion-checkbox
|
|
49688
|
+
labelPlacement="end"
|
|
49689
|
+
[checked]="selectedPerms().has(p)"
|
|
49690
|
+
(ionChange)="togglePerm(p, $event)"
|
|
49691
|
+
>
|
|
49692
|
+
{{ p }}
|
|
49693
|
+
</ion-checkbox>
|
|
49694
|
+
}
|
|
49695
|
+
</div>
|
|
49696
|
+
<ion-input
|
|
49697
|
+
label="{{ t('expiresLabel') }}"
|
|
49698
|
+
labelPlacement="stacked"
|
|
49699
|
+
type="number"
|
|
49700
|
+
[(ngModel)]="expiresInDays"
|
|
49701
|
+
/>
|
|
49702
|
+
<div class="apikey-actions">
|
|
49703
|
+
<ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
|
|
49704
|
+
@if (submitting()) {
|
|
49705
|
+
<ion-spinner name="dots" /> {{ t('creating') }}
|
|
49706
|
+
} @else {
|
|
49707
|
+
{{ t('create') }}
|
|
49708
|
+
}
|
|
49709
|
+
</ion-button>
|
|
49710
|
+
<ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
|
|
49711
|
+
{{ t('cancel') }}
|
|
49712
|
+
</ion-button>
|
|
49713
|
+
</div>
|
|
49714
|
+
</div>
|
|
49715
|
+
} @else {
|
|
49716
|
+
<div class="apikey-actions">
|
|
49717
|
+
<ion-button expand="block" color="primary" (click)="openCreate()">
|
|
49718
|
+
{{ t('newKey') }}
|
|
49719
|
+
</ion-button>
|
|
49720
|
+
</div>
|
|
49721
|
+
}
|
|
49722
|
+
}
|
|
49723
|
+
}
|
|
49724
|
+
</ion-content>
|
|
49725
|
+
`, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.apikey-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.apikey-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:10px;border:1.5px solid var(--ion-color-light)}.apikey-row__main{display:flex;flex-direction:column}.apikey-row__name{font-weight:600;color:var(--ion-color-dark)}.apikey-row__meta{font-size:12px;color:var(--ion-color-medium-shade, #6b6e76)}.apikey-create{margin-top:8px;padding:16px;border-radius:12px;background:var(--ion-color-light)}.apikey-create__label{margin:12px 0 4px;font-size:13px;font-weight:600;color:var(--ion-color-dark)}.apikey-perms{display:flex;flex-direction:column;gap:6px;margin-bottom:12px;font-size:13px}.apikey-actions{margin-top:16px;display:flex;flex-direction:column;gap:8px}.apikey-error{margin:8px 0;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.apikey-secret__warn{font-size:.875rem;color:var(--ion-color-warning-shade, #b88600);margin:0 0 12px}.apikey-secret__box{padding:12px;border-radius:8px;background:var(--ion-color-dark);color:#fff;word-break:break-all;font-family:monospace;font-size:13px}\n"] }]
|
|
49726
|
+
}], ctorParameters: () => [], propDecorators: { _modalRef: [{
|
|
49727
|
+
type: Input
|
|
49728
|
+
}], i18nNamespace: [{
|
|
49729
|
+
type: Input
|
|
49730
|
+
}] } });
|
|
49731
|
+
|
|
48849
49732
|
/**
|
|
48850
49733
|
* Defaults i18n (es/en) embebidos en `val-organization-view`. Auto-registrados en
|
|
48851
49734
|
* el constructor del componente si el consumer no proveyó el namespace
|
|
@@ -48901,6 +49784,16 @@ const ORGANIZATION_VIEW_I18N = {
|
|
|
48901
49784
|
inviteCtaTitle: '¿Deseas agregar a alguien?',
|
|
48902
49785
|
inviteCtaHint: 'Invita a usuarios a unirse a tu organización.',
|
|
48903
49786
|
inviteOpen: 'Invitar',
|
|
49787
|
+
importOpen: 'Importar',
|
|
49788
|
+
apiKeysOpen: 'Gestionar',
|
|
49789
|
+
apiKeysSectionTitle: 'Integraciones (API Keys)',
|
|
49790
|
+
apiKeysSectionHint: 'También puedes operar tu organización vía API: crea una Client API Key org-scopeada para integraciones máquina-a-máquina (por ejemplo, dar de alta miembros en masa desde tu propio sistema).',
|
|
49791
|
+
apiKeysMoreInfo: 'Más información',
|
|
49792
|
+
orgsMoreInfo: 'Más información',
|
|
49793
|
+
orgsInfoTitle: '¿Qué son las organizaciones?',
|
|
49794
|
+
orgsInfoSubtitle: 'Todo lo que necesitas saber',
|
|
49795
|
+
orgsInfoClose: 'Cerrar',
|
|
49796
|
+
orgsInfoContent: 'Las organizaciones te permiten separar tus proyectos y datos por empresa, equipo o contexto. Puedes pertenecer a varias organizaciones y cambiar entre ellas en cualquier momento.\n\nCada organización tiene sus propios miembros, roles y permisos. La organización activa determina qué datos ves y qué acciones puedes realizar.\n\nPuedes crear una organización nueva o unirte a una existente mediante invitación.',
|
|
48904
49797
|
viewMemberCta: 'Ver miembro',
|
|
48905
49798
|
roleViewer: 'Visor',
|
|
48906
49799
|
roleEditor: 'Editor',
|
|
@@ -48970,6 +49863,16 @@ const ORGANIZATION_VIEW_I18N = {
|
|
|
48970
49863
|
inviteCtaTitle: 'Want to add someone?',
|
|
48971
49864
|
inviteCtaHint: 'Invite users to join your organization.',
|
|
48972
49865
|
inviteOpen: 'Invite',
|
|
49866
|
+
importOpen: 'Import',
|
|
49867
|
+
apiKeysOpen: 'Manage',
|
|
49868
|
+
apiKeysSectionTitle: 'Integrations (API Keys)',
|
|
49869
|
+
apiKeysSectionHint: 'You can also operate your organization via API: create an org-scoped Client API Key for machine-to-machine integrations (for example, bulk-provisioning members from your own system).',
|
|
49870
|
+
apiKeysMoreInfo: 'More information',
|
|
49871
|
+
orgsMoreInfo: 'More information',
|
|
49872
|
+
orgsInfoTitle: 'What are organizations?',
|
|
49873
|
+
orgsInfoSubtitle: 'Everything you need to know',
|
|
49874
|
+
orgsInfoClose: 'Close',
|
|
49875
|
+
orgsInfoContent: 'Organizations let you separate your projects and data by company, team, or context. You can belong to multiple organizations and switch between them at any time.\n\nEach organization has its own members, roles, and permissions. The active organization determines what data you see and what actions you can take.\n\nYou can create a new organization or join an existing one via invitation.',
|
|
48973
49876
|
viewMemberCta: 'View member',
|
|
48974
49877
|
roleViewer: 'Viewer',
|
|
48975
49878
|
roleEditor: 'Editor',
|
|
@@ -48994,7 +49897,7 @@ const ORGANIZATION_VIEW_I18N = {
|
|
|
48994
49897
|
},
|
|
48995
49898
|
};
|
|
48996
49899
|
|
|
48997
|
-
const DEFAULT_NAMESPACE$
|
|
49900
|
+
const DEFAULT_NAMESPACE$4 = 'Settings.Organization';
|
|
48998
49901
|
const DEFAULT_VIEW_PERMISSIONS_ROUTE = '/app/settings/permissions';
|
|
48999
49902
|
const DEFAULT_LEAVE_REDIRECT_ROUTE = '/app/settings/account';
|
|
49000
49903
|
/**
|
|
@@ -49051,7 +49954,7 @@ class OrganizationViewComponent {
|
|
|
49051
49954
|
showInvite: merged.showInvite ?? true,
|
|
49052
49955
|
showTransferOwnership: merged.showTransferOwnership ?? true,
|
|
49053
49956
|
showLeave: merged.showLeave ?? true,
|
|
49054
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
49957
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$4,
|
|
49055
49958
|
viewPermissionsRoute: merged.viewPermissionsRoute ?? DEFAULT_VIEW_PERMISSIONS_ROUTE,
|
|
49056
49959
|
leaveRedirectRoute: merged.leaveRedirectRoute ?? DEFAULT_LEAVE_REDIRECT_ROUTE,
|
|
49057
49960
|
onOrgEdited: merged.onOrgEdited,
|
|
@@ -49083,6 +49986,22 @@ class OrganizationViewComponent {
|
|
|
49083
49986
|
this.members = signal([]);
|
|
49084
49987
|
this.membersLoading = signal(false);
|
|
49085
49988
|
this.membersLoadError = signal(null);
|
|
49989
|
+
this.membersErrorState = computed(() => {
|
|
49990
|
+
this.i18n.lang();
|
|
49991
|
+
const err = this.membersLoadError();
|
|
49992
|
+
if (!err)
|
|
49993
|
+
return null;
|
|
49994
|
+
return createErrorStateProps(err, {
|
|
49995
|
+
title: { offline: this.tt('offlineTitle'), error: this.tt('errorTitle') },
|
|
49996
|
+
description: {
|
|
49997
|
+
offline: this.tt('offlineHint'),
|
|
49998
|
+
error: interpretError(err).message,
|
|
49999
|
+
},
|
|
50000
|
+
retryLabel: this.tt('retry'),
|
|
50001
|
+
onRetry: () => this.loadMembers(),
|
|
50002
|
+
retrying: this.membersLoading(),
|
|
50003
|
+
});
|
|
50004
|
+
});
|
|
49086
50005
|
this.membersNextToken = signal(null);
|
|
49087
50006
|
this.loadingMoreMembers = signal(false);
|
|
49088
50007
|
this.showAllMembers = signal(false);
|
|
@@ -49169,6 +50088,22 @@ class OrganizationViewComponent {
|
|
|
49169
50088
|
shape: 'round',
|
|
49170
50089
|
type: 'button',
|
|
49171
50090
|
}));
|
|
50091
|
+
this.openImportButtonProps = computed(() => ({
|
|
50092
|
+
text: this.tt('importOpen'),
|
|
50093
|
+
color: 'dark',
|
|
50094
|
+
fill: 'outline',
|
|
50095
|
+
size: 'default',
|
|
50096
|
+
shape: 'round',
|
|
50097
|
+
type: 'button',
|
|
50098
|
+
}));
|
|
50099
|
+
this.openApiKeysButtonProps = computed(() => ({
|
|
50100
|
+
text: this.tt('apiKeysOpen'),
|
|
50101
|
+
color: 'dark',
|
|
50102
|
+
fill: 'clear',
|
|
50103
|
+
size: 'default',
|
|
50104
|
+
shape: 'round',
|
|
50105
|
+
type: 'button',
|
|
50106
|
+
}));
|
|
49172
50107
|
/** Guarda para detectar el primer disparo del effect (evita doble carga). */
|
|
49173
50108
|
this.lastLoadedOrgId = null;
|
|
49174
50109
|
const ns = this.ns;
|
|
@@ -49269,6 +50204,59 @@ class OrganizationViewComponent {
|
|
|
49269
50204
|
backdropDismiss: true,
|
|
49270
50205
|
});
|
|
49271
50206
|
}
|
|
50207
|
+
onOpenImport() {
|
|
50208
|
+
void this.modalService.open({
|
|
50209
|
+
component: MemberImportModalComponent,
|
|
50210
|
+
componentProps: {
|
|
50211
|
+
orgId: this.activeOrgId(),
|
|
50212
|
+
availableRoles: this.availableRoles().map(r => r.name),
|
|
50213
|
+
onSuccess: () => {
|
|
50214
|
+
this.loadMembers();
|
|
50215
|
+
this.resolvedConfig().onMemberInvited?.();
|
|
50216
|
+
},
|
|
50217
|
+
},
|
|
50218
|
+
breakpoints: {
|
|
50219
|
+
breakpoints: [0, 0.92],
|
|
50220
|
+
initialBreakpoint: 0.92,
|
|
50221
|
+
showHandle: true,
|
|
50222
|
+
},
|
|
50223
|
+
backdropDismiss: true,
|
|
50224
|
+
});
|
|
50225
|
+
}
|
|
50226
|
+
onOpenApiKeys() {
|
|
50227
|
+
void this.modalService.open({
|
|
50228
|
+
component: ApiKeysModalComponent,
|
|
50229
|
+
breakpoints: {
|
|
50230
|
+
breakpoints: [0, 0.92],
|
|
50231
|
+
initialBreakpoint: 0.92,
|
|
50232
|
+
showHandle: true,
|
|
50233
|
+
},
|
|
50234
|
+
backdropDismiss: true,
|
|
50235
|
+
});
|
|
50236
|
+
}
|
|
50237
|
+
// "Más información" sobre organizaciones — mismo sheet explicativo que muestra
|
|
50238
|
+
// account-view sin org, pero disponible siempre (todo user tiene org por defecto).
|
|
50239
|
+
onOrgInfo() {
|
|
50240
|
+
void this.modalService.open({
|
|
50241
|
+
component: OrgInfoSheetComponent,
|
|
50242
|
+
componentProps: {
|
|
50243
|
+
title: this.tt('orgsInfoTitle'),
|
|
50244
|
+
subtitle: this.tt('orgsInfoSubtitle'),
|
|
50245
|
+
body: this.tt('orgsInfoContent'),
|
|
50246
|
+
closeLabel: this.tt('orgsInfoClose'),
|
|
50247
|
+
closeProps: {
|
|
50248
|
+
text: this.tt('orgsInfoClose'),
|
|
50249
|
+
fill: 'outline',
|
|
50250
|
+
color: 'dark',
|
|
50251
|
+
size: 'default',
|
|
50252
|
+
expand: 'block',
|
|
50253
|
+
type: 'button',
|
|
50254
|
+
},
|
|
50255
|
+
},
|
|
50256
|
+
breakpoints: { breakpoints: [0, 0.85], initialBreakpoint: 0.85, showHandle: true },
|
|
50257
|
+
backdropDismiss: true,
|
|
50258
|
+
});
|
|
50259
|
+
}
|
|
49272
50260
|
onViewPermissions() {
|
|
49273
50261
|
this.nav.navigateByUrl(this.resolvedConfig().viewPermissionsRoute);
|
|
49274
50262
|
}
|
|
@@ -49350,6 +50338,7 @@ class OrganizationViewComponent {
|
|
|
49350
50338
|
if (!orgId)
|
|
49351
50339
|
return Promise.resolve();
|
|
49352
50340
|
this.membersLoading.set(true);
|
|
50341
|
+
this.membersLoadError.set(null);
|
|
49353
50342
|
this.membersNextToken.set(null);
|
|
49354
50343
|
this.showAllMembers.set(false);
|
|
49355
50344
|
return new Promise(resolve => {
|
|
@@ -49513,6 +50502,12 @@ class OrganizationViewComponent {
|
|
|
49513
50502
|
}
|
|
49514
50503
|
</div>
|
|
49515
50504
|
|
|
50505
|
+
<!-- "Más información" sobre organizaciones — disponible siempre (todo
|
|
50506
|
+
user tiene org por defecto y no ve el empty-state de account). -->
|
|
50507
|
+
<button class="org-more-info-link" type="button" (click)="onOrgInfo()">
|
|
50508
|
+
{{ tt('orgsMoreInfo') }}
|
|
50509
|
+
</button>
|
|
50510
|
+
|
|
49516
50511
|
<div class="section-body">
|
|
49517
50512
|
@if (loading()) {
|
|
49518
50513
|
<val-skeleton-layout [props]="{ preset: 'detail', rows: 4 }" />
|
|
@@ -49560,6 +50555,8 @@ class OrganizationViewComponent {
|
|
|
49560
50555
|
<div class="section-body">
|
|
49561
50556
|
@if (membersLoading()) {
|
|
49562
50557
|
<val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
|
|
50558
|
+
} @else if (membersErrorState()) {
|
|
50559
|
+
<val-empty-state [props]="membersErrorState()!" />
|
|
49563
50560
|
} @else if (members().length === 0) {
|
|
49564
50561
|
<val-text
|
|
49565
50562
|
[props]="{
|
|
@@ -49652,10 +50649,25 @@ class OrganizationViewComponent {
|
|
|
49652
50649
|
/>
|
|
49653
50650
|
</div>
|
|
49654
50651
|
<val-button [props]="openInviteButtonProps()" (click)="onOpenInvite()" />
|
|
50652
|
+
<val-button [props]="openImportButtonProps()" (click)="onOpenImport()" />
|
|
49655
50653
|
</div>
|
|
49656
50654
|
</section>
|
|
49657
50655
|
}
|
|
49658
50656
|
|
|
50657
|
+
<!-- API Keys: sección propia. Integraciones M2M (import de miembros, etc.). -->
|
|
50658
|
+
<section *valHasPermission="'api-keys:read'" class="settings-section" data-testid="org-apikeys-section">
|
|
50659
|
+
<div class="invite-cta">
|
|
50660
|
+
<div class="invite-cta__text">
|
|
50661
|
+
<val-title [props]="{ size: 'medium', color: 'dark', bold: true, content: tt('apiKeysSectionTitle') }" />
|
|
50662
|
+
<val-text [props]="{ size: 'medium', color: 'dark', bold: false, content: tt('apiKeysSectionHint') }" />
|
|
50663
|
+
<button class="org-more-info-link" type="button" (click)="onOpenApiKeys()">
|
|
50664
|
+
{{ tt('apiKeysMoreInfo') }}
|
|
50665
|
+
</button>
|
|
50666
|
+
</div>
|
|
50667
|
+
<val-button [props]="openApiKeysButtonProps()" (click)="onOpenApiKeys()" />
|
|
50668
|
+
</div>
|
|
50669
|
+
</section>
|
|
50670
|
+
|
|
49659
50671
|
@if (resolvedConfig().showTransferOwnership && isOwner()) {
|
|
49660
50672
|
<!-- Transfer ownership: solo owner (gating RBAC interno) -->
|
|
49661
50673
|
<section class="settings-section" data-testid="org-transfer-section">
|
|
@@ -49746,7 +50758,7 @@ class OrganizationViewComponent {
|
|
|
49746
50758
|
</section>
|
|
49747
50759
|
}
|
|
49748
50760
|
</div>
|
|
49749
|
-
`, 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))}.section-header-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.section-body{display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.row-actions--gap{display:flex;gap:8px}.invite-cta{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}.invite-cta__text{display:flex;flex-direction:column;gap:4px;flex:1}.org-info-card{border-radius:14px;background:var(--ion-color-light, #f4f5f8);overflow:hidden}:host-context(body.dark) .org-info-card,:host-context(html.ion-palette-dark) .org-info-card,:host-context([data-theme=\"dark\"]) .org-info-card{background:#ffffff0d}.org-info-field{padding:12px 16px;display:flex;flex-direction:column;gap:4px;border-bottom:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-field--full{width:100%}.org-info-field:last-child{border-bottom:none}.org-info-row{display:flex}.org-info-row .org-info-field{flex:1}.org-info-row .org-info-field:first-child{border-right:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.org-info-value{font-size:.95rem;font-weight:500;color:var(--ion-color-dark)}.org-info-value--muted{color:var(--ion-color-medium);font-weight:400}.plan-badge{display:inline-block;font-size:.78rem;font-weight:700;padding:3px 10px;border-radius:20px;width:fit-content}.plan-badge--free{background:var(--ion-color-light-shade, #d7d8da);color:var(--ion-color-dark)}.plan-badge--pro{background:var(--ion-color-primary);color:#fff}.plan-badge--enterprise{background:#f5c542;color:#222}.members-list{display:flex;flex-direction:column;gap:8px}.members-show-more{background:none;border:none;color:var(--ion-color-primary);font-size:14px;font-weight:500;cursor:pointer;padding:8px 0}.rbac-debug{opacity:.7}.rbac-debug__body{display:flex;flex-direction:column;gap:6px;font-family:monospace;font-size:.78rem}.rbac-debug__row{display:flex;gap:8px;align-items:flex-start}.rbac-debug__label{color:var(--ion-color-medium);min-width:72px;flex-shrink:0}.rbac-debug__value{color:var(--ion-color-dark);word-break:break-all}.rbac-debug__value--perms{color:var(--ion-color-medium)}\n"], dependencies: [{ kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "directive", type: HasPermissionDirective, selector: "[valHasPermission]", inputs: ["valHasPermission"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", 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: MemberCardComponent, selector: "val-member-card", inputs: ["props"], outputs: ["onAction"] }] }); }
|
|
50761
|
+
`, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.org-more-info-link{background:none;border:none;padding:4px 0;margin-top:4px;font-size:13px;font-weight:600;color:var(--ion-color-primary, #7026df);cursor:pointer;text-align:left}.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))}.section-header-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.section-body{display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.row-actions--gap{display:flex;gap:8px}.invite-cta{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}.invite-cta__text{display:flex;flex-direction:column;gap:4px;flex:1}.org-info-card{border-radius:14px;background:var(--ion-color-light, #f4f5f8);overflow:hidden}:host-context(body.dark) .org-info-card,:host-context(html.ion-palette-dark) .org-info-card,:host-context([data-theme=\"dark\"]) .org-info-card{background:#ffffff0d}.org-info-field{padding:12px 16px;display:flex;flex-direction:column;gap:4px;border-bottom:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-field--full{width:100%}.org-info-field:last-child{border-bottom:none}.org-info-row{display:flex}.org-info-row .org-info-field{flex:1}.org-info-row .org-info-field:first-child{border-right:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.org-info-value{font-size:.95rem;font-weight:500;color:var(--ion-color-dark)}.org-info-value--muted{color:var(--ion-color-medium);font-weight:400}.plan-badge{display:inline-block;font-size:.78rem;font-weight:700;padding:3px 10px;border-radius:20px;width:fit-content}.plan-badge--free{background:var(--ion-color-light-shade, #d7d8da);color:var(--ion-color-dark)}.plan-badge--pro{background:var(--ion-color-primary);color:#fff}.plan-badge--enterprise{background:#f5c542;color:#222}.members-list{display:flex;flex-direction:column;gap:8px}.members-show-more{background:none;border:none;color:var(--ion-color-primary);font-size:14px;font-weight:500;cursor:pointer;padding:8px 0}.rbac-debug{opacity:.7}.rbac-debug__body{display:flex;flex-direction:column;gap:6px;font-family:monospace;font-size:.78rem}.rbac-debug__row{display:flex;gap:8px;align-items:flex-start}.rbac-debug__label{color:var(--ion-color-medium);min-width:72px;flex-shrink:0}.rbac-debug__value{color:var(--ion-color-dark);word-break:break-all}.rbac-debug__value--perms{color:var(--ion-color-medium)}\n"], dependencies: [{ kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "directive", type: HasPermissionDirective, selector: "[valHasPermission]", inputs: ["valHasPermission"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", 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: MemberCardComponent, selector: "val-member-card", inputs: ["props"], outputs: ["onAction"] }] }); }
|
|
49750
50762
|
}
|
|
49751
50763
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrganizationViewComponent, decorators: [{
|
|
49752
50764
|
type: Component,
|
|
@@ -49760,6 +50772,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
49760
50772
|
ButtonComponent,
|
|
49761
50773
|
MemberCardComponent,
|
|
49762
50774
|
TransferOwnershipModalComponent,
|
|
50775
|
+
MemberImportModalComponent,
|
|
50776
|
+
ApiKeysModalComponent,
|
|
50777
|
+
OrgInfoSheetComponent,
|
|
49763
50778
|
], template: `
|
|
49764
50779
|
<div class="page">
|
|
49765
50780
|
<header class="page-header">
|
|
@@ -49791,6 +50806,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
49791
50806
|
}
|
|
49792
50807
|
</div>
|
|
49793
50808
|
|
|
50809
|
+
<!-- "Más información" sobre organizaciones — disponible siempre (todo
|
|
50810
|
+
user tiene org por defecto y no ve el empty-state de account). -->
|
|
50811
|
+
<button class="org-more-info-link" type="button" (click)="onOrgInfo()">
|
|
50812
|
+
{{ tt('orgsMoreInfo') }}
|
|
50813
|
+
</button>
|
|
50814
|
+
|
|
49794
50815
|
<div class="section-body">
|
|
49795
50816
|
@if (loading()) {
|
|
49796
50817
|
<val-skeleton-layout [props]="{ preset: 'detail', rows: 4 }" />
|
|
@@ -49838,6 +50859,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
49838
50859
|
<div class="section-body">
|
|
49839
50860
|
@if (membersLoading()) {
|
|
49840
50861
|
<val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
|
|
50862
|
+
} @else if (membersErrorState()) {
|
|
50863
|
+
<val-empty-state [props]="membersErrorState()!" />
|
|
49841
50864
|
} @else if (members().length === 0) {
|
|
49842
50865
|
<val-text
|
|
49843
50866
|
[props]="{
|
|
@@ -49930,10 +50953,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
49930
50953
|
/>
|
|
49931
50954
|
</div>
|
|
49932
50955
|
<val-button [props]="openInviteButtonProps()" (click)="onOpenInvite()" />
|
|
50956
|
+
<val-button [props]="openImportButtonProps()" (click)="onOpenImport()" />
|
|
49933
50957
|
</div>
|
|
49934
50958
|
</section>
|
|
49935
50959
|
}
|
|
49936
50960
|
|
|
50961
|
+
<!-- API Keys: sección propia. Integraciones M2M (import de miembros, etc.). -->
|
|
50962
|
+
<section *valHasPermission="'api-keys:read'" class="settings-section" data-testid="org-apikeys-section">
|
|
50963
|
+
<div class="invite-cta">
|
|
50964
|
+
<div class="invite-cta__text">
|
|
50965
|
+
<val-title [props]="{ size: 'medium', color: 'dark', bold: true, content: tt('apiKeysSectionTitle') }" />
|
|
50966
|
+
<val-text [props]="{ size: 'medium', color: 'dark', bold: false, content: tt('apiKeysSectionHint') }" />
|
|
50967
|
+
<button class="org-more-info-link" type="button" (click)="onOpenApiKeys()">
|
|
50968
|
+
{{ tt('apiKeysMoreInfo') }}
|
|
50969
|
+
</button>
|
|
50970
|
+
</div>
|
|
50971
|
+
<val-button [props]="openApiKeysButtonProps()" (click)="onOpenApiKeys()" />
|
|
50972
|
+
</div>
|
|
50973
|
+
</section>
|
|
50974
|
+
|
|
49937
50975
|
@if (resolvedConfig().showTransferOwnership && isOwner()) {
|
|
49938
50976
|
<!-- Transfer ownership: solo owner (gating RBAC interno) -->
|
|
49939
50977
|
<section class="settings-section" data-testid="org-transfer-section">
|
|
@@ -50024,7 +51062,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
50024
51062
|
</section>
|
|
50025
51063
|
}
|
|
50026
51064
|
</div>
|
|
50027
|
-
`, 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))}.section-header-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.section-body{display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.row-actions--gap{display:flex;gap:8px}.invite-cta{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}.invite-cta__text{display:flex;flex-direction:column;gap:4px;flex:1}.org-info-card{border-radius:14px;background:var(--ion-color-light, #f4f5f8);overflow:hidden}:host-context(body.dark) .org-info-card,:host-context(html.ion-palette-dark) .org-info-card,:host-context([data-theme=\"dark\"]) .org-info-card{background:#ffffff0d}.org-info-field{padding:12px 16px;display:flex;flex-direction:column;gap:4px;border-bottom:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-field--full{width:100%}.org-info-field:last-child{border-bottom:none}.org-info-row{display:flex}.org-info-row .org-info-field{flex:1}.org-info-row .org-info-field:first-child{border-right:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.org-info-value{font-size:.95rem;font-weight:500;color:var(--ion-color-dark)}.org-info-value--muted{color:var(--ion-color-medium);font-weight:400}.plan-badge{display:inline-block;font-size:.78rem;font-weight:700;padding:3px 10px;border-radius:20px;width:fit-content}.plan-badge--free{background:var(--ion-color-light-shade, #d7d8da);color:var(--ion-color-dark)}.plan-badge--pro{background:var(--ion-color-primary);color:#fff}.plan-badge--enterprise{background:#f5c542;color:#222}.members-list{display:flex;flex-direction:column;gap:8px}.members-show-more{background:none;border:none;color:var(--ion-color-primary);font-size:14px;font-weight:500;cursor:pointer;padding:8px 0}.rbac-debug{opacity:.7}.rbac-debug__body{display:flex;flex-direction:column;gap:6px;font-family:monospace;font-size:.78rem}.rbac-debug__row{display:flex;gap:8px;align-items:flex-start}.rbac-debug__label{color:var(--ion-color-medium);min-width:72px;flex-shrink:0}.rbac-debug__value{color:var(--ion-color-dark);word-break:break-all}.rbac-debug__value--perms{color:var(--ion-color-medium)}\n"] }]
|
|
51065
|
+
`, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.org-more-info-link{background:none;border:none;padding:4px 0;margin-top:4px;font-size:13px;font-weight:600;color:var(--ion-color-primary, #7026df);cursor:pointer;text-align:left}.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))}.section-header-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.section-body{display:flex;flex-direction:column;gap:10px}.row-actions{margin-top:12px}.row-actions--gap{display:flex;gap:8px}.invite-cta{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}.invite-cta__text{display:flex;flex-direction:column;gap:4px;flex:1}.org-info-card{border-radius:14px;background:var(--ion-color-light, #f4f5f8);overflow:hidden}:host-context(body.dark) .org-info-card,:host-context(html.ion-palette-dark) .org-info-card,:host-context([data-theme=\"dark\"]) .org-info-card{background:#ffffff0d}.org-info-field{padding:12px 16px;display:flex;flex-direction:column;gap:4px;border-bottom:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-field--full{width:100%}.org-info-field:last-child{border-bottom:none}.org-info-row{display:flex}.org-info-row .org-info-field{flex:1}.org-info-row .org-info-field:first-child{border-right:1px solid var(--val-border-color, rgba(0, 0, 0, .06))}.org-info-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.org-info-value{font-size:.95rem;font-weight:500;color:var(--ion-color-dark)}.org-info-value--muted{color:var(--ion-color-medium);font-weight:400}.plan-badge{display:inline-block;font-size:.78rem;font-weight:700;padding:3px 10px;border-radius:20px;width:fit-content}.plan-badge--free{background:var(--ion-color-light-shade, #d7d8da);color:var(--ion-color-dark)}.plan-badge--pro{background:var(--ion-color-primary);color:#fff}.plan-badge--enterprise{background:#f5c542;color:#222}.members-list{display:flex;flex-direction:column;gap:8px}.members-show-more{background:none;border:none;color:var(--ion-color-primary);font-size:14px;font-weight:500;cursor:pointer;padding:8px 0}.rbac-debug{opacity:.7}.rbac-debug__body{display:flex;flex-direction:column;gap:6px;font-family:monospace;font-size:.78rem}.rbac-debug__row{display:flex;gap:8px;align-items:flex-start}.rbac-debug__label{color:var(--ion-color-medium);min-width:72px;flex-shrink:0}.rbac-debug__value{color:var(--ion-color-dark);word-break:break-all}.rbac-debug__value--perms{color:var(--ion-color-medium)}\n"] }]
|
|
50028
51066
|
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
50029
51067
|
type: Input
|
|
50030
51068
|
}] } });
|
|
@@ -50138,7 +51176,7 @@ addIcons({
|
|
|
50138
51176
|
notificationsOutline,
|
|
50139
51177
|
refreshOutline,
|
|
50140
51178
|
});
|
|
50141
|
-
const DEFAULT_NAMESPACE$
|
|
51179
|
+
const DEFAULT_NAMESPACE$3 = 'Settings.Notifications';
|
|
50142
51180
|
/**
|
|
50143
51181
|
* `val-notification-preferences-view` — **preferencias de notificaciones push**
|
|
50144
51182
|
* (FCM) full-feature autocontenida (organism). Promovida desde `showcase` bajo el
|
|
@@ -50181,7 +51219,7 @@ class NotificationPreferencesViewComponent {
|
|
|
50181
51219
|
const merged = { ...fromRoute, ...(this.config ?? {}) };
|
|
50182
51220
|
return {
|
|
50183
51221
|
showDetails: merged.showDetails ?? true,
|
|
50184
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
51222
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$3,
|
|
50185
51223
|
onPushEnabled: merged.onPushEnabled,
|
|
50186
51224
|
onPushDisabled: merged.onPushDisabled,
|
|
50187
51225
|
};
|
|
@@ -50952,7 +51990,7 @@ const PERMISSIONS_VIEW_I18N = {
|
|
|
50952
51990
|
},
|
|
50953
51991
|
};
|
|
50954
51992
|
|
|
50955
|
-
const DEFAULT_NAMESPACE$
|
|
51993
|
+
const DEFAULT_NAMESPACE$2 = 'Settings.Permissions';
|
|
50956
51994
|
/**
|
|
50957
51995
|
* `val-permissions-view` — vista Permisos full-feature autocontenida (organism).
|
|
50958
51996
|
* Visor RBAC de solo lectura: muestra los roles del sistema (Visor/Editor/Admin +
|
|
@@ -50992,7 +52030,7 @@ class PermissionsViewComponent {
|
|
|
50992
52030
|
return {
|
|
50993
52031
|
showSystemRoles: merged.showSystemRoles ?? true,
|
|
50994
52032
|
showCustomRoles: merged.showCustomRoles ?? true,
|
|
50995
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
52033
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$2,
|
|
50996
52034
|
};
|
|
50997
52035
|
});
|
|
50998
52036
|
this.loading = signal(false);
|
|
@@ -51769,7 +52807,7 @@ addIcons({
|
|
|
51769
52807
|
openOutline,
|
|
51770
52808
|
personOutline,
|
|
51771
52809
|
});
|
|
51772
|
-
const DEFAULT_NAMESPACE = 'Notifications';
|
|
52810
|
+
const DEFAULT_NAMESPACE$1 = 'Notifications';
|
|
51773
52811
|
const DEFAULT_SETTINGS_PATH = '/app/settings/notifications';
|
|
51774
52812
|
const DEFAULT_PAGE_SIZE = 10;
|
|
51775
52813
|
const DEFAULT_MAX_LOAD = 50;
|
|
@@ -51829,7 +52867,7 @@ class NotificationsViewComponent {
|
|
|
51829
52867
|
showPushOffCta: merged.showPushOffCta ?? true,
|
|
51830
52868
|
enableMarkAll: merged.enableMarkAll ?? true,
|
|
51831
52869
|
settingsNotificationsPath: merged.settingsNotificationsPath ?? DEFAULT_SETTINGS_PATH,
|
|
51832
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
52870
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$1,
|
|
51833
52871
|
pageSize: merged.pageSize ?? DEFAULT_PAGE_SIZE,
|
|
51834
52872
|
maxLoad: merged.maxLoad ?? DEFAULT_MAX_LOAD,
|
|
51835
52873
|
onNotificationOpen: merged.onNotificationOpen,
|
|
@@ -52497,6 +53535,797 @@ function provideValtechNotificationsRoutes(opts) {
|
|
|
52497
53535
|
];
|
|
52498
53536
|
}
|
|
52499
53537
|
|
|
53538
|
+
/**
|
|
53539
|
+
* Defaults i18n del componente (es/en). El componente los auto-registra en su
|
|
53540
|
+
* constructor si el consumer no proveyó el namespace `ContentReaction`. Esto
|
|
53541
|
+
* garantiza que NUNCA haya una key faltante: una key faltante se evalúa en el
|
|
53542
|
+
* template por cada change-detection → `console.warn` por CD → con el
|
|
53543
|
+
* `val-debug-console` activo (espeja console.* a un signal) se dispara un loop
|
|
53544
|
+
* infinito que congela el navegador en dev. Auto-registrar lo elimina de raíz.
|
|
53545
|
+
*/
|
|
53546
|
+
const CONTENT_REACTION_I18N = {
|
|
53547
|
+
es: {
|
|
53548
|
+
question: '¿Te resultó útil este contenido?',
|
|
53549
|
+
commentPlaceholder: 'Cuéntanos más (opcional)...',
|
|
53550
|
+
submit: 'Enviar opinión',
|
|
53551
|
+
update: 'Actualizar opinión',
|
|
53552
|
+
thankYou: '¡Gracias por tu opinión!',
|
|
53553
|
+
submitted: 'Tu opinión ha sido registrada',
|
|
53554
|
+
errorSubmitting: 'Error al enviar. Intenta de nuevo.',
|
|
53555
|
+
positiveLabel: 'Sí, mucho',
|
|
53556
|
+
negativeLabel: 'No tanto',
|
|
53557
|
+
negativeFeedbackTitle: '¿Cuéntanos qué no te gustó?',
|
|
53558
|
+
},
|
|
53559
|
+
en: {
|
|
53560
|
+
question: 'Was this content helpful?',
|
|
53561
|
+
commentPlaceholder: 'Tell us more (optional)...',
|
|
53562
|
+
submit: 'Send feedback',
|
|
53563
|
+
update: 'Update feedback',
|
|
53564
|
+
thankYou: 'Thanks for your feedback!',
|
|
53565
|
+
submitted: 'Your feedback has been recorded',
|
|
53566
|
+
errorSubmitting: 'Failed to send. Please try again.',
|
|
53567
|
+
positiveLabel: 'Yes, a lot',
|
|
53568
|
+
negativeLabel: 'Not really',
|
|
53569
|
+
negativeFeedbackTitle: "Tell us what you didn't like?",
|
|
53570
|
+
},
|
|
53571
|
+
};
|
|
53572
|
+
/**
|
|
53573
|
+
* Componente para reacciones de contenido con emojis.
|
|
53574
|
+
*
|
|
53575
|
+
* @example
|
|
53576
|
+
* ```html
|
|
53577
|
+
* <val-content-reaction
|
|
53578
|
+
* [props]="{
|
|
53579
|
+
* entityRef: { entityType: 'article', entityId: 'art-123' },
|
|
53580
|
+
* question: '¿Te fue útil este artículo?'
|
|
53581
|
+
* }"
|
|
53582
|
+
* (reactionSubmit)="onReactionSubmit($event)"
|
|
53583
|
+
* />
|
|
53584
|
+
* ```
|
|
53585
|
+
*/
|
|
53586
|
+
class ContentReactionComponent {
|
|
53587
|
+
constructor() {
|
|
53588
|
+
this.feedbackService = inject(FeedbackService);
|
|
53589
|
+
this.toast = inject(ToastService);
|
|
53590
|
+
this.i18n = inject(I18nService);
|
|
53591
|
+
this.auth = inject(AuthService, { optional: true });
|
|
53592
|
+
this.isConfigured = computed(() => this.feedbackService.isConfigured());
|
|
53593
|
+
this.props = {};
|
|
53594
|
+
this.reactionSubmit = new EventEmitter();
|
|
53595
|
+
this.reactionChange = new EventEmitter();
|
|
53596
|
+
// Estado reactivo
|
|
53597
|
+
this.state = signal({
|
|
53598
|
+
selectedValue: null,
|
|
53599
|
+
comment: '',
|
|
53600
|
+
isLoading: false,
|
|
53601
|
+
isSubmitted: false,
|
|
53602
|
+
hadPreviousReaction: false,
|
|
53603
|
+
error: null,
|
|
53604
|
+
});
|
|
53605
|
+
// Valores por defecto
|
|
53606
|
+
this.defaultEmojis = ['😞', '😐', '😊'];
|
|
53607
|
+
this.defaultLabels = ['No me ayudó', 'Regular', 'Muy útil'];
|
|
53608
|
+
// Computed properties
|
|
53609
|
+
this.resolvedProps = computed(() => ({
|
|
53610
|
+
entityRef: this.props.entityRef,
|
|
53611
|
+
question: this.props.question || this.t('question'),
|
|
53612
|
+
showComment: this.props.showComment ?? true,
|
|
53613
|
+
commentOnValues: this.props.commentOnValues,
|
|
53614
|
+
commentPlaceholder: this.props.commentPlaceholder || this.t('commentPlaceholder'),
|
|
53615
|
+
maxCommentLength: this.props.maxCommentLength ?? 500,
|
|
53616
|
+
emojis: this.props.emojis || this.defaultEmojis,
|
|
53617
|
+
emojiLabels: this.props.emojiLabels || this.defaultLabels,
|
|
53618
|
+
showThankYou: this.props.showThankYou ?? true,
|
|
53619
|
+
thankYouMessage: this.props.thankYouMessage || this.t('thankYou'),
|
|
53620
|
+
disabled: this.props.disabled ?? false,
|
|
53621
|
+
readonly: this.props.readonly ?? false,
|
|
53622
|
+
// Variante: 'emoji' cuando se declara explícitamente o cuando se pasan emojis custom.
|
|
53623
|
+
variant: this.props.variant ?? (this.props.emojis ? 'emoji' : 'buttons'),
|
|
53624
|
+
}));
|
|
53625
|
+
this.activeReactionValues = computed(() => this.resolvedProps().emojis.length === 2 ? ['negative', 'positive'] : ['negative', 'neutral', 'positive']);
|
|
53626
|
+
this.showCommentField = computed(() => {
|
|
53627
|
+
const sel = this.state().selectedValue;
|
|
53628
|
+
if (!sel)
|
|
53629
|
+
return false;
|
|
53630
|
+
const commentOn = this.resolvedProps().commentOnValues;
|
|
53631
|
+
if (commentOn !== undefined)
|
|
53632
|
+
return commentOn.includes(sel);
|
|
53633
|
+
return this.resolvedProps().showComment;
|
|
53634
|
+
});
|
|
53635
|
+
this.canSubmit = computed(() => this.state().selectedValue !== null && !this.state().isLoading);
|
|
53636
|
+
// Auto-registro de defaults i18n (es/en) si el consumer no proveyó el
|
|
53637
|
+
// namespace. Garantiza que `t()` nunca caiga en key faltante. Ver
|
|
53638
|
+
// CONTENT_REACTION_I18N. Idempotente: solo la 1ª instancia registra.
|
|
53639
|
+
if (!this.i18n.hasNamespace('ContentReaction')) {
|
|
53640
|
+
this.i18n.registerContent('ContentReaction', CONTENT_REACTION_I18N);
|
|
53641
|
+
}
|
|
53642
|
+
}
|
|
53643
|
+
ngOnInit() {
|
|
53644
|
+
this.loadPreviousReaction();
|
|
53645
|
+
}
|
|
53646
|
+
ngOnChanges(changes) {
|
|
53647
|
+
if (changes['props'] && !changes['props'].firstChange) {
|
|
53648
|
+
this.loadPreviousReaction();
|
|
53649
|
+
}
|
|
53650
|
+
}
|
|
53651
|
+
async loadPreviousReaction() {
|
|
53652
|
+
if (!this.props.entityRef)
|
|
53653
|
+
return;
|
|
53654
|
+
// Si hay initialValue, usarlo directamente sin consultar
|
|
53655
|
+
if (this.props.initialValue) {
|
|
53656
|
+
this.state.update(s => ({
|
|
53657
|
+
...s,
|
|
53658
|
+
selectedValue: this.props.initialValue,
|
|
53659
|
+
hadPreviousReaction: true,
|
|
53660
|
+
isLoading: false,
|
|
53661
|
+
isSubmitted: true,
|
|
53662
|
+
}));
|
|
53663
|
+
return;
|
|
53664
|
+
}
|
|
53665
|
+
// Si skipInitialCheck está activo, no consultar
|
|
53666
|
+
if (this.props.skipInitialCheck) {
|
|
53667
|
+
return;
|
|
53668
|
+
}
|
|
53669
|
+
this.state.update(s => ({ ...s, isLoading: true, error: null }));
|
|
53670
|
+
try {
|
|
53671
|
+
const check = await this.feedbackService.checkFeedback(this.props.entityRef.entityType, this.props.entityRef.entityId);
|
|
53672
|
+
if (check.hasFeedback && check.reactionValue) {
|
|
53673
|
+
this.state.update(s => ({
|
|
53674
|
+
...s,
|
|
53675
|
+
selectedValue: check.reactionValue,
|
|
53676
|
+
hadPreviousReaction: true,
|
|
53677
|
+
isLoading: false,
|
|
53678
|
+
isSubmitted: true,
|
|
53679
|
+
}));
|
|
53680
|
+
}
|
|
53681
|
+
else {
|
|
53682
|
+
this.state.update(s => ({ ...s, isLoading: false }));
|
|
53683
|
+
}
|
|
53684
|
+
}
|
|
53685
|
+
catch (error) {
|
|
53686
|
+
console.error('Error loading previous reaction:', error);
|
|
53687
|
+
this.state.update(s => ({ ...s, isLoading: false }));
|
|
53688
|
+
}
|
|
53689
|
+
}
|
|
53690
|
+
selectReaction(value) {
|
|
53691
|
+
if (this.resolvedProps().disabled || this.resolvedProps().readonly)
|
|
53692
|
+
return;
|
|
53693
|
+
const previousValue = this.state().selectedValue;
|
|
53694
|
+
this.state.update(s => ({
|
|
53695
|
+
...s,
|
|
53696
|
+
selectedValue: value,
|
|
53697
|
+
isSubmitted: false,
|
|
53698
|
+
error: null,
|
|
53699
|
+
}));
|
|
53700
|
+
this.reactionChange.emit({ value, previousValue });
|
|
53701
|
+
}
|
|
53702
|
+
async submitReaction() {
|
|
53703
|
+
const currentState = this.state();
|
|
53704
|
+
const props = this.resolvedProps();
|
|
53705
|
+
if (!currentState.selectedValue || props.disabled)
|
|
53706
|
+
return;
|
|
53707
|
+
this.state.update(s => ({ ...s, isLoading: true, error: null }));
|
|
53708
|
+
try {
|
|
53709
|
+
// Determinar si usar endpoint anónimo o autenticado
|
|
53710
|
+
const isAuthenticated = this.auth?.isAuthenticated() ?? false;
|
|
53711
|
+
const useAnonymous = this.props.allowAnonymous && !isAuthenticated;
|
|
53712
|
+
if (useAnonymous) {
|
|
53713
|
+
// Usar endpoint anónimo (público)
|
|
53714
|
+
await this.feedbackService.createAnonymousReaction(props.entityRef, currentState.selectedValue, currentState.comment || undefined);
|
|
53715
|
+
}
|
|
53716
|
+
else {
|
|
53717
|
+
// Usar endpoint autenticado
|
|
53718
|
+
await this.feedbackService.createReaction(props.entityRef, currentState.selectedValue, currentState.comment || undefined);
|
|
53719
|
+
}
|
|
53720
|
+
this.state.update(s => ({
|
|
53721
|
+
...s,
|
|
53722
|
+
isLoading: false,
|
|
53723
|
+
isSubmitted: true,
|
|
53724
|
+
comment: '',
|
|
53725
|
+
hadPreviousReaction: !useAnonymous, // Solo para autenticados
|
|
53726
|
+
}));
|
|
53727
|
+
this.reactionSubmit.emit({
|
|
53728
|
+
value: currentState.selectedValue,
|
|
53729
|
+
comment: currentState.comment || undefined,
|
|
53730
|
+
entityRef: props.entityRef,
|
|
53731
|
+
isUpdate: currentState.hadPreviousReaction,
|
|
53732
|
+
});
|
|
53733
|
+
if (props.showThankYou) {
|
|
53734
|
+
this.toast.show({
|
|
53735
|
+
message: props.thankYouMessage,
|
|
53736
|
+
duration: 2000,
|
|
53737
|
+
});
|
|
53738
|
+
}
|
|
53739
|
+
}
|
|
53740
|
+
catch (error) {
|
|
53741
|
+
console.error('Error submitting reaction:', error);
|
|
53742
|
+
this.state.update(s => ({
|
|
53743
|
+
...s,
|
|
53744
|
+
isLoading: false,
|
|
53745
|
+
error: this.t('errorSubmitting'),
|
|
53746
|
+
}));
|
|
53747
|
+
this.toast.show({
|
|
53748
|
+
message: this.t('errorSubmitting'),
|
|
53749
|
+
duration: 3000,
|
|
53750
|
+
});
|
|
53751
|
+
}
|
|
53752
|
+
}
|
|
53753
|
+
updateComment(event) {
|
|
53754
|
+
const value = event.detail.value || '';
|
|
53755
|
+
this.state.update(s => ({ ...s, comment: value }));
|
|
53756
|
+
}
|
|
53757
|
+
getEmoji(index) {
|
|
53758
|
+
return this.resolvedProps().emojis[index];
|
|
53759
|
+
}
|
|
53760
|
+
getEmojiLabel(index) {
|
|
53761
|
+
return this.resolvedProps().emojiLabels[index];
|
|
53762
|
+
}
|
|
53763
|
+
isSelected(value) {
|
|
53764
|
+
return this.state().selectedValue === value;
|
|
53765
|
+
}
|
|
53766
|
+
t(key) {
|
|
53767
|
+
const translations = {
|
|
53768
|
+
question: '¿Te resultó útil este contenido?',
|
|
53769
|
+
commentPlaceholder: 'Cuéntanos más (opcional)...',
|
|
53770
|
+
submit: 'Enviar opinión',
|
|
53771
|
+
update: 'Actualizar opinión',
|
|
53772
|
+
thankYou: '¡Gracias por tu opinión!',
|
|
53773
|
+
submitted: 'Tu opinión ha sido registrada',
|
|
53774
|
+
errorSubmitting: 'Error al enviar. Intenta de nuevo.',
|
|
53775
|
+
positiveLabel: 'Sí, mucho',
|
|
53776
|
+
negativeLabel: 'No tanto',
|
|
53777
|
+
negativeFeedbackTitle: '¿Cuéntanos qué no te gustó?',
|
|
53778
|
+
};
|
|
53779
|
+
return this.i18n.t(key, 'ContentReaction') || translations[key] || key;
|
|
53780
|
+
}
|
|
53781
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
53782
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ContentReactionComponent, isStandalone: true, selector: "val-content-reaction", inputs: { props: "props" }, outputs: { reactionSubmit: "reactionSubmit", reactionChange: "reactionChange" }, usesOnChanges: true, ngImport: i0, template: "@if (isConfigured()) {\n<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n}\n", styles: [":host{display:block}.content-reaction{padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.question{font-size:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:.875rem}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }] }); }
|
|
53783
|
+
}
|
|
53784
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, decorators: [{
|
|
53785
|
+
type: Component,
|
|
53786
|
+
args: [{ selector: 'val-content-reaction', standalone: true, imports: [CommonModule, FormsModule, IonButton, IonSpinner, IonTextarea], template: "@if (isConfigured()) {\n<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n}\n", styles: [":host{display:block}.content-reaction{padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.question{font-size:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:.875rem}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
|
|
53787
|
+
}], ctorParameters: () => [], propDecorators: { props: [{
|
|
53788
|
+
type: Input
|
|
53789
|
+
}], reactionSubmit: [{
|
|
53790
|
+
type: Output
|
|
53791
|
+
}], reactionChange: [{
|
|
53792
|
+
type: Output
|
|
53793
|
+
}] } });
|
|
53794
|
+
|
|
53795
|
+
/**
|
|
53796
|
+
* Defaults i18n (es/en) embebidos en `val-about-view`. Auto-registrados en el
|
|
53797
|
+
* constructor del componente si el consumer no proveyó el namespace
|
|
53798
|
+
* (`Settings.About` por default). SOLO incluye las keys que la vista usa.
|
|
53799
|
+
*
|
|
53800
|
+
* Textos portados EXACTOS del override `Settings.About` de showcase
|
|
53801
|
+
* (`frontend/showcase/src/i18n/index.ts`). Las keys de las acciones
|
|
53802
|
+
* showcase-specific (`feedback*`, `share*`, `faq*`, `donate*`) NO se incluyen:
|
|
53803
|
+
* esas acciones se modelan ahora como `actions[]` de la config (el consumer
|
|
53804
|
+
* aporta sus propios textos). `copyrightText` se incluye aquí (antes salía del
|
|
53805
|
+
* namespace `Layout`) para que la vista sea autocontenida.
|
|
53806
|
+
*/
|
|
53807
|
+
const ABOUT_VIEW_I18N = {
|
|
53808
|
+
es: {
|
|
53809
|
+
pageTitle: 'Acerca de',
|
|
53810
|
+
pageDescription: 'Demo del factory Valtech',
|
|
53811
|
+
tagline: 'App de demo — explora las funcionalidades en vivo',
|
|
53812
|
+
descTitle: 'Acerca de Showcase',
|
|
53813
|
+
descBody: 'Showcase es la app de referencia del factory Valtech. Aquí puedes explorar el flujo completo en vivo: autenticación, perfil, organizaciones, notificaciones y ajustes de cuenta.',
|
|
53814
|
+
actionsTitle: 'Acciones',
|
|
53815
|
+
rateTitle: 'Calificar la app',
|
|
53816
|
+
rateDesc: 'Cuéntanos qué te ha parecido la experiencia.',
|
|
53817
|
+
rateModalTitle: 'Tu opinión',
|
|
53818
|
+
rateQuestion: '¿Qué te ha parecido la experiencia hasta ahora?',
|
|
53819
|
+
rateComment: 'Cuéntanos más (opcional)…',
|
|
53820
|
+
rateThanks: '¡Gracias por tu opinión!',
|
|
53821
|
+
rateClose: 'Cerrar',
|
|
53822
|
+
rateBad: 'No tanto',
|
|
53823
|
+
rateMid: 'Bien',
|
|
53824
|
+
rateGood: '¡Me encanta!',
|
|
53825
|
+
socialTitle: 'Síguenos',
|
|
53826
|
+
socialDesc: 'Estamos en redes sociales.',
|
|
53827
|
+
versionLabel: 'Versión',
|
|
53828
|
+
versionCopied: 'v{v} copiado',
|
|
53829
|
+
copyrightText: '© {year}, Soluciones Valtech Ltda. o sus afiliados. Todos los derechos reservados.',
|
|
53830
|
+
},
|
|
53831
|
+
en: {
|
|
53832
|
+
pageTitle: 'About',
|
|
53833
|
+
pageDescription: 'Valtech factory demo',
|
|
53834
|
+
tagline: 'Demo app — explore features live',
|
|
53835
|
+
descTitle: 'About Showcase',
|
|
53836
|
+
descBody: 'Showcase is the reference app for the Valtech factory. Explore the full flow live: authentication, profile, organizations, notifications and account settings.',
|
|
53837
|
+
actionsTitle: 'Actions',
|
|
53838
|
+
rateTitle: 'Rate the app',
|
|
53839
|
+
rateDesc: 'Tell us how your experience has been.',
|
|
53840
|
+
rateModalTitle: 'Your feedback',
|
|
53841
|
+
rateQuestion: 'How has your experience been so far?',
|
|
53842
|
+
rateComment: 'Tell us more (optional)…',
|
|
53843
|
+
rateThanks: 'Thanks for your feedback!',
|
|
53844
|
+
rateClose: 'Close',
|
|
53845
|
+
rateBad: 'Not great',
|
|
53846
|
+
rateMid: 'Good',
|
|
53847
|
+
rateGood: 'Love it!',
|
|
53848
|
+
socialTitle: 'Follow us',
|
|
53849
|
+
socialDesc: "We're on social media.",
|
|
53850
|
+
versionLabel: 'Version',
|
|
53851
|
+
versionCopied: 'v{v} copied',
|
|
53852
|
+
copyrightText: '© {year}, Soluciones Valtech Ltda. or its affiliates. All rights reserved.',
|
|
53853
|
+
},
|
|
53854
|
+
};
|
|
53855
|
+
|
|
53856
|
+
addIcons({
|
|
53857
|
+
copyOutline,
|
|
53858
|
+
starOutline,
|
|
53859
|
+
chatbubbleEllipsesOutline,
|
|
53860
|
+
shareSocialOutline,
|
|
53861
|
+
helpCircleOutline,
|
|
53862
|
+
logoFacebook,
|
|
53863
|
+
logoInstagram,
|
|
53864
|
+
logoLinkedin,
|
|
53865
|
+
logoTwitter,
|
|
53866
|
+
logoYoutube,
|
|
53867
|
+
logoTiktok,
|
|
53868
|
+
});
|
|
53869
|
+
const DEFAULT_NAMESPACE = 'Settings.About';
|
|
53870
|
+
const DEFAULT_BRAND_NAME = 'Valtech';
|
|
53871
|
+
const DEFAULT_LOGO_SRC = 'assets/images/main-icon.png';
|
|
53872
|
+
const DEFAULT_RATING_ENTITY_ID = 'experience';
|
|
53873
|
+
/**
|
|
53874
|
+
* `val-about-view` — vista About full-feature autocontenida (organism).
|
|
53875
|
+
* Promovida desde `showcase` bajo el proceso de ADR-021. Reúne:
|
|
53876
|
+
* - **HERO** — logo + nombre de marca + tagline + pill de versión (copiable).
|
|
53877
|
+
* - **Descripción** — título + cuerpo.
|
|
53878
|
+
* - **Acciones** — action-card de rating built-in (abre `val-content-reaction`
|
|
53879
|
+
* DIRECTAMENTE vía `ModalService`, sin wrapper local — decisión CTO) + las
|
|
53880
|
+
* acciones genéricas que el consumer aporte por `config.actions`.
|
|
53881
|
+
* - **Redes** — links sociales (`config.socialLinks`); sección oculta si vacía.
|
|
53882
|
+
* - **Footer** — copyright con el año actual.
|
|
53883
|
+
*
|
|
53884
|
+
* Lo app-specific (logo, nombre, versión, redes, acciones share/faq) viaja como
|
|
53885
|
+
* DATA de config — NO como slots (ADR-021). La versión la pasa el consumer
|
|
53886
|
+
* (`config.version` = su `APP_VERSION`); la lib NO importa el `version.ts` de la
|
|
53887
|
+
* app. Si no se pasa versión, el pill se oculta.
|
|
53888
|
+
*
|
|
53889
|
+
* NO renderiza ion-content — vive dentro de val-page-wrapper. `ActivatedRoute`
|
|
53890
|
+
* se inyecta `{ optional: true }` solo para leer el route data de config.
|
|
53891
|
+
*
|
|
53892
|
+
* Auto-registra sus defaults i18n (es/en) en el constructor si el consumer no
|
|
53893
|
+
* proveyó el namespace configurado (default `Settings.About`).
|
|
53894
|
+
*
|
|
53895
|
+
* GOTCHA i18n ContentReaction: `val-content-reaction` exige su namespace
|
|
53896
|
+
* `ContentReaction` registrado donde se use (key faltante en binding + CD =
|
|
53897
|
+
* navegador congelado en dev). `ContentReactionComponent` se auto-registra ese
|
|
53898
|
+
* namespace en SU constructor, y como se abre vía `ModalService` ese constructor
|
|
53899
|
+
* corre antes de que su template binde — queda cubierto. Igual lo importamos
|
|
53900
|
+
* como dependencia explícita de esta vista para que la garantía sea local.
|
|
53901
|
+
*/
|
|
53902
|
+
class AboutViewComponent {
|
|
53903
|
+
/** Namespace i18n resuelto (capturado para llamadas no-reactivas). */
|
|
53904
|
+
get ns() {
|
|
53905
|
+
return this.resolvedConfig().i18nNamespace;
|
|
53906
|
+
}
|
|
53907
|
+
constructor() {
|
|
53908
|
+
this.nav = inject(NavigationService);
|
|
53909
|
+
this.i18n = inject(I18nService);
|
|
53910
|
+
this.modalService = inject(ModalService);
|
|
53911
|
+
this.toast = inject(ToastService);
|
|
53912
|
+
this.route = inject(ActivatedRoute, { optional: true });
|
|
53913
|
+
this.resolvedConfig = computed(() => {
|
|
53914
|
+
const fromRoute = (this.route?.snapshot.data['aboutConfig'] ?? {});
|
|
53915
|
+
const merged = { ...fromRoute, ...(this.config ?? {}) };
|
|
53916
|
+
return {
|
|
53917
|
+
brand: {
|
|
53918
|
+
name: merged.brand?.name ?? DEFAULT_BRAND_NAME,
|
|
53919
|
+
logoSrc: merged.brand?.logoSrc ?? DEFAULT_LOGO_SRC,
|
|
53920
|
+
},
|
|
53921
|
+
version: merged.version ?? '',
|
|
53922
|
+
socialLinks: merged.socialLinks ?? [],
|
|
53923
|
+
showRating: merged.showRating ?? true,
|
|
53924
|
+
ratingEntityId: merged.ratingEntityId ?? DEFAULT_RATING_ENTITY_ID,
|
|
53925
|
+
actions: merged.actions ?? [],
|
|
53926
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
53927
|
+
onRated: merged.onRated,
|
|
53928
|
+
};
|
|
53929
|
+
});
|
|
53930
|
+
this.brandName = computed(() => this.resolvedConfig().brand.name);
|
|
53931
|
+
this.logoSrc = computed(() => this.resolvedConfig().brand.logoSrc);
|
|
53932
|
+
this.version = computed(() => this.resolvedConfig().version);
|
|
53933
|
+
this.socialLinks = computed(() => this.resolvedConfig().socialLinks);
|
|
53934
|
+
this.showRating = computed(() => this.resolvedConfig().showRating);
|
|
53935
|
+
this.actions = computed(() => this.resolvedConfig().actions);
|
|
53936
|
+
/** Copyright del footer — año actual interpolado en `copyrightText`. */
|
|
53937
|
+
this.copyright = computed(() => {
|
|
53938
|
+
this.i18n.lang();
|
|
53939
|
+
const year = String(new Date().getFullYear());
|
|
53940
|
+
return this.tt('copyrightText').replace('{year}', year);
|
|
53941
|
+
});
|
|
53942
|
+
/** Title metadata para secciones — bold + size small. */
|
|
53943
|
+
this.rateActionCard = computed(() => {
|
|
53944
|
+
this.i18n.lang();
|
|
53945
|
+
return {
|
|
53946
|
+
icon: { ionicon: 'star-outline', color: 'primary' },
|
|
53947
|
+
title: this.tt('rateTitle'),
|
|
53948
|
+
description: this.tt('rateDesc'),
|
|
53949
|
+
size: 'medium',
|
|
53950
|
+
bordered: false,
|
|
53951
|
+
shadowed: false,
|
|
53952
|
+
};
|
|
53953
|
+
});
|
|
53954
|
+
const ns = this.ns;
|
|
53955
|
+
if (!this.i18n.hasNamespace(ns)) {
|
|
53956
|
+
this.i18n.registerContent(ns, ABOUT_VIEW_I18N);
|
|
53957
|
+
}
|
|
53958
|
+
this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
|
|
53959
|
+
}
|
|
53960
|
+
/** Helper i18n bound al namespace resuelto. Reactivo vía `lang()`. */
|
|
53961
|
+
tt(key) {
|
|
53962
|
+
this.i18n.lang();
|
|
53963
|
+
return this.i18n.t(key, this.ns);
|
|
53964
|
+
}
|
|
53965
|
+
/** Title metadata para secciones — bold + size small. */
|
|
53966
|
+
sectionTitle(key) {
|
|
53967
|
+
this.i18n.lang();
|
|
53968
|
+
return {
|
|
53969
|
+
content: this.tt(key),
|
|
53970
|
+
size: 'small',
|
|
53971
|
+
color: 'dark',
|
|
53972
|
+
bold: true,
|
|
53973
|
+
};
|
|
53974
|
+
}
|
|
53975
|
+
/** ActionCardMetadata para una acción genérica del consumer. */
|
|
53976
|
+
extraActionCard(action) {
|
|
53977
|
+
return {
|
|
53978
|
+
icon: { ionicon: action.icon ?? 'chatbubble-ellipses-outline', color: 'primary' },
|
|
53979
|
+
title: action.label,
|
|
53980
|
+
description: action.description,
|
|
53981
|
+
size: 'medium',
|
|
53982
|
+
bordered: false,
|
|
53983
|
+
shadowed: false,
|
|
53984
|
+
};
|
|
53985
|
+
}
|
|
53986
|
+
/** Copia la versión al portapapeles + toast (color dark). */
|
|
53987
|
+
async copyVersion() {
|
|
53988
|
+
const v = this.version();
|
|
53989
|
+
if (!v)
|
|
53990
|
+
return;
|
|
53991
|
+
try {
|
|
53992
|
+
await navigator.clipboard.writeText(v);
|
|
53993
|
+
this.toast.show({
|
|
53994
|
+
message: this.tt('versionCopied').replace('{v}', v),
|
|
53995
|
+
color: 'dark',
|
|
53996
|
+
duration: 1500,
|
|
53997
|
+
});
|
|
53998
|
+
}
|
|
53999
|
+
catch {
|
|
54000
|
+
// clipboard no disponible — silencioso
|
|
54001
|
+
}
|
|
54002
|
+
}
|
|
54003
|
+
/** Abre una URL externa en nueva tab. `mailto:` queda en la misma tab. */
|
|
54004
|
+
openUrl(url) {
|
|
54005
|
+
if (url.startsWith('mailto:')) {
|
|
54006
|
+
window.location.href = url;
|
|
54007
|
+
return;
|
|
54008
|
+
}
|
|
54009
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
54010
|
+
}
|
|
54011
|
+
/**
|
|
54012
|
+
* "Calificar la app" — abre `val-content-reaction` DIRECTAMENTE como sheet
|
|
54013
|
+
* modal (sin wrapper local — decisión CTO). El componente persiste la reacción
|
|
54014
|
+
* contra el backend de feedback y muestra su propio estado de "gracias"; el
|
|
54015
|
+
* sheet se cierra arrastrando (breakpoint 0). El namespace i18n
|
|
54016
|
+
* `ContentReaction` lo auto-registra `ContentReactionComponent` en su
|
|
54017
|
+
* constructor, que corre al crear el modal — sin riesgo de key faltante.
|
|
54018
|
+
*
|
|
54019
|
+
* Sheet (bottom sheet): el contenido scrollea dentro y el botón "Enviar" queda
|
|
54020
|
+
* siempre alcanzable, también en viewports chicos.
|
|
54021
|
+
*/
|
|
54022
|
+
async openRating() {
|
|
54023
|
+
const reactionProps = {
|
|
54024
|
+
// entityType "app" distingue esta reacción de las de artículos/FAQs.
|
|
54025
|
+
entityRef: { entityType: 'app', entityId: this.resolvedConfig().ratingEntityId },
|
|
54026
|
+
question: this.tt('rateQuestion'),
|
|
54027
|
+
commentPlaceholder: this.tt('rateComment'),
|
|
54028
|
+
thankYouMessage: this.tt('rateThanks'),
|
|
54029
|
+
showComment: true,
|
|
54030
|
+
showThankYou: true,
|
|
54031
|
+
// Modal one-shot: NO bloquear el render con el check de rating previo.
|
|
54032
|
+
skipInitialCheck: true,
|
|
54033
|
+
emojis: ['😕', '🙂', '😍'],
|
|
54034
|
+
emojiLabels: [this.tt('rateBad'), this.tt('rateMid'), this.tt('rateGood')],
|
|
54035
|
+
};
|
|
54036
|
+
await this.modalService.open({
|
|
54037
|
+
component: ContentReactionComponent,
|
|
54038
|
+
componentProps: { props: reactionProps },
|
|
54039
|
+
breakpoints: {
|
|
54040
|
+
initialBreakpoint: 0.9,
|
|
54041
|
+
breakpoints: [0, 0.9, 1],
|
|
54042
|
+
showHandle: true,
|
|
54043
|
+
handleBehavior: 'cycle',
|
|
54044
|
+
},
|
|
54045
|
+
});
|
|
54046
|
+
this.resolvedConfig().onRated?.();
|
|
54047
|
+
}
|
|
54048
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AboutViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
54049
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AboutViewComponent, isStandalone: true, selector: "val-about-view", inputs: { config: "config" }, ngImport: i0, template: `
|
|
54050
|
+
<ion-grid class="about-grid">
|
|
54051
|
+
<!-- HERO: logo + name + version -->
|
|
54052
|
+
<div class="about-hero">
|
|
54053
|
+
<img class="about-hero__logo" [src]="logoSrc()" [alt]="brandName()" />
|
|
54054
|
+
<val-title
|
|
54055
|
+
[props]="{
|
|
54056
|
+
content: brandName(),
|
|
54057
|
+
size: 'xlarge',
|
|
54058
|
+
color: 'dark',
|
|
54059
|
+
bold: true,
|
|
54060
|
+
}"
|
|
54061
|
+
/>
|
|
54062
|
+
<val-text
|
|
54063
|
+
[props]="{
|
|
54064
|
+
content: tt('tagline'),
|
|
54065
|
+
size: 'medium',
|
|
54066
|
+
color: 'dark',
|
|
54067
|
+
bold: false,
|
|
54068
|
+
}"
|
|
54069
|
+
/>
|
|
54070
|
+
@if (version()) {
|
|
54071
|
+
<div
|
|
54072
|
+
class="version-row"
|
|
54073
|
+
(click)="copyVersion()"
|
|
54074
|
+
role="button"
|
|
54075
|
+
[attr.aria-label]="tt('versionLabel') + ' ' + version()"
|
|
54076
|
+
>
|
|
54077
|
+
<val-pill
|
|
54078
|
+
[props]="{
|
|
54079
|
+
label: tt('versionLabel') + ' ' + version(),
|
|
54080
|
+
color: 'dark',
|
|
54081
|
+
weight: 'bold',
|
|
54082
|
+
size: 'small',
|
|
54083
|
+
}"
|
|
54084
|
+
/>
|
|
54085
|
+
<ion-icon name="copy-outline" class="version-copy-icon" />
|
|
54086
|
+
</div>
|
|
54087
|
+
}
|
|
54088
|
+
</div>
|
|
54089
|
+
|
|
54090
|
+
<!-- DESCRIPTION -->
|
|
54091
|
+
<section class="about-section">
|
|
54092
|
+
<val-title [props]="sectionTitle('descTitle')" />
|
|
54093
|
+
<val-text
|
|
54094
|
+
[props]="{
|
|
54095
|
+
content: tt('descBody'),
|
|
54096
|
+
size: 'medium',
|
|
54097
|
+
color: 'dark',
|
|
54098
|
+
bold: false,
|
|
54099
|
+
}"
|
|
54100
|
+
/>
|
|
54101
|
+
</section>
|
|
54102
|
+
|
|
54103
|
+
<!-- ACTIONS — rating built-in + acciones del consumer. -->
|
|
54104
|
+
@if (showRating() || actions().length > 0) {
|
|
54105
|
+
<section class="about-section about-section--reviews">
|
|
54106
|
+
<val-title [props]="sectionTitle('actionsTitle')" />
|
|
54107
|
+
<div class="about-actions">
|
|
54108
|
+
@if (showRating()) {
|
|
54109
|
+
<val-action-card [props]="rateActionCard()" (onClick)="openRating()" />
|
|
54110
|
+
}
|
|
54111
|
+
@for (action of actions(); track action.label) {
|
|
54112
|
+
<val-action-card [props]="extraActionCard(action)" (onClick)="action.onClick()" />
|
|
54113
|
+
}
|
|
54114
|
+
</div>
|
|
54115
|
+
</section>
|
|
54116
|
+
}
|
|
54117
|
+
|
|
54118
|
+
<!-- SOCIAL — penúltimo, justo antes del footer de derechos. -->
|
|
54119
|
+
@if (socialLinks().length > 0) {
|
|
54120
|
+
<section class="about-section">
|
|
54121
|
+
<val-title [props]="sectionTitle('socialTitle')" />
|
|
54122
|
+
<val-text
|
|
54123
|
+
[props]="{
|
|
54124
|
+
content: tt('socialDesc'),
|
|
54125
|
+
size: 'medium',
|
|
54126
|
+
color: 'dark',
|
|
54127
|
+
bold: false,
|
|
54128
|
+
}"
|
|
54129
|
+
/>
|
|
54130
|
+
<div class="about-social">
|
|
54131
|
+
@for (s of socialLinks(); track s.url) {
|
|
54132
|
+
<ion-button
|
|
54133
|
+
fill="clear"
|
|
54134
|
+
size="small"
|
|
54135
|
+
class="social-btn"
|
|
54136
|
+
[attr.aria-label]="s.label"
|
|
54137
|
+
(click)="openUrl(s.url)"
|
|
54138
|
+
>
|
|
54139
|
+
<ion-icon [name]="s.icon" slot="icon-only" />
|
|
54140
|
+
</ion-button>
|
|
54141
|
+
}
|
|
54142
|
+
</div>
|
|
54143
|
+
</section>
|
|
54144
|
+
}
|
|
54145
|
+
|
|
54146
|
+
<!-- FOOTER -->
|
|
54147
|
+
<footer class="about-footer">
|
|
54148
|
+
<val-text
|
|
54149
|
+
[props]="{
|
|
54150
|
+
content: copyright(),
|
|
54151
|
+
size: 'small',
|
|
54152
|
+
color: 'dark',
|
|
54153
|
+
bold: false,
|
|
54154
|
+
}"
|
|
54155
|
+
/>
|
|
54156
|
+
</footer>
|
|
54157
|
+
</ion-grid>
|
|
54158
|
+
`, isInline: true, styles: [":host{display:block}.about-grid{padding:16px 0;max-width:720px;margin:0 auto}.about-hero{display:flex;flex-direction:column;align-items:center;gap:8px;padding:24px 0 16px;text-align:center}.about-hero__logo{height:96px;width:auto;display:block}.version-row{display:flex;align-items:center;gap:6px;cursor:pointer;opacity:.85;transition:opacity .15s ease}.version-row:hover{opacity:1}.version-copy-icon{font-size:14px;color:var(--ion-color-medium)}.about-section{padding:16px 0 8px;display:flex;flex-direction:column;gap:8px}.about-actions{display:flex;flex-direction:column;gap:8px}.about-section--reviews val-action-card{display:block}.about-section--reviews val-action-card ::ng-deep .action-card{background:var(--ion-color-light, #f4f5f8);box-shadow:none}.about-section--reviews val-action-card ::ng-deep .action-card__description{color:var(--ion-color-dark);opacity:.7}.about-social{display:flex;flex-wrap:wrap;gap:10px;padding-top:8px}.about-social .social-btn{--border-radius: 50%;--background: var(--ion-color-light, #f4f5f8);--color: var(--ion-color-dark);--padding-start: 0;--padding-end: 0;width:48px;height:48px;margin:0;transition:transform .15s ease}.about-social .social-btn:hover{--background: var(--ion-color-primary-tint, #8a4ce5);--color: #fff;transform:translateY(-2px)}.about-social .social-btn ion-icon{font-size:22px}.about-footer{display:flex;flex-direction:column;align-items:center;gap:4px;padding:24px 0;text-align:center}:host-context(body.dark) val-action-card ::ng-deep .action-card,:host-context(html.ion-palette-dark) val-action-card ::ng-deep .action-card,:host-context([data-theme=\"dark\"]) val-action-card ::ng-deep .action-card{background:#ffffff0a}:host-context(body.dark) .about-section--reviews val-action-card ::ng-deep .action-card:hover,:host-context(html.ion-palette-dark) .about-section--reviews val-action-card ::ng-deep .action-card:hover,:host-context([data-theme=\"dark\"]) .about-section--reviews val-action-card ::ng-deep .action-card:hover{background:#ffffff14}:host-context(body.dark) .about-social .social-btn,:host-context(html.ion-palette-dark) .about-social .social-btn,:host-context([data-theme=\"dark\"]) .about-social .social-btn{--background: rgba(255, 255, 255, .04)}\n"], dependencies: [{ 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: IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: PillComponent, selector: "val-pill", inputs: ["preset", "props"], outputs: ["pillClick", "pillAction"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ActionCardComponent, selector: "val-action-card", inputs: ["props"], outputs: ["onClick"] }] }); }
|
|
54159
|
+
}
|
|
54160
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AboutViewComponent, decorators: [{
|
|
54161
|
+
type: Component,
|
|
54162
|
+
args: [{ selector: 'val-about-view', standalone: true, imports: [IonButton, IonGrid, IonIcon, PillComponent, TitleComponent, TextComponent, ActionCardComponent], template: `
|
|
54163
|
+
<ion-grid class="about-grid">
|
|
54164
|
+
<!-- HERO: logo + name + version -->
|
|
54165
|
+
<div class="about-hero">
|
|
54166
|
+
<img class="about-hero__logo" [src]="logoSrc()" [alt]="brandName()" />
|
|
54167
|
+
<val-title
|
|
54168
|
+
[props]="{
|
|
54169
|
+
content: brandName(),
|
|
54170
|
+
size: 'xlarge',
|
|
54171
|
+
color: 'dark',
|
|
54172
|
+
bold: true,
|
|
54173
|
+
}"
|
|
54174
|
+
/>
|
|
54175
|
+
<val-text
|
|
54176
|
+
[props]="{
|
|
54177
|
+
content: tt('tagline'),
|
|
54178
|
+
size: 'medium',
|
|
54179
|
+
color: 'dark',
|
|
54180
|
+
bold: false,
|
|
54181
|
+
}"
|
|
54182
|
+
/>
|
|
54183
|
+
@if (version()) {
|
|
54184
|
+
<div
|
|
54185
|
+
class="version-row"
|
|
54186
|
+
(click)="copyVersion()"
|
|
54187
|
+
role="button"
|
|
54188
|
+
[attr.aria-label]="tt('versionLabel') + ' ' + version()"
|
|
54189
|
+
>
|
|
54190
|
+
<val-pill
|
|
54191
|
+
[props]="{
|
|
54192
|
+
label: tt('versionLabel') + ' ' + version(),
|
|
54193
|
+
color: 'dark',
|
|
54194
|
+
weight: 'bold',
|
|
54195
|
+
size: 'small',
|
|
54196
|
+
}"
|
|
54197
|
+
/>
|
|
54198
|
+
<ion-icon name="copy-outline" class="version-copy-icon" />
|
|
54199
|
+
</div>
|
|
54200
|
+
}
|
|
54201
|
+
</div>
|
|
54202
|
+
|
|
54203
|
+
<!-- DESCRIPTION -->
|
|
54204
|
+
<section class="about-section">
|
|
54205
|
+
<val-title [props]="sectionTitle('descTitle')" />
|
|
54206
|
+
<val-text
|
|
54207
|
+
[props]="{
|
|
54208
|
+
content: tt('descBody'),
|
|
54209
|
+
size: 'medium',
|
|
54210
|
+
color: 'dark',
|
|
54211
|
+
bold: false,
|
|
54212
|
+
}"
|
|
54213
|
+
/>
|
|
54214
|
+
</section>
|
|
54215
|
+
|
|
54216
|
+
<!-- ACTIONS — rating built-in + acciones del consumer. -->
|
|
54217
|
+
@if (showRating() || actions().length > 0) {
|
|
54218
|
+
<section class="about-section about-section--reviews">
|
|
54219
|
+
<val-title [props]="sectionTitle('actionsTitle')" />
|
|
54220
|
+
<div class="about-actions">
|
|
54221
|
+
@if (showRating()) {
|
|
54222
|
+
<val-action-card [props]="rateActionCard()" (onClick)="openRating()" />
|
|
54223
|
+
}
|
|
54224
|
+
@for (action of actions(); track action.label) {
|
|
54225
|
+
<val-action-card [props]="extraActionCard(action)" (onClick)="action.onClick()" />
|
|
54226
|
+
}
|
|
54227
|
+
</div>
|
|
54228
|
+
</section>
|
|
54229
|
+
}
|
|
54230
|
+
|
|
54231
|
+
<!-- SOCIAL — penúltimo, justo antes del footer de derechos. -->
|
|
54232
|
+
@if (socialLinks().length > 0) {
|
|
54233
|
+
<section class="about-section">
|
|
54234
|
+
<val-title [props]="sectionTitle('socialTitle')" />
|
|
54235
|
+
<val-text
|
|
54236
|
+
[props]="{
|
|
54237
|
+
content: tt('socialDesc'),
|
|
54238
|
+
size: 'medium',
|
|
54239
|
+
color: 'dark',
|
|
54240
|
+
bold: false,
|
|
54241
|
+
}"
|
|
54242
|
+
/>
|
|
54243
|
+
<div class="about-social">
|
|
54244
|
+
@for (s of socialLinks(); track s.url) {
|
|
54245
|
+
<ion-button
|
|
54246
|
+
fill="clear"
|
|
54247
|
+
size="small"
|
|
54248
|
+
class="social-btn"
|
|
54249
|
+
[attr.aria-label]="s.label"
|
|
54250
|
+
(click)="openUrl(s.url)"
|
|
54251
|
+
>
|
|
54252
|
+
<ion-icon [name]="s.icon" slot="icon-only" />
|
|
54253
|
+
</ion-button>
|
|
54254
|
+
}
|
|
54255
|
+
</div>
|
|
54256
|
+
</section>
|
|
54257
|
+
}
|
|
54258
|
+
|
|
54259
|
+
<!-- FOOTER -->
|
|
54260
|
+
<footer class="about-footer">
|
|
54261
|
+
<val-text
|
|
54262
|
+
[props]="{
|
|
54263
|
+
content: copyright(),
|
|
54264
|
+
size: 'small',
|
|
54265
|
+
color: 'dark',
|
|
54266
|
+
bold: false,
|
|
54267
|
+
}"
|
|
54268
|
+
/>
|
|
54269
|
+
</footer>
|
|
54270
|
+
</ion-grid>
|
|
54271
|
+
`, styles: [":host{display:block}.about-grid{padding:16px 0;max-width:720px;margin:0 auto}.about-hero{display:flex;flex-direction:column;align-items:center;gap:8px;padding:24px 0 16px;text-align:center}.about-hero__logo{height:96px;width:auto;display:block}.version-row{display:flex;align-items:center;gap:6px;cursor:pointer;opacity:.85;transition:opacity .15s ease}.version-row:hover{opacity:1}.version-copy-icon{font-size:14px;color:var(--ion-color-medium)}.about-section{padding:16px 0 8px;display:flex;flex-direction:column;gap:8px}.about-actions{display:flex;flex-direction:column;gap:8px}.about-section--reviews val-action-card{display:block}.about-section--reviews val-action-card ::ng-deep .action-card{background:var(--ion-color-light, #f4f5f8);box-shadow:none}.about-section--reviews val-action-card ::ng-deep .action-card__description{color:var(--ion-color-dark);opacity:.7}.about-social{display:flex;flex-wrap:wrap;gap:10px;padding-top:8px}.about-social .social-btn{--border-radius: 50%;--background: var(--ion-color-light, #f4f5f8);--color: var(--ion-color-dark);--padding-start: 0;--padding-end: 0;width:48px;height:48px;margin:0;transition:transform .15s ease}.about-social .social-btn:hover{--background: var(--ion-color-primary-tint, #8a4ce5);--color: #fff;transform:translateY(-2px)}.about-social .social-btn ion-icon{font-size:22px}.about-footer{display:flex;flex-direction:column;align-items:center;gap:4px;padding:24px 0;text-align:center}:host-context(body.dark) val-action-card ::ng-deep .action-card,:host-context(html.ion-palette-dark) val-action-card ::ng-deep .action-card,:host-context([data-theme=\"dark\"]) val-action-card ::ng-deep .action-card{background:#ffffff0a}:host-context(body.dark) .about-section--reviews val-action-card ::ng-deep .action-card:hover,:host-context(html.ion-palette-dark) .about-section--reviews val-action-card ::ng-deep .action-card:hover,:host-context([data-theme=\"dark\"]) .about-section--reviews val-action-card ::ng-deep .action-card:hover{background:#ffffff14}:host-context(body.dark) .about-social .social-btn,:host-context(html.ion-palette-dark) .about-social .social-btn,:host-context([data-theme=\"dark\"]) .about-social .social-btn{--background: rgba(255, 255, 255, .04)}\n"] }]
|
|
54272
|
+
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
54273
|
+
type: Input
|
|
54274
|
+
}] } });
|
|
54275
|
+
|
|
54276
|
+
/**
|
|
54277
|
+
* Configuración acotada de `val-about-view` (object-first). La vista About es
|
|
54278
|
+
* app-specific por naturaleza (logo, nombre, versión, redes, acciones extra),
|
|
54279
|
+
* así que lo propio de cada app se parametriza como **DATA de config** — NO como
|
|
54280
|
+
* slots arbitrarios (ADR-021: una vista full-feature no abre slots; si falta
|
|
54281
|
+
* algo, se promueve un nuevo punto de extensión).
|
|
54282
|
+
*
|
|
54283
|
+
* Ejes de variación permitidos:
|
|
54284
|
+
* - **branding** — `brand.name` / `brand.logoSrc` (qué muestra el HERO).
|
|
54285
|
+
* - **versión** — `version` la pasa el consumer (la lib NO puede importar el
|
|
54286
|
+
* `version.ts` de la app). Si no se pasa, se oculta el pill de versión.
|
|
54287
|
+
* - **redes** — `socialLinks`; si está vacío se oculta la sección social.
|
|
54288
|
+
* - **rating** — `showRating` + `ratingEntityId` + `onRated`.
|
|
54289
|
+
* - **acciones extra** — `actions[]` genéricas que el consumer agrega junto a
|
|
54290
|
+
* la acción de rating built-in.
|
|
54291
|
+
* - **branding i18n** — `i18nNamespace` (default `'Settings.About'`).
|
|
54292
|
+
*/
|
|
54293
|
+
|
|
54294
|
+
/**
|
|
54295
|
+
* Helper para montar la vista About (`val-about-view`) como ruta en una app del
|
|
54296
|
+
* factory. El `config` se pasa por route `data` (`aboutConfig`) y el componente
|
|
54297
|
+
* lo lee como fallback de su `@Input() config` (el `@Input` gana).
|
|
54298
|
+
*
|
|
54299
|
+
* @example
|
|
54300
|
+
* ```ts
|
|
54301
|
+
* // settings.routes.ts
|
|
54302
|
+
* import { APP_VERSION } from '../version';
|
|
54303
|
+
*
|
|
54304
|
+
* export const settingsRoutes: Routes = [
|
|
54305
|
+
* ...provideValtechAboutRoutes({ config: { version: APP_VERSION } }), // → /app/about
|
|
54306
|
+
* { path: 'preferences', loadComponent: () => ... },
|
|
54307
|
+
* ];
|
|
54308
|
+
*
|
|
54309
|
+
* // con redes y acciones extra:
|
|
54310
|
+
* ...provideValtechAboutRoutes({
|
|
54311
|
+
* config: {
|
|
54312
|
+
* version: APP_VERSION,
|
|
54313
|
+
* socialLinks: VALTECH_SOCIAL_LINKS.map(s => ({ icon: s.icon, url: s.url, label: s.name })),
|
|
54314
|
+
* actions: [{ label: 'Compartir', icon: 'share-social-outline', onClick: () => share() }],
|
|
54315
|
+
* },
|
|
54316
|
+
* }),
|
|
54317
|
+
* ```
|
|
54318
|
+
*/
|
|
54319
|
+
function provideValtechAboutRoutes(opts) {
|
|
54320
|
+
return [
|
|
54321
|
+
{
|
|
54322
|
+
path: opts?.path ?? 'about',
|
|
54323
|
+
component: AboutViewComponent,
|
|
54324
|
+
data: { aboutConfig: opts?.config },
|
|
54325
|
+
},
|
|
54326
|
+
];
|
|
54327
|
+
}
|
|
54328
|
+
|
|
52500
54329
|
/** Built-in label sets for the three platform locales. */
|
|
52501
54330
|
const CALLOUT_LABELS = {
|
|
52502
54331
|
es: {
|
|
@@ -58626,263 +60455,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
58626
60455
|
type: Output
|
|
58627
60456
|
}] } });
|
|
58628
60457
|
|
|
58629
|
-
/**
|
|
58630
|
-
* Defaults i18n del componente (es/en). El componente los auto-registra en su
|
|
58631
|
-
* constructor si el consumer no proveyó el namespace `ContentReaction`. Esto
|
|
58632
|
-
* garantiza que NUNCA haya una key faltante: una key faltante se evalúa en el
|
|
58633
|
-
* template por cada change-detection → `console.warn` por CD → con el
|
|
58634
|
-
* `val-debug-console` activo (espeja console.* a un signal) se dispara un loop
|
|
58635
|
-
* infinito que congela el navegador en dev. Auto-registrar lo elimina de raíz.
|
|
58636
|
-
*/
|
|
58637
|
-
const CONTENT_REACTION_I18N = {
|
|
58638
|
-
es: {
|
|
58639
|
-
question: '¿Te resultó útil este contenido?',
|
|
58640
|
-
commentPlaceholder: 'Cuéntanos más (opcional)...',
|
|
58641
|
-
submit: 'Enviar opinión',
|
|
58642
|
-
update: 'Actualizar opinión',
|
|
58643
|
-
thankYou: '¡Gracias por tu opinión!',
|
|
58644
|
-
submitted: 'Tu opinión ha sido registrada',
|
|
58645
|
-
errorSubmitting: 'Error al enviar. Intenta de nuevo.',
|
|
58646
|
-
positiveLabel: 'Sí, mucho',
|
|
58647
|
-
negativeLabel: 'No tanto',
|
|
58648
|
-
negativeFeedbackTitle: '¿Cuéntanos qué no te gustó?',
|
|
58649
|
-
},
|
|
58650
|
-
en: {
|
|
58651
|
-
question: 'Was this content helpful?',
|
|
58652
|
-
commentPlaceholder: 'Tell us more (optional)...',
|
|
58653
|
-
submit: 'Send feedback',
|
|
58654
|
-
update: 'Update feedback',
|
|
58655
|
-
thankYou: 'Thanks for your feedback!',
|
|
58656
|
-
submitted: 'Your feedback has been recorded',
|
|
58657
|
-
errorSubmitting: 'Failed to send. Please try again.',
|
|
58658
|
-
positiveLabel: 'Yes, a lot',
|
|
58659
|
-
negativeLabel: 'Not really',
|
|
58660
|
-
negativeFeedbackTitle: "Tell us what you didn't like?",
|
|
58661
|
-
},
|
|
58662
|
-
};
|
|
58663
|
-
/**
|
|
58664
|
-
* Componente para reacciones de contenido con emojis.
|
|
58665
|
-
*
|
|
58666
|
-
* @example
|
|
58667
|
-
* ```html
|
|
58668
|
-
* <val-content-reaction
|
|
58669
|
-
* [props]="{
|
|
58670
|
-
* entityRef: { entityType: 'article', entityId: 'art-123' },
|
|
58671
|
-
* question: '¿Te fue útil este artículo?'
|
|
58672
|
-
* }"
|
|
58673
|
-
* (reactionSubmit)="onReactionSubmit($event)"
|
|
58674
|
-
* />
|
|
58675
|
-
* ```
|
|
58676
|
-
*/
|
|
58677
|
-
class ContentReactionComponent {
|
|
58678
|
-
constructor() {
|
|
58679
|
-
this.feedbackService = inject(FeedbackService);
|
|
58680
|
-
this.toast = inject(ToastService);
|
|
58681
|
-
this.i18n = inject(I18nService);
|
|
58682
|
-
this.auth = inject(AuthService, { optional: true });
|
|
58683
|
-
this.isConfigured = computed(() => this.feedbackService.isConfigured());
|
|
58684
|
-
this.props = {};
|
|
58685
|
-
this.reactionSubmit = new EventEmitter();
|
|
58686
|
-
this.reactionChange = new EventEmitter();
|
|
58687
|
-
// Estado reactivo
|
|
58688
|
-
this.state = signal({
|
|
58689
|
-
selectedValue: null,
|
|
58690
|
-
comment: '',
|
|
58691
|
-
isLoading: false,
|
|
58692
|
-
isSubmitted: false,
|
|
58693
|
-
hadPreviousReaction: false,
|
|
58694
|
-
error: null,
|
|
58695
|
-
});
|
|
58696
|
-
// Valores por defecto
|
|
58697
|
-
this.defaultEmojis = ['😞', '😐', '😊'];
|
|
58698
|
-
this.defaultLabels = ['No me ayudó', 'Regular', 'Muy útil'];
|
|
58699
|
-
// Computed properties
|
|
58700
|
-
this.resolvedProps = computed(() => ({
|
|
58701
|
-
entityRef: this.props.entityRef,
|
|
58702
|
-
question: this.props.question || this.t('question'),
|
|
58703
|
-
showComment: this.props.showComment ?? true,
|
|
58704
|
-
commentOnValues: this.props.commentOnValues,
|
|
58705
|
-
commentPlaceholder: this.props.commentPlaceholder || this.t('commentPlaceholder'),
|
|
58706
|
-
maxCommentLength: this.props.maxCommentLength ?? 500,
|
|
58707
|
-
emojis: this.props.emojis || this.defaultEmojis,
|
|
58708
|
-
emojiLabels: this.props.emojiLabels || this.defaultLabels,
|
|
58709
|
-
showThankYou: this.props.showThankYou ?? true,
|
|
58710
|
-
thankYouMessage: this.props.thankYouMessage || this.t('thankYou'),
|
|
58711
|
-
disabled: this.props.disabled ?? false,
|
|
58712
|
-
readonly: this.props.readonly ?? false,
|
|
58713
|
-
// Variante: 'emoji' cuando se declara explícitamente o cuando se pasan emojis custom.
|
|
58714
|
-
variant: this.props.variant ?? (this.props.emojis ? 'emoji' : 'buttons'),
|
|
58715
|
-
}));
|
|
58716
|
-
this.activeReactionValues = computed(() => this.resolvedProps().emojis.length === 2 ? ['negative', 'positive'] : ['negative', 'neutral', 'positive']);
|
|
58717
|
-
this.showCommentField = computed(() => {
|
|
58718
|
-
const sel = this.state().selectedValue;
|
|
58719
|
-
if (!sel)
|
|
58720
|
-
return false;
|
|
58721
|
-
const commentOn = this.resolvedProps().commentOnValues;
|
|
58722
|
-
if (commentOn !== undefined)
|
|
58723
|
-
return commentOn.includes(sel);
|
|
58724
|
-
return this.resolvedProps().showComment;
|
|
58725
|
-
});
|
|
58726
|
-
this.canSubmit = computed(() => this.state().selectedValue !== null && !this.state().isLoading);
|
|
58727
|
-
// Auto-registro de defaults i18n (es/en) si el consumer no proveyó el
|
|
58728
|
-
// namespace. Garantiza que `t()` nunca caiga en key faltante. Ver
|
|
58729
|
-
// CONTENT_REACTION_I18N. Idempotente: solo la 1ª instancia registra.
|
|
58730
|
-
if (!this.i18n.hasNamespace('ContentReaction')) {
|
|
58731
|
-
this.i18n.registerContent('ContentReaction', CONTENT_REACTION_I18N);
|
|
58732
|
-
}
|
|
58733
|
-
}
|
|
58734
|
-
ngOnInit() {
|
|
58735
|
-
this.loadPreviousReaction();
|
|
58736
|
-
}
|
|
58737
|
-
ngOnChanges(changes) {
|
|
58738
|
-
if (changes['props'] && !changes['props'].firstChange) {
|
|
58739
|
-
this.loadPreviousReaction();
|
|
58740
|
-
}
|
|
58741
|
-
}
|
|
58742
|
-
async loadPreviousReaction() {
|
|
58743
|
-
if (!this.props.entityRef)
|
|
58744
|
-
return;
|
|
58745
|
-
// Si hay initialValue, usarlo directamente sin consultar
|
|
58746
|
-
if (this.props.initialValue) {
|
|
58747
|
-
this.state.update(s => ({
|
|
58748
|
-
...s,
|
|
58749
|
-
selectedValue: this.props.initialValue,
|
|
58750
|
-
hadPreviousReaction: true,
|
|
58751
|
-
isLoading: false,
|
|
58752
|
-
isSubmitted: true,
|
|
58753
|
-
}));
|
|
58754
|
-
return;
|
|
58755
|
-
}
|
|
58756
|
-
// Si skipInitialCheck está activo, no consultar
|
|
58757
|
-
if (this.props.skipInitialCheck) {
|
|
58758
|
-
return;
|
|
58759
|
-
}
|
|
58760
|
-
this.state.update(s => ({ ...s, isLoading: true, error: null }));
|
|
58761
|
-
try {
|
|
58762
|
-
const check = await this.feedbackService.checkFeedback(this.props.entityRef.entityType, this.props.entityRef.entityId);
|
|
58763
|
-
if (check.hasFeedback && check.reactionValue) {
|
|
58764
|
-
this.state.update(s => ({
|
|
58765
|
-
...s,
|
|
58766
|
-
selectedValue: check.reactionValue,
|
|
58767
|
-
hadPreviousReaction: true,
|
|
58768
|
-
isLoading: false,
|
|
58769
|
-
isSubmitted: true,
|
|
58770
|
-
}));
|
|
58771
|
-
}
|
|
58772
|
-
else {
|
|
58773
|
-
this.state.update(s => ({ ...s, isLoading: false }));
|
|
58774
|
-
}
|
|
58775
|
-
}
|
|
58776
|
-
catch (error) {
|
|
58777
|
-
console.error('Error loading previous reaction:', error);
|
|
58778
|
-
this.state.update(s => ({ ...s, isLoading: false }));
|
|
58779
|
-
}
|
|
58780
|
-
}
|
|
58781
|
-
selectReaction(value) {
|
|
58782
|
-
if (this.resolvedProps().disabled || this.resolvedProps().readonly)
|
|
58783
|
-
return;
|
|
58784
|
-
const previousValue = this.state().selectedValue;
|
|
58785
|
-
this.state.update(s => ({
|
|
58786
|
-
...s,
|
|
58787
|
-
selectedValue: value,
|
|
58788
|
-
isSubmitted: false,
|
|
58789
|
-
error: null,
|
|
58790
|
-
}));
|
|
58791
|
-
this.reactionChange.emit({ value, previousValue });
|
|
58792
|
-
}
|
|
58793
|
-
async submitReaction() {
|
|
58794
|
-
const currentState = this.state();
|
|
58795
|
-
const props = this.resolvedProps();
|
|
58796
|
-
if (!currentState.selectedValue || props.disabled)
|
|
58797
|
-
return;
|
|
58798
|
-
this.state.update(s => ({ ...s, isLoading: true, error: null }));
|
|
58799
|
-
try {
|
|
58800
|
-
// Determinar si usar endpoint anónimo o autenticado
|
|
58801
|
-
const isAuthenticated = this.auth?.isAuthenticated() ?? false;
|
|
58802
|
-
const useAnonymous = this.props.allowAnonymous && !isAuthenticated;
|
|
58803
|
-
if (useAnonymous) {
|
|
58804
|
-
// Usar endpoint anónimo (público)
|
|
58805
|
-
await this.feedbackService.createAnonymousReaction(props.entityRef, currentState.selectedValue, currentState.comment || undefined);
|
|
58806
|
-
}
|
|
58807
|
-
else {
|
|
58808
|
-
// Usar endpoint autenticado
|
|
58809
|
-
await this.feedbackService.createReaction(props.entityRef, currentState.selectedValue, currentState.comment || undefined);
|
|
58810
|
-
}
|
|
58811
|
-
this.state.update(s => ({
|
|
58812
|
-
...s,
|
|
58813
|
-
isLoading: false,
|
|
58814
|
-
isSubmitted: true,
|
|
58815
|
-
comment: '',
|
|
58816
|
-
hadPreviousReaction: !useAnonymous, // Solo para autenticados
|
|
58817
|
-
}));
|
|
58818
|
-
this.reactionSubmit.emit({
|
|
58819
|
-
value: currentState.selectedValue,
|
|
58820
|
-
comment: currentState.comment || undefined,
|
|
58821
|
-
entityRef: props.entityRef,
|
|
58822
|
-
isUpdate: currentState.hadPreviousReaction,
|
|
58823
|
-
});
|
|
58824
|
-
if (props.showThankYou) {
|
|
58825
|
-
this.toast.show({
|
|
58826
|
-
message: props.thankYouMessage,
|
|
58827
|
-
duration: 2000,
|
|
58828
|
-
});
|
|
58829
|
-
}
|
|
58830
|
-
}
|
|
58831
|
-
catch (error) {
|
|
58832
|
-
console.error('Error submitting reaction:', error);
|
|
58833
|
-
this.state.update(s => ({
|
|
58834
|
-
...s,
|
|
58835
|
-
isLoading: false,
|
|
58836
|
-
error: this.t('errorSubmitting'),
|
|
58837
|
-
}));
|
|
58838
|
-
this.toast.show({
|
|
58839
|
-
message: this.t('errorSubmitting'),
|
|
58840
|
-
duration: 3000,
|
|
58841
|
-
});
|
|
58842
|
-
}
|
|
58843
|
-
}
|
|
58844
|
-
updateComment(event) {
|
|
58845
|
-
const value = event.detail.value || '';
|
|
58846
|
-
this.state.update(s => ({ ...s, comment: value }));
|
|
58847
|
-
}
|
|
58848
|
-
getEmoji(index) {
|
|
58849
|
-
return this.resolvedProps().emojis[index];
|
|
58850
|
-
}
|
|
58851
|
-
getEmojiLabel(index) {
|
|
58852
|
-
return this.resolvedProps().emojiLabels[index];
|
|
58853
|
-
}
|
|
58854
|
-
isSelected(value) {
|
|
58855
|
-
return this.state().selectedValue === value;
|
|
58856
|
-
}
|
|
58857
|
-
t(key) {
|
|
58858
|
-
const translations = {
|
|
58859
|
-
question: '¿Te resultó útil este contenido?',
|
|
58860
|
-
commentPlaceholder: 'Cuéntanos más (opcional)...',
|
|
58861
|
-
submit: 'Enviar opinión',
|
|
58862
|
-
update: 'Actualizar opinión',
|
|
58863
|
-
thankYou: '¡Gracias por tu opinión!',
|
|
58864
|
-
submitted: 'Tu opinión ha sido registrada',
|
|
58865
|
-
errorSubmitting: 'Error al enviar. Intenta de nuevo.',
|
|
58866
|
-
positiveLabel: 'Sí, mucho',
|
|
58867
|
-
negativeLabel: 'No tanto',
|
|
58868
|
-
negativeFeedbackTitle: '¿Cuéntanos qué no te gustó?',
|
|
58869
|
-
};
|
|
58870
|
-
return this.i18n.t(key, 'ContentReaction') || translations[key] || key;
|
|
58871
|
-
}
|
|
58872
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
58873
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ContentReactionComponent, isStandalone: true, selector: "val-content-reaction", inputs: { props: "props" }, outputs: { reactionSubmit: "reactionSubmit", reactionChange: "reactionChange" }, usesOnChanges: true, ngImport: i0, template: "@if (isConfigured()) {\n<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n}\n", styles: [":host{display:block}.content-reaction{padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.question{font-size:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:.875rem}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }] }); }
|
|
58874
|
-
}
|
|
58875
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, decorators: [{
|
|
58876
|
-
type: Component,
|
|
58877
|
-
args: [{ selector: 'val-content-reaction', standalone: true, imports: [CommonModule, FormsModule, IonButton, IonSpinner, IonTextarea], template: "@if (isConfigured()) {\n<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" shape=\"round\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n}\n", styles: [":host{display:block}.content-reaction{padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.question{font-size:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:.875rem}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
|
|
58878
|
-
}], ctorParameters: () => [], propDecorators: { props: [{
|
|
58879
|
-
type: Input
|
|
58880
|
-
}], reactionSubmit: [{
|
|
58881
|
-
type: Output
|
|
58882
|
-
}], reactionChange: [{
|
|
58883
|
-
type: Output
|
|
58884
|
-
}] } });
|
|
58885
|
-
|
|
58886
60458
|
const DEFAULT_SPLASH_SCREEN_CONFIG = {
|
|
58887
60459
|
fadeOutDuration: 300,
|
|
58888
60460
|
};
|
|
@@ -62735,5 +64307,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
62735
64307
|
* Generated bundle index. Do not edit.
|
|
62736
64308
|
*/
|
|
62737
64309
|
|
|
62738
|
-
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, AccountViewComponent, 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, CreateOrgModalComponent, 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, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, 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, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, 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, PermissionCatalogService, PermissionsViewComponent, 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, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_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, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
64310
|
+
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, AboutViewComponent, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, ApiKeyService, ApiKeysModalComponent, 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, CreateOrgModalComponent, 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, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, 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, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MemberImportModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, 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, PermissionCatalogService, PermissionsViewComponent, 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, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_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, provideValtechAboutRoutes, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
62739
64311
|
//# sourceMappingURL=valtech-components.mjs.map
|