valtech-components 2.0.988 → 2.0.990
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/components/atoms/button/button.component.mjs +6 -3
- package/esm2022/lib/components/organisms/edit-org-modal/edit-org-modal.component.mjs +229 -0
- package/esm2022/lib/components/organisms/edit-org-modal/edit-org-modal.i18n.mjs +38 -0
- package/esm2022/lib/components/organisms/invite-member-modal/invite-member-modal.component.mjs +519 -0
- package/esm2022/lib/components/organisms/invite-member-modal/invite-member-modal.i18n.mjs +54 -0
- package/esm2022/lib/components/organisms/member-detail-modal/member-detail-modal.component.mjs +406 -0
- package/esm2022/lib/components/organisms/member-detail-modal/member-detail-modal.i18n.mjs +72 -0
- package/esm2022/lib/components/organisms/member-detail-modal/types.mjs +2 -0
- package/esm2022/lib/components/organisms/organization-view/organization-view.component.mjs +1065 -0
- package/esm2022/lib/components/organisms/organization-view/organization-view.i18n.mjs +148 -0
- package/esm2022/lib/components/organisms/organization-view/organization.routes.mjs +30 -0
- package/esm2022/lib/components/organisms/organization-view/types.mjs +2 -0
- package/esm2022/lib/components/organisms/transfer-ownership-modal/transfer-ownership-modal.component.mjs +231 -0
- package/esm2022/lib/components/organisms/transfer-ownership-modal/transfer-ownership-modal.i18n.mjs +27 -0
- package/esm2022/lib/components/organisms/transfer-ownership-modal/types.mjs +2 -0
- package/esm2022/lib/components/types.mjs +1 -1
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +10 -1
- package/fesm2022/valtech-components.mjs +2755 -30
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/organisms/article/article.component.d.ts +1 -0
- package/lib/components/organisms/edit-org-modal/edit-org-modal.component.d.ts +49 -0
- package/lib/components/organisms/edit-org-modal/edit-org-modal.i18n.d.ts +7 -0
- package/lib/components/organisms/invite-member-modal/invite-member-modal.component.d.ts +74 -0
- package/lib/components/organisms/invite-member-modal/invite-member-modal.i18n.d.ts +7 -0
- package/lib/components/organisms/member-detail-modal/member-detail-modal.component.d.ts +67 -0
- package/lib/components/organisms/member-detail-modal/member-detail-modal.i18n.d.ts +7 -0
- package/lib/components/organisms/member-detail-modal/types.d.ts +17 -0
- package/lib/components/organisms/organization-view/organization-view.component.d.ts +104 -0
- package/lib/components/organisms/organization-view/organization-view.i18n.d.ts +9 -0
- package/lib/components/organisms/organization-view/organization.routes.d.ts +25 -0
- package/lib/components/organisms/organization-view/types.d.ts +61 -0
- package/lib/components/organisms/transfer-ownership-modal/transfer-ownership-modal.component.d.ts +44 -0
- package/lib/components/organisms/transfer-ownership-modal/transfer-ownership-modal.i18n.d.ts +8 -0
- package/lib/components/organisms/transfer-ownership-modal/types.d.ts +11 -0
- package/lib/components/types.d.ts +2 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +9 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, InjectionToken, makeEnvironmentProviders, APP_INITIALIZER, ErrorHandler, Injectable, DestroyRef, Inject, signal, computed, PLATFORM_ID, isDevMode, Optional, runInInjectionContext, effect, Pipe, TemplateRef, ViewContainerRef, input, Directive, EventEmitter, Component, Input, Output, ChangeDetectionStrategy, HostBinding, HostListener, NgZone, ViewChild, ChangeDetectorRef, output, ContentChild, ElementRef, viewChild, untracked, Injector, isSignal, ViewEncapsulation } from '@angular/core';
|
|
3
3
|
import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, shareReplay, race, timer, forkJoin } from 'rxjs';
|
|
4
|
-
import { catchError, switchMap, finalize, filter, take, map as map$1, tap, retry, timeout, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
|
|
4
|
+
import { catchError, switchMap, finalize, filter, take, map as map$1, tap, distinctUntilChanged as distinctUntilChanged$1, retry, timeout, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
|
|
5
5
|
import * as i1$3 from '@angular/common/http';
|
|
6
6
|
import { provideHttpClient, withInterceptors, HttpClient, HttpErrorResponse } from '@angular/common/http';
|
|
7
7
|
import * as i1$1 from '@angular/fire/firestore';
|
|
8
|
-
import { provideFirestore, getFirestore, connectFirestoreEmulator, enableIndexedDbPersistence, doc, getDoc, collection, query as query$1, getDocs, getCountFromServer, limit, docData, collectionData, serverTimestamp, addDoc, setDoc, updateDoc, deleteDoc, writeBatch, arrayUnion, arrayRemove, increment, where, orderBy, startAfter, startAt, endBefore, endAt, Timestamp, onSnapshot } from '@angular/fire/firestore';
|
|
8
|
+
import { provideFirestore, getFirestore, connectFirestoreEmulator, enableIndexedDbPersistence, doc, getDoc, collection, query as query$1, getDocs, getCountFromServer, limit, docData, collectionData, serverTimestamp, addDoc, setDoc, updateDoc, deleteDoc, writeBatch, arrayUnion, arrayRemove, increment, where, orderBy, startAfter, startAt, endBefore, endAt, Timestamp, onSnapshot, Firestore } from '@angular/fire/firestore';
|
|
9
9
|
import * as i1$5 from '@angular/common';
|
|
10
10
|
import { isPlatformBrowser, CommonModule, NgStyle, NgFor, DOCUMENT, NgClass, TitleCasePipe } from '@angular/common';
|
|
11
11
|
import { provideAnalytics, getAnalytics, Analytics, logEvent, setUserId, setUserProperties } from '@angular/fire/analytics';
|
|
@@ -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.990';
|
|
58
58
|
|
|
59
59
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
60
60
|
let isRefreshing = false;
|
|
@@ -10497,6 +10497,7 @@ class ButtonComponent {
|
|
|
10497
10497
|
[fill]="resolvedProps.fill"
|
|
10498
10498
|
[size]="resolvedProps.size"
|
|
10499
10499
|
[href]="resolvedProps.href"
|
|
10500
|
+
[routerLink]="resolvedProps.routerLink ?? null"
|
|
10500
10501
|
[target]="resolvedProps.target"
|
|
10501
10502
|
[shape]="resolvedProps.shape"
|
|
10502
10503
|
(click)="clickHandler()"
|
|
@@ -10507,11 +10508,11 @@ class ButtonComponent {
|
|
|
10507
10508
|
<ion-spinner *ngIf="resolvedProps.state === states.WORKING" name="circular"></ion-spinner>
|
|
10508
10509
|
<ion-text *ngIf="resolvedProps.state !== states.WORKING">{{ displayText }}</ion-text>
|
|
10509
10510
|
</ion-button>
|
|
10510
|
-
`, 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)}ion-button{font-family:var(--ion-default-font),Arial,sans-serif;min-width:6.25rem;margin:0}ion-button.small{--padding-bottom: 12px;--padding-end: 14px;--padding-start: 14px;--padding-top: 12px}ion-button.button-clear{padding:0;margin:0}ion-button ion-spinner{--color: currentColor}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }] }); }
|
|
10511
|
+
`, 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)}ion-button{font-family:var(--ion-default-font),Arial,sans-serif;min-width:6.25rem;margin:0}ion-button.small{--padding-bottom: 12px;--padding-end: 14px;--padding-start: 14px;--padding-top: 12px}ion-button.button-clear{padding:0;margin:0}ion-button ion-spinner{--color: currentColor}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }] }); }
|
|
10511
10512
|
}
|
|
10512
10513
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonComponent, decorators: [{
|
|
10513
10514
|
type: Component,
|
|
10514
|
-
args: [{ selector: 'val-button', standalone: true, imports: [CommonModule, IonButton, IonIcon, IonSpinner, IonText], template: `
|
|
10515
|
+
args: [{ selector: 'val-button', standalone: true, imports: [CommonModule, RouterLink, IonButton, IonIcon, IonSpinner, IonText], template: `
|
|
10515
10516
|
<ion-button
|
|
10516
10517
|
[attr.data-testid]="resolvedProps.token"
|
|
10517
10518
|
[type]="resolvedProps.type"
|
|
@@ -10520,6 +10521,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
10520
10521
|
[fill]="resolvedProps.fill"
|
|
10521
10522
|
[size]="resolvedProps.size"
|
|
10522
10523
|
[href]="resolvedProps.href"
|
|
10524
|
+
[routerLink]="resolvedProps.routerLink ?? null"
|
|
10523
10525
|
[target]="resolvedProps.target"
|
|
10524
10526
|
[shape]="resolvedProps.shape"
|
|
10525
10527
|
(click)="clickHandler()"
|
|
@@ -41735,7 +41737,7 @@ const PROFILE_VIEW_I18N = {
|
|
|
41735
41737
|
const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
|
|
41736
41738
|
const HANDLE_MIN = 3;
|
|
41737
41739
|
const HANDLE_MAX = 20;
|
|
41738
|
-
const DEFAULT_NAMESPACE$
|
|
41740
|
+
const DEFAULT_NAMESPACE$c = 'Settings.Profile';
|
|
41739
41741
|
/**
|
|
41740
41742
|
* `val-profile-view` — vista Perfil full-feature autocontenida (organism).
|
|
41741
41743
|
*
|
|
@@ -41785,7 +41787,7 @@ class ProfileViewComponent {
|
|
|
41785
41787
|
showPhone: merged.showPhone ?? true,
|
|
41786
41788
|
showShareCta: merged.showShareCta ?? true,
|
|
41787
41789
|
profileBaseUrl: merged.profileBaseUrl ?? '',
|
|
41788
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
41790
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$c,
|
|
41789
41791
|
onSaved: merged.onSaved,
|
|
41790
41792
|
onAvatarUploaded: merged.onAvatarUploaded,
|
|
41791
41793
|
};
|
|
@@ -42701,7 +42703,7 @@ const PREFERENCES_VIEW_I18N = {
|
|
|
42701
42703
|
},
|
|
42702
42704
|
};
|
|
42703
42705
|
|
|
42704
|
-
const DEFAULT_NAMESPACE$
|
|
42706
|
+
const DEFAULT_NAMESPACE$b = 'Settings.General';
|
|
42705
42707
|
const DEFAULT_LANGUAGES = ['es', 'en'];
|
|
42706
42708
|
/**
|
|
42707
42709
|
* `val-preferences-view` — vista de preferencias de UI cross-app (tema +
|
|
@@ -42738,7 +42740,7 @@ class PreferencesViewComponent {
|
|
|
42738
42740
|
showLanguage: merged.showLanguage ?? true,
|
|
42739
42741
|
showFontSize: merged.showFontSize ?? true,
|
|
42740
42742
|
supportedLanguages: merged.supportedLanguages ?? DEFAULT_LANGUAGES,
|
|
42741
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
42743
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$b,
|
|
42742
42744
|
onThemeChanged: merged.onThemeChanged,
|
|
42743
42745
|
onLanguageChanged: merged.onLanguageChanged,
|
|
42744
42746
|
onFontSizeChanged: merged.onFontSizeChanged,
|
|
@@ -44550,7 +44552,7 @@ const SECURITY_VIEW_I18N = {
|
|
|
44550
44552
|
};
|
|
44551
44553
|
|
|
44552
44554
|
addIcons({ laptopOutline, phonePortraitOutline });
|
|
44553
|
-
const DEFAULT_NAMESPACE$
|
|
44555
|
+
const DEFAULT_NAMESPACE$a = 'Settings.Security';
|
|
44554
44556
|
/**
|
|
44555
44557
|
* `val-security-view` — vista Seguridad full-feature autocontenida (organism).
|
|
44556
44558
|
*
|
|
@@ -44599,7 +44601,7 @@ class SecurityViewComponent {
|
|
|
44599
44601
|
showChangeEmail: merged.showChangeEmail ?? true,
|
|
44600
44602
|
showMfa: merged.showMfa ?? true,
|
|
44601
44603
|
showPolicies: merged.showPolicies ?? true,
|
|
44602
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
44604
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$a,
|
|
44603
44605
|
handleDeepLinks: merged.handleDeepLinks ?? true,
|
|
44604
44606
|
homeRoute: merged.homeRoute ?? '/app/home',
|
|
44605
44607
|
onMfaCompleted: merged.onMfaCompleted,
|
|
@@ -45505,7 +45507,7 @@ const CREATE_ORG_MODAL_I18N = {
|
|
|
45505
45507
|
},
|
|
45506
45508
|
};
|
|
45507
45509
|
|
|
45508
|
-
const DEFAULT_NAMESPACE$
|
|
45510
|
+
const DEFAULT_NAMESPACE$9 = 'CreateOrgModal';
|
|
45509
45511
|
/**
|
|
45510
45512
|
* `val-create-org-modal` — modal de creación de organización (organism
|
|
45511
45513
|
* compartido). Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por
|
|
@@ -45531,10 +45533,10 @@ class CreateOrgModalComponent {
|
|
|
45531
45533
|
this.toast = inject(ToastService);
|
|
45532
45534
|
this.errors = inject(ValtechErrorService);
|
|
45533
45535
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
45534
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
45536
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$9;
|
|
45535
45537
|
this._busy = signal(false);
|
|
45536
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
45537
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
45538
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$9)) {
|
|
45539
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$9, CREATE_ORG_MODAL_I18N);
|
|
45538
45540
|
}
|
|
45539
45541
|
this.formMeta = signal(this.buildFormMeta());
|
|
45540
45542
|
effect(() => {
|
|
@@ -45770,7 +45772,7 @@ const DELETE_ACCOUNT_MODAL_I18N = {
|
|
|
45770
45772
|
},
|
|
45771
45773
|
};
|
|
45772
45774
|
|
|
45773
|
-
const DEFAULT_NAMESPACE$
|
|
45775
|
+
const DEFAULT_NAMESPACE$8 = 'Settings.DeleteAccount';
|
|
45774
45776
|
/**
|
|
45775
45777
|
* `val-delete-account-modal` — modal de eliminación de cuenta (organism,
|
|
45776
45778
|
* cuenta-específico). Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
@@ -45796,7 +45798,7 @@ class DeleteAccountModalComponent {
|
|
|
45796
45798
|
this.i18n = inject(I18nService);
|
|
45797
45799
|
this.toast = inject(ToastService);
|
|
45798
45800
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
45799
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
45801
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$8;
|
|
45800
45802
|
this.hasPassword = signal(null);
|
|
45801
45803
|
this.codeSent = signal(false);
|
|
45802
45804
|
this.formState = signal(ComponentStates.ENABLED);
|
|
@@ -45863,8 +45865,8 @@ class DeleteAccountModalComponent {
|
|
|
45863
45865
|
],
|
|
45864
45866
|
actions: this.submitBtn('delete-code-submit', 'confirmCta'),
|
|
45865
45867
|
}));
|
|
45866
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
45867
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
45868
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$8)) {
|
|
45869
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$8, DELETE_ACCOUNT_MODAL_I18N);
|
|
45868
45870
|
}
|
|
45869
45871
|
}
|
|
45870
45872
|
ngOnInit() {
|
|
@@ -46065,7 +46067,7 @@ const ORG_INFO_SHEET_I18N = {
|
|
|
46065
46067
|
},
|
|
46066
46068
|
};
|
|
46067
46069
|
|
|
46068
|
-
const DEFAULT_NAMESPACE$
|
|
46070
|
+
const DEFAULT_NAMESPACE$7 = 'OrgInfoSheet';
|
|
46069
46071
|
/**
|
|
46070
46072
|
* `val-org-info-sheet` — sheet informativo "¿Qué son las organizaciones?".
|
|
46071
46073
|
* Extraído del `OrgInfoSheetComponent` inline de `AccountPage` bajo el proceso de
|
|
@@ -46110,15 +46112,15 @@ class OrgInfoSheetComponent {
|
|
|
46110
46112
|
text: this.closeProps.text || this.resolvedCloseLabel(),
|
|
46111
46113
|
}));
|
|
46112
46114
|
addIcons({ peopleOutline, swapHorizontalOutline, mailOutline });
|
|
46113
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
46114
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
46115
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
|
|
46116
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$7, ORG_INFO_SHEET_I18N);
|
|
46115
46117
|
}
|
|
46116
46118
|
}
|
|
46117
46119
|
dismiss() {
|
|
46118
46120
|
this._modalRef?.dismiss(null, 'cancel');
|
|
46119
46121
|
}
|
|
46120
46122
|
tt(key) {
|
|
46121
|
-
return this.i18n.t(key, DEFAULT_NAMESPACE$
|
|
46123
|
+
return this.i18n.t(key, DEFAULT_NAMESPACE$7);
|
|
46122
46124
|
}
|
|
46123
46125
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgInfoSheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
46124
46126
|
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: `
|
|
@@ -46285,7 +46287,7 @@ const SWITCH_ORG_MODAL_I18N = {
|
|
|
46285
46287
|
},
|
|
46286
46288
|
};
|
|
46287
46289
|
|
|
46288
|
-
const DEFAULT_NAMESPACE$
|
|
46290
|
+
const DEFAULT_NAMESPACE$6 = 'Settings.SwitchOrg';
|
|
46289
46291
|
addIcons({ businessOutline, checkmarkCircleOutline });
|
|
46290
46292
|
/**
|
|
46291
46293
|
* `val-switch-org-modal` — modal de cambio de organización activa (organism
|
|
@@ -46311,7 +46313,7 @@ class SwitchOrgModalComponent {
|
|
|
46311
46313
|
this.orgSwitch = inject(OrgSwitchService);
|
|
46312
46314
|
this.errors = inject(ValtechErrorService);
|
|
46313
46315
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
46314
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
46316
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$6;
|
|
46315
46317
|
this.orgs = signal([]);
|
|
46316
46318
|
this.loading = signal(false);
|
|
46317
46319
|
this.query = signal('');
|
|
@@ -46327,8 +46329,8 @@ class SwitchOrgModalComponent {
|
|
|
46327
46329
|
return list;
|
|
46328
46330
|
return list.filter(o => o.name.toLowerCase().includes(q));
|
|
46329
46331
|
});
|
|
46330
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
46331
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
46332
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$6)) {
|
|
46333
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$6, SWITCH_ORG_MODAL_I18N);
|
|
46332
46334
|
}
|
|
46333
46335
|
this.loadOrgs();
|
|
46334
46336
|
}
|
|
@@ -46646,7 +46648,7 @@ const ACCOUNT_VIEW_I18N = {
|
|
|
46646
46648
|
};
|
|
46647
46649
|
|
|
46648
46650
|
addIcons({ peopleOutline, businessOutline, chevronForwardOutline, swapHorizontalOutline, mailOutline });
|
|
46649
|
-
const DEFAULT_NAMESPACE = 'Settings.Account';
|
|
46651
|
+
const DEFAULT_NAMESPACE$5 = 'Settings.Account';
|
|
46650
46652
|
const DEFAULT_MANAGE_ORG_ROUTE = '/app/settings/organization';
|
|
46651
46653
|
/**
|
|
46652
46654
|
* `val-account-view` — vista Cuenta full-feature autocontenida (organism).
|
|
@@ -46687,7 +46689,7 @@ class AccountViewComponent {
|
|
|
46687
46689
|
showNewOrgCta: merged.showNewOrgCta ?? true,
|
|
46688
46690
|
showLogout: merged.showLogout ?? true,
|
|
46689
46691
|
showDeleteAccount: merged.showDeleteAccount ?? true,
|
|
46690
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
46692
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$5,
|
|
46691
46693
|
manageOrgRoute: merged.manageOrgRoute ?? DEFAULT_MANAGE_ORG_ROUTE,
|
|
46692
46694
|
onOrgSwitched: merged.onOrgSwitched,
|
|
46693
46695
|
onOrgCreated: merged.onOrgCreated,
|
|
@@ -47374,6 +47376,2729 @@ function provideValtechAccountRoutes(opts) {
|
|
|
47374
47376
|
];
|
|
47375
47377
|
}
|
|
47376
47378
|
|
|
47379
|
+
/**
|
|
47380
|
+
* Defaults i18n (es/en) embebidos en `val-edit-org-modal`. Auto-registrados en
|
|
47381
|
+
* el constructor del componente si el consumer no proveyó el namespace
|
|
47382
|
+
* (`EditOrgModal` por default). SOLO incluye las keys que el modal usa.
|
|
47383
|
+
*/
|
|
47384
|
+
const EDIT_ORG_MODAL_I18N = {
|
|
47385
|
+
es: {
|
|
47386
|
+
title: 'Editar organización',
|
|
47387
|
+
subtitle: 'Actualiza el nombre y descripción.',
|
|
47388
|
+
nameLabel: 'Nombre',
|
|
47389
|
+
namePlaceholder: 'Nombre de la organización',
|
|
47390
|
+
nameRequired: 'El nombre es obligatorio.',
|
|
47391
|
+
nameMin: 'Mínimo 2 caracteres.',
|
|
47392
|
+
descriptionLabel: 'Descripción (opcional)',
|
|
47393
|
+
descriptionPlaceholder: 'Describe tu organización',
|
|
47394
|
+
saveCta: 'Guardar cambios',
|
|
47395
|
+
close: 'Cancelar',
|
|
47396
|
+
saveSuccess: 'Organización actualizada.',
|
|
47397
|
+
saveError: 'No se pudo guardar. Intenta de nuevo.',
|
|
47398
|
+
fillRequired: 'Completa todos los campos requeridos.',
|
|
47399
|
+
},
|
|
47400
|
+
en: {
|
|
47401
|
+
title: 'Edit organization',
|
|
47402
|
+
subtitle: 'Update the name and description.',
|
|
47403
|
+
nameLabel: 'Name',
|
|
47404
|
+
namePlaceholder: 'Organization name',
|
|
47405
|
+
nameRequired: 'Name is required.',
|
|
47406
|
+
nameMin: 'Minimum 2 characters.',
|
|
47407
|
+
descriptionLabel: 'Description (optional)',
|
|
47408
|
+
descriptionPlaceholder: 'Describe your organization',
|
|
47409
|
+
saveCta: 'Save changes',
|
|
47410
|
+
close: 'Cancel',
|
|
47411
|
+
saveSuccess: 'Organization updated.',
|
|
47412
|
+
saveError: 'Could not save. Please try again.',
|
|
47413
|
+
fillRequired: 'Please fill in all required fields.',
|
|
47414
|
+
},
|
|
47415
|
+
};
|
|
47416
|
+
|
|
47417
|
+
const DEFAULT_NAMESPACE$4 = 'EditOrgModal';
|
|
47418
|
+
/**
|
|
47419
|
+
* `val-edit-org-modal` — modal de edición de organización (nombre + descripción).
|
|
47420
|
+
* Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por la vista de
|
|
47421
|
+
* gestión de organización (y futuras vistas org-aware).
|
|
47422
|
+
*
|
|
47423
|
+
* Edita la org vía `OrgService.updateOrg`. El formulario se construye una vez y se
|
|
47424
|
+
* mantiene reactivo a idioma + estado de trabajo vía effects `allowSignalWrites`.
|
|
47425
|
+
* Si el consumer no pasa `org`, cae al `activeOrgId` del `AuthService`.
|
|
47426
|
+
*
|
|
47427
|
+
* Abierto vía `ModalService` — recibe `_modalRef` por `componentProps` + callback
|
|
47428
|
+
* `onSuccess(updated)`. Header canónico (Regla #5): sin `ion-title`, botón Cerrar
|
|
47429
|
+
* texto en `slot=end`, título/subtítulo en el body con `val-display`/`val-title`.
|
|
47430
|
+
*
|
|
47431
|
+
* Auto-registra sus defaults i18n (es/en) si el consumer no proveyó el namespace
|
|
47432
|
+
* `EditOrgModal`.
|
|
47433
|
+
*/
|
|
47434
|
+
class EditOrgModalComponent {
|
|
47435
|
+
constructor() {
|
|
47436
|
+
this.i18n = inject(I18nService);
|
|
47437
|
+
this.orgService = inject(OrgService);
|
|
47438
|
+
this.toast = inject(ToastService);
|
|
47439
|
+
this.errors = inject(ValtechErrorService);
|
|
47440
|
+
this.auth = inject(AuthService);
|
|
47441
|
+
/** Organización a editar (pasada por `componentProps`). */
|
|
47442
|
+
this.org = null;
|
|
47443
|
+
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
47444
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$4;
|
|
47445
|
+
this.saving = signal(false);
|
|
47446
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$4)) {
|
|
47447
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$4, EDIT_ORG_MODAL_I18N);
|
|
47448
|
+
}
|
|
47449
|
+
this.formMeta = signal(this.buildFormMeta());
|
|
47450
|
+
effect(() => {
|
|
47451
|
+
this.i18n.lang();
|
|
47452
|
+
this.formMeta.set(this.buildFormMeta());
|
|
47453
|
+
}, { allowSignalWrites: true });
|
|
47454
|
+
effect(() => {
|
|
47455
|
+
const w = this.saving();
|
|
47456
|
+
this.formMeta.update(m => ({
|
|
47457
|
+
...m,
|
|
47458
|
+
state: w ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
47459
|
+
actions: {
|
|
47460
|
+
...m.actions,
|
|
47461
|
+
state: w ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
47462
|
+
},
|
|
47463
|
+
}));
|
|
47464
|
+
}, { allowSignalWrites: true });
|
|
47465
|
+
}
|
|
47466
|
+
ngOnInit() {
|
|
47467
|
+
this.formMeta.set(this.buildFormMeta());
|
|
47468
|
+
}
|
|
47469
|
+
get activeOrgId() {
|
|
47470
|
+
const u = this.auth.user();
|
|
47471
|
+
return u?.activeOrgId ?? u?.activeOrg ?? '';
|
|
47472
|
+
}
|
|
47473
|
+
buildFormMeta() {
|
|
47474
|
+
const nameField = {
|
|
47475
|
+
token: 'edit-org-name',
|
|
47476
|
+
name: 'name',
|
|
47477
|
+
label: this.t('nameLabel'),
|
|
47478
|
+
hint: '',
|
|
47479
|
+
placeholder: this.t('namePlaceholder'),
|
|
47480
|
+
type: InputType.TEXT,
|
|
47481
|
+
order: 1,
|
|
47482
|
+
validators: [Validators.required, Validators.minLength(2), Validators.maxLength(100)],
|
|
47483
|
+
errors: {
|
|
47484
|
+
required: this.t('nameRequired'),
|
|
47485
|
+
minlength: this.t('nameMin'),
|
|
47486
|
+
},
|
|
47487
|
+
value: this.org?.name ?? '',
|
|
47488
|
+
state: ComponentStates.ENABLED,
|
|
47489
|
+
};
|
|
47490
|
+
const descField = {
|
|
47491
|
+
token: 'edit-org-desc',
|
|
47492
|
+
name: 'description',
|
|
47493
|
+
label: this.t('descriptionLabel'),
|
|
47494
|
+
hint: '',
|
|
47495
|
+
placeholder: this.t('descriptionPlaceholder'),
|
|
47496
|
+
type: InputType.TEXTAREA,
|
|
47497
|
+
order: 2,
|
|
47498
|
+
validators: [],
|
|
47499
|
+
errors: {},
|
|
47500
|
+
value: this.org?.description ?? '',
|
|
47501
|
+
state: ComponentStates.ENABLED,
|
|
47502
|
+
};
|
|
47503
|
+
return {
|
|
47504
|
+
name: '',
|
|
47505
|
+
state: ComponentStates.ENABLED,
|
|
47506
|
+
sections: [{ name: '', order: 1, fields: [nameField, descField] }],
|
|
47507
|
+
actions: {
|
|
47508
|
+
text: this.t('saveCta'),
|
|
47509
|
+
color: 'dark',
|
|
47510
|
+
type: 'submit',
|
|
47511
|
+
fill: 'solid',
|
|
47512
|
+
size: 'default',
|
|
47513
|
+
shape: 'round',
|
|
47514
|
+
expand: 'block',
|
|
47515
|
+
state: ComponentStates.ENABLED,
|
|
47516
|
+
token: 'edit-org-submit',
|
|
47517
|
+
},
|
|
47518
|
+
};
|
|
47519
|
+
}
|
|
47520
|
+
onFormSubmit(event) {
|
|
47521
|
+
const name = event.fields['name'];
|
|
47522
|
+
if (!name?.trim()) {
|
|
47523
|
+
this.toast.show({
|
|
47524
|
+
message: this.t('fillRequired'),
|
|
47525
|
+
duration: 3000,
|
|
47526
|
+
color: 'dark',
|
|
47527
|
+
position: 'top',
|
|
47528
|
+
});
|
|
47529
|
+
return;
|
|
47530
|
+
}
|
|
47531
|
+
const description = event.fields['description'] ?? '';
|
|
47532
|
+
void this.save(name, description);
|
|
47533
|
+
}
|
|
47534
|
+
async save(name, description) {
|
|
47535
|
+
const orgId = this.org?.id ?? this.activeOrgId;
|
|
47536
|
+
if (!orgId || this.saving())
|
|
47537
|
+
return;
|
|
47538
|
+
this.saving.set(true);
|
|
47539
|
+
try {
|
|
47540
|
+
const updated = await firstValueFrom(this.orgService.updateOrg(orgId, { name, description }));
|
|
47541
|
+
this.toast.show({
|
|
47542
|
+
message: this.t('saveSuccess'),
|
|
47543
|
+
color: 'dark',
|
|
47544
|
+
duration: 3500,
|
|
47545
|
+
});
|
|
47546
|
+
this.onSuccess?.(updated);
|
|
47547
|
+
this._modalRef?.dismiss(updated, 'confirm');
|
|
47548
|
+
}
|
|
47549
|
+
catch (err) {
|
|
47550
|
+
this.errors.handle(err, {
|
|
47551
|
+
context: 'editOrgModal.save',
|
|
47552
|
+
fallbackKey: 'saveError',
|
|
47553
|
+
i18nNamespace: this.i18nNamespace,
|
|
47554
|
+
});
|
|
47555
|
+
}
|
|
47556
|
+
finally {
|
|
47557
|
+
this.saving.set(false);
|
|
47558
|
+
}
|
|
47559
|
+
}
|
|
47560
|
+
dismiss() {
|
|
47561
|
+
this._modalRef?.dismiss(null, 'cancel');
|
|
47562
|
+
}
|
|
47563
|
+
t(key) {
|
|
47564
|
+
return this.i18n.t(key, this.i18nNamespace);
|
|
47565
|
+
}
|
|
47566
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EditOrgModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
47567
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: EditOrgModalComponent, isStandalone: true, selector: "val-edit-org-modal", inputs: { _modalRef: "_modalRef", org: "org", onSuccess: "onSuccess", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
|
|
47568
|
+
<ion-header>
|
|
47569
|
+
<ion-toolbar>
|
|
47570
|
+
<ion-buttons slot="end">
|
|
47571
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
47572
|
+
<strong>{{ t('close') }}</strong>
|
|
47573
|
+
</ion-button>
|
|
47574
|
+
</ion-buttons>
|
|
47575
|
+
</ion-toolbar>
|
|
47576
|
+
</ion-header>
|
|
47577
|
+
<ion-content class="ion-padding">
|
|
47578
|
+
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
47579
|
+
<val-title
|
|
47580
|
+
[props]="{
|
|
47581
|
+
content: t('subtitle'),
|
|
47582
|
+
size: 'large',
|
|
47583
|
+
color: '',
|
|
47584
|
+
bold: false,
|
|
47585
|
+
}"
|
|
47586
|
+
/>
|
|
47587
|
+
<val-form [props]="formMeta()" (onSubmit)="onFormSubmit($event)" />
|
|
47588
|
+
</ion-content>
|
|
47589
|
+
`, isInline: true, dependencies: [{ 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: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
|
|
47590
|
+
}
|
|
47591
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EditOrgModalComponent, decorators: [{
|
|
47592
|
+
type: Component,
|
|
47593
|
+
args: [{
|
|
47594
|
+
selector: 'val-edit-org-modal',
|
|
47595
|
+
standalone: true,
|
|
47596
|
+
imports: [IonHeader, IonToolbar, IonButtons, IonButton, IonContent, FormComponent, DisplayComponent, TitleComponent],
|
|
47597
|
+
template: `
|
|
47598
|
+
<ion-header>
|
|
47599
|
+
<ion-toolbar>
|
|
47600
|
+
<ion-buttons slot="end">
|
|
47601
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
47602
|
+
<strong>{{ t('close') }}</strong>
|
|
47603
|
+
</ion-button>
|
|
47604
|
+
</ion-buttons>
|
|
47605
|
+
</ion-toolbar>
|
|
47606
|
+
</ion-header>
|
|
47607
|
+
<ion-content class="ion-padding">
|
|
47608
|
+
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
47609
|
+
<val-title
|
|
47610
|
+
[props]="{
|
|
47611
|
+
content: t('subtitle'),
|
|
47612
|
+
size: 'large',
|
|
47613
|
+
color: '',
|
|
47614
|
+
bold: false,
|
|
47615
|
+
}"
|
|
47616
|
+
/>
|
|
47617
|
+
<val-form [props]="formMeta()" (onSubmit)="onFormSubmit($event)" />
|
|
47618
|
+
</ion-content>
|
|
47619
|
+
`,
|
|
47620
|
+
}]
|
|
47621
|
+
}], ctorParameters: () => [], propDecorators: { _modalRef: [{
|
|
47622
|
+
type: Input
|
|
47623
|
+
}], org: [{
|
|
47624
|
+
type: Input
|
|
47625
|
+
}], onSuccess: [{
|
|
47626
|
+
type: Input
|
|
47627
|
+
}], i18nNamespace: [{
|
|
47628
|
+
type: Input
|
|
47629
|
+
}] } });
|
|
47630
|
+
|
|
47631
|
+
/**
|
|
47632
|
+
* Defaults i18n (es/en) embebidos en `val-invite-member-modal`. Auto-registrados
|
|
47633
|
+
* en el constructor del componente si el consumer no proveyó el namespace
|
|
47634
|
+
* (`InviteModal` por default). SOLO incluye las keys que el modal usa.
|
|
47635
|
+
*/
|
|
47636
|
+
const INVITE_MEMBER_MODAL_I18N = {
|
|
47637
|
+
es: {
|
|
47638
|
+
title: 'Invitar miembro',
|
|
47639
|
+
subtitle: 'Busca por handle o escribe un correo para invitar.',
|
|
47640
|
+
searchPlaceholder: 'Handle (@usuario) o correo...',
|
|
47641
|
+
searchingLabel: 'Buscando...',
|
|
47642
|
+
noResults: 'No se encontraron usuarios.',
|
|
47643
|
+
inviteByEmail: 'Invitar por correo a:',
|
|
47644
|
+
sendInvite: 'Enviar invitación',
|
|
47645
|
+
sending: 'Enviando...',
|
|
47646
|
+
success: 'Invitación enviada.',
|
|
47647
|
+
successPlural: 'Invitaciones enviadas.',
|
|
47648
|
+
close: 'Cerrar',
|
|
47649
|
+
errorInvalid: 'Ingresa un handle o correo válido.',
|
|
47650
|
+
sendSelected: 'Invitar seleccionados',
|
|
47651
|
+
selectedLabel: 'Seleccionados',
|
|
47652
|
+
roleLabel: 'Rol',
|
|
47653
|
+
roleViewer: 'Visor',
|
|
47654
|
+
roleViewerDesc: 'Solo lectura',
|
|
47655
|
+
roleEditor: 'Editor',
|
|
47656
|
+
roleEditorDesc: 'Puede editar contenido',
|
|
47657
|
+
roleAdmin: 'Administrador',
|
|
47658
|
+
roleAdminDesc: 'Gestión completa',
|
|
47659
|
+
},
|
|
47660
|
+
en: {
|
|
47661
|
+
title: 'Invite member',
|
|
47662
|
+
subtitle: 'Search by handle or enter an email to invite.',
|
|
47663
|
+
searchPlaceholder: 'Handle (@user) or email...',
|
|
47664
|
+
searchingLabel: 'Searching...',
|
|
47665
|
+
noResults: 'No users found.',
|
|
47666
|
+
inviteByEmail: 'Invite by email:',
|
|
47667
|
+
sendInvite: 'Send invitation',
|
|
47668
|
+
sending: 'Sending...',
|
|
47669
|
+
success: 'Invitation sent.',
|
|
47670
|
+
successPlural: 'Invitations sent.',
|
|
47671
|
+
close: 'Close',
|
|
47672
|
+
errorInvalid: 'Enter a valid handle or email.',
|
|
47673
|
+
sendSelected: 'Invite selected',
|
|
47674
|
+
selectedLabel: 'Selected',
|
|
47675
|
+
roleLabel: 'Role',
|
|
47676
|
+
roleViewer: 'Viewer',
|
|
47677
|
+
roleViewerDesc: 'Read only',
|
|
47678
|
+
roleEditor: 'Editor',
|
|
47679
|
+
roleEditorDesc: 'Can edit content',
|
|
47680
|
+
roleAdmin: 'Admin',
|
|
47681
|
+
roleAdminDesc: 'Full management',
|
|
47682
|
+
},
|
|
47683
|
+
};
|
|
47684
|
+
|
|
47685
|
+
const DEFAULT_NAMESPACE$3 = 'InviteModal';
|
|
47686
|
+
addIcons({ personOutline, mailOutline, closeOutline, checkmarkCircleOutline });
|
|
47687
|
+
/**
|
|
47688
|
+
* `val-invite-member-modal` — invitación de miembros a una organización.
|
|
47689
|
+
* Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
47690
|
+
*
|
|
47691
|
+
* Busca usuarios por handle en Firestore `/profiles` (multi-select) o invita por
|
|
47692
|
+
* email cuando el query es un correo válido. Asigna el rol seleccionado y envía la
|
|
47693
|
+
* invitación vía `OrgService.inviteUser`. Excluye al usuario actual y a los
|
|
47694
|
+
* `existingMemberIds` de los resultados.
|
|
47695
|
+
*
|
|
47696
|
+
* Abierto vía `ModalService` — recibe `_modalRef` por `componentProps` + callback
|
|
47697
|
+
* `onSuccess`. Header canónico (Regla #5): sin `ion-title`, botón Cerrar texto en
|
|
47698
|
+
* `slot=end`, título/subtítulo en el body con `val-display`/`val-title`.
|
|
47699
|
+
*
|
|
47700
|
+
* Auto-registra sus defaults i18n (es/en) si el consumer no proveyó el namespace
|
|
47701
|
+
* `InviteModal`.
|
|
47702
|
+
*/
|
|
47703
|
+
class InviteMemberModalComponent {
|
|
47704
|
+
constructor() {
|
|
47705
|
+
this.orgService = inject(OrgService);
|
|
47706
|
+
this.auth = inject(AuthService);
|
|
47707
|
+
this.toast = inject(ToastService);
|
|
47708
|
+
this.errors = inject(ValtechErrorService);
|
|
47709
|
+
this.firestore = inject(Firestore);
|
|
47710
|
+
this.i18n = inject(I18nService);
|
|
47711
|
+
/** Organización a la que se invita. */
|
|
47712
|
+
this.orgId = '';
|
|
47713
|
+
/** Ids de miembros ya existentes (excluidos de los resultados). */
|
|
47714
|
+
this.existingMemberIds = [];
|
|
47715
|
+
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
47716
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$3;
|
|
47717
|
+
this.query = signal('');
|
|
47718
|
+
this.searching = signal(false);
|
|
47719
|
+
this.searchResults = signal([]);
|
|
47720
|
+
this.selectedUsers = signal([]);
|
|
47721
|
+
this.sending = signal(false);
|
|
47722
|
+
this.selectedRole = signal('viewer');
|
|
47723
|
+
this.availableRoles = signal([
|
|
47724
|
+
{ id: 'viewer', name: 'viewer' },
|
|
47725
|
+
{ id: 'editor', name: 'editor' },
|
|
47726
|
+
{ id: 'admin', name: 'admin' },
|
|
47727
|
+
]);
|
|
47728
|
+
this.roleControl = new FormControl('viewer');
|
|
47729
|
+
this.roleSelectProps = computed(() => ({
|
|
47730
|
+
control: this.roleControl,
|
|
47731
|
+
label: '',
|
|
47732
|
+
placeholder: this.t('roleLabel'),
|
|
47733
|
+
type: InputType.SELECT,
|
|
47734
|
+
state: ComponentStates.ENABLED,
|
|
47735
|
+
options: this.availableRoles().map(r => ({
|
|
47736
|
+
id: r.id,
|
|
47737
|
+
name: this.roleDisplayName(r),
|
|
47738
|
+
order: 0,
|
|
47739
|
+
})),
|
|
47740
|
+
}));
|
|
47741
|
+
this.isEmail = computed(() => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.query().trim()));
|
|
47742
|
+
this.sendButtonProps = computed(() => ({
|
|
47743
|
+
text: this.t('sendInvite'),
|
|
47744
|
+
token: 'invite-submit-email',
|
|
47745
|
+
color: 'primary',
|
|
47746
|
+
fill: 'solid',
|
|
47747
|
+
expand: 'block',
|
|
47748
|
+
size: 'default',
|
|
47749
|
+
type: 'button',
|
|
47750
|
+
state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
47751
|
+
}));
|
|
47752
|
+
this.sendSelectedProps = computed(() => ({
|
|
47753
|
+
text: `${this.t('sendSelected')} (${this.selectedUsers().length})`,
|
|
47754
|
+
token: 'invite-submit',
|
|
47755
|
+
color: 'primary',
|
|
47756
|
+
fill: 'solid',
|
|
47757
|
+
expand: 'block',
|
|
47758
|
+
size: 'default',
|
|
47759
|
+
type: 'button',
|
|
47760
|
+
state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
47761
|
+
}));
|
|
47762
|
+
this.searchSubject = new Subject();
|
|
47763
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$3)) {
|
|
47764
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$3, INVITE_MEMBER_MODAL_I18N);
|
|
47765
|
+
}
|
|
47766
|
+
this.loadRoles();
|
|
47767
|
+
this.roleSub = this.roleControl.valueChanges.subscribe(v => {
|
|
47768
|
+
if (v)
|
|
47769
|
+
this.selectedRole.set(v);
|
|
47770
|
+
});
|
|
47771
|
+
// val-searchbar already debounces -- pipeline only needs distinctUntilChanged + switchMap
|
|
47772
|
+
this.searchSubject
|
|
47773
|
+
.pipe(distinctUntilChanged$1(), switchMap(q => {
|
|
47774
|
+
if (!q || q.length < 2 || this.isEmail()) {
|
|
47775
|
+
this.searching.set(false);
|
|
47776
|
+
this.searchResults.set([]);
|
|
47777
|
+
return [];
|
|
47778
|
+
}
|
|
47779
|
+
const cleanQ = (q.startsWith('@') ? q.slice(1) : q).toLowerCase();
|
|
47780
|
+
const q$ = query$1(collection(this.firestore, 'profiles'), orderBy('handle'), startAt(cleanQ), endAt(cleanQ + ''), limit(8));
|
|
47781
|
+
return from(getDocs(q$)).pipe(map$1(snap => ({
|
|
47782
|
+
users: snap.docs.map(d => ({
|
|
47783
|
+
userId: d.id,
|
|
47784
|
+
handle: d.data()['handle'],
|
|
47785
|
+
name: d.data()['name'],
|
|
47786
|
+
})),
|
|
47787
|
+
})));
|
|
47788
|
+
}))
|
|
47789
|
+
.subscribe({
|
|
47790
|
+
next: res => {
|
|
47791
|
+
if (res && 'users' in res) {
|
|
47792
|
+
const currentUserId = this.auth.user()?.userId;
|
|
47793
|
+
const excluded = new Set([
|
|
47794
|
+
...(currentUserId ? [currentUserId] : []),
|
|
47795
|
+
...this.existingMemberIds,
|
|
47796
|
+
]);
|
|
47797
|
+
this.searchResults.set(res.users.filter(u => !excluded.has(u.userId)));
|
|
47798
|
+
}
|
|
47799
|
+
this.searching.set(false);
|
|
47800
|
+
},
|
|
47801
|
+
error: () => {
|
|
47802
|
+
this.searchResults.set([]);
|
|
47803
|
+
this.searching.set(false);
|
|
47804
|
+
},
|
|
47805
|
+
});
|
|
47806
|
+
}
|
|
47807
|
+
onQueryChange(value) {
|
|
47808
|
+
this.query.set(value ?? '');
|
|
47809
|
+
if (!this.isEmail() && (value ?? '').length >= 2) {
|
|
47810
|
+
this.searching.set(true);
|
|
47811
|
+
}
|
|
47812
|
+
else {
|
|
47813
|
+
this.searching.set(false);
|
|
47814
|
+
this.searchResults.set([]);
|
|
47815
|
+
}
|
|
47816
|
+
this.searchSubject.next(value ?? '');
|
|
47817
|
+
}
|
|
47818
|
+
loadRoles() {
|
|
47819
|
+
if (!this.orgId)
|
|
47820
|
+
return;
|
|
47821
|
+
this.orgService.listOrgRoles(this.orgId).subscribe({
|
|
47822
|
+
next: roles => {
|
|
47823
|
+
if (roles.length)
|
|
47824
|
+
this.availableRoles.set(roles);
|
|
47825
|
+
},
|
|
47826
|
+
});
|
|
47827
|
+
}
|
|
47828
|
+
roleDisplayName(role) {
|
|
47829
|
+
const keyMap = {
|
|
47830
|
+
viewer: 'roleViewer',
|
|
47831
|
+
editor: 'roleEditor',
|
|
47832
|
+
admin: 'roleAdmin',
|
|
47833
|
+
};
|
|
47834
|
+
const key = keyMap[role.name] ?? role.name;
|
|
47835
|
+
const label = this.t(key);
|
|
47836
|
+
return label && !label.startsWith('[') ? label : role.name;
|
|
47837
|
+
}
|
|
47838
|
+
async onInviteByEmail() {
|
|
47839
|
+
const email = this.query().trim();
|
|
47840
|
+
if (!email || this.sending())
|
|
47841
|
+
return;
|
|
47842
|
+
this.sending.set(true);
|
|
47843
|
+
try {
|
|
47844
|
+
await firstValueFrom(this.orgService.inviteUser(this.orgId, {
|
|
47845
|
+
email,
|
|
47846
|
+
roleId: this.selectedRole(),
|
|
47847
|
+
}));
|
|
47848
|
+
this.toast.show({
|
|
47849
|
+
message: this.t('success'),
|
|
47850
|
+
color: 'dark',
|
|
47851
|
+
duration: 3500,
|
|
47852
|
+
});
|
|
47853
|
+
this.onSuccess?.();
|
|
47854
|
+
this.dismiss();
|
|
47855
|
+
}
|
|
47856
|
+
catch (err) {
|
|
47857
|
+
this.errors.handle(err, {
|
|
47858
|
+
context: 'invite.byEmail',
|
|
47859
|
+
fallbackKey: 'sendInvite',
|
|
47860
|
+
i18nNamespace: this.i18nNamespace,
|
|
47861
|
+
});
|
|
47862
|
+
}
|
|
47863
|
+
finally {
|
|
47864
|
+
this.sending.set(false);
|
|
47865
|
+
}
|
|
47866
|
+
}
|
|
47867
|
+
isSelected(userId) {
|
|
47868
|
+
return this.selectedUsers().some(u => u.userId === userId);
|
|
47869
|
+
}
|
|
47870
|
+
toggleUser(user) {
|
|
47871
|
+
if (this.isSelected(user.userId)) {
|
|
47872
|
+
this.selectedUsers.update(list => list.filter(u => u.userId !== user.userId));
|
|
47873
|
+
}
|
|
47874
|
+
else {
|
|
47875
|
+
this.selectedUsers.update(list => [...list, user]);
|
|
47876
|
+
}
|
|
47877
|
+
}
|
|
47878
|
+
async onSendSelected() {
|
|
47879
|
+
const users = this.selectedUsers();
|
|
47880
|
+
if (!users.length || this.sending())
|
|
47881
|
+
return;
|
|
47882
|
+
this.sending.set(true);
|
|
47883
|
+
try {
|
|
47884
|
+
await Promise.all(users.map(u => firstValueFrom(this.orgService.inviteUser(this.orgId, {
|
|
47885
|
+
userId: u.userId,
|
|
47886
|
+
roleId: this.selectedRole(),
|
|
47887
|
+
}))));
|
|
47888
|
+
this.toast.show({
|
|
47889
|
+
message: users.length === 1 ? this.t('success') : this.t('successPlural'),
|
|
47890
|
+
color: 'dark',
|
|
47891
|
+
duration: 3500,
|
|
47892
|
+
});
|
|
47893
|
+
this.selectedUsers.set([]);
|
|
47894
|
+
this.onSuccess?.();
|
|
47895
|
+
this.dismiss();
|
|
47896
|
+
}
|
|
47897
|
+
catch (err) {
|
|
47898
|
+
this.errors.handle(err, {
|
|
47899
|
+
context: 'invite.byUserId',
|
|
47900
|
+
fallbackKey: 'sendInvite',
|
|
47901
|
+
i18nNamespace: this.i18nNamespace,
|
|
47902
|
+
});
|
|
47903
|
+
}
|
|
47904
|
+
finally {
|
|
47905
|
+
this.sending.set(false);
|
|
47906
|
+
}
|
|
47907
|
+
}
|
|
47908
|
+
ngOnDestroy() {
|
|
47909
|
+
this.roleSub?.unsubscribe();
|
|
47910
|
+
}
|
|
47911
|
+
dismiss() {
|
|
47912
|
+
this._modalRef?.dismiss(null, 'cancel');
|
|
47913
|
+
}
|
|
47914
|
+
t(key) {
|
|
47915
|
+
return this.i18n.t(key, this.i18nNamespace);
|
|
47916
|
+
}
|
|
47917
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InviteMemberModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
47918
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: InviteMemberModalComponent, isStandalone: true, selector: "val-invite-member-modal", inputs: { _modalRef: "_modalRef", orgId: "orgId", existingMemberIds: "existingMemberIds", onSuccess: "onSuccess", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
|
|
47919
|
+
<ion-header>
|
|
47920
|
+
<ion-toolbar>
|
|
47921
|
+
<ion-buttons slot="end">
|
|
47922
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
47923
|
+
<strong>{{ t('close') }}</strong>
|
|
47924
|
+
</ion-button>
|
|
47925
|
+
</ion-buttons>
|
|
47926
|
+
</ion-toolbar>
|
|
47927
|
+
</ion-header>
|
|
47928
|
+
|
|
47929
|
+
<ion-content class="ion-padding">
|
|
47930
|
+
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
47931
|
+
<val-title
|
|
47932
|
+
[props]="{
|
|
47933
|
+
content: t('subtitle'),
|
|
47934
|
+
size: 'large',
|
|
47935
|
+
color: '',
|
|
47936
|
+
bold: false,
|
|
47937
|
+
}"
|
|
47938
|
+
/>
|
|
47939
|
+
|
|
47940
|
+
<div class="role-select-wrap">
|
|
47941
|
+
<val-title
|
|
47942
|
+
[props]="{
|
|
47943
|
+
content: t('roleLabel'),
|
|
47944
|
+
size: 'small',
|
|
47945
|
+
color: 'dark',
|
|
47946
|
+
bold: false,
|
|
47947
|
+
}"
|
|
47948
|
+
/>
|
|
47949
|
+
<val-select-input data-testid="invite-role-select" [props]="roleSelectProps()" />
|
|
47950
|
+
</div>
|
|
47951
|
+
|
|
47952
|
+
<div class="search-section">
|
|
47953
|
+
<val-searchbar
|
|
47954
|
+
data-testid="invite-search"
|
|
47955
|
+
[props]="{
|
|
47956
|
+
placeholder: t('searchPlaceholder'),
|
|
47957
|
+
debounce: 350,
|
|
47958
|
+
showCancelButton: 'never',
|
|
47959
|
+
}"
|
|
47960
|
+
(filterEvent)="onQueryChange($event)"
|
|
47961
|
+
/>
|
|
47962
|
+
|
|
47963
|
+
@if (searching()) {
|
|
47964
|
+
<div class="search-state">
|
|
47965
|
+
<ion-spinner name="crescent" />
|
|
47966
|
+
<span>{{ t('searchingLabel') }}</span>
|
|
47967
|
+
</div>
|
|
47968
|
+
} @else if (isEmail()) {
|
|
47969
|
+
<div class="email-invite-card">
|
|
47970
|
+
<ion-icon name="mail-outline" class="email-icon" />
|
|
47971
|
+
<div class="email-invite-text">
|
|
47972
|
+
<span class="email-invite-label">{{ t('inviteByEmail') }}</span>
|
|
47973
|
+
<span class="email-invite-address">{{ query().trim() }}</span>
|
|
47974
|
+
</div>
|
|
47975
|
+
</div>
|
|
47976
|
+
<val-button [props]="sendButtonProps()" (click)="onInviteByEmail()" />
|
|
47977
|
+
} @else if (query().length >= 2 && searchResults().length === 0) {
|
|
47978
|
+
<val-empty-state
|
|
47979
|
+
[props]="{
|
|
47980
|
+
variant: 'empty',
|
|
47981
|
+
title: t('noResults'),
|
|
47982
|
+
icon: 'person-outline',
|
|
47983
|
+
}"
|
|
47984
|
+
/>
|
|
47985
|
+
} @else if (searchResults().length > 0) {
|
|
47986
|
+
<div class="results-list">
|
|
47987
|
+
@for (user of searchResults(); track user.userId) {
|
|
47988
|
+
<div
|
|
47989
|
+
class="result-card"
|
|
47990
|
+
[attr.data-testid]="'invite-result-' + user.userId"
|
|
47991
|
+
[class.result-card--selected]="isSelected(user.userId)"
|
|
47992
|
+
(click)="toggleUser(user)"
|
|
47993
|
+
role="checkbox"
|
|
47994
|
+
[attr.aria-checked]="isSelected(user.userId)"
|
|
47995
|
+
>
|
|
47996
|
+
<div class="result-card__avatar">
|
|
47997
|
+
<ion-icon name="person-outline" />
|
|
47998
|
+
</div>
|
|
47999
|
+
<div class="result-card__body">
|
|
48000
|
+
<span class="result-card__name">{{ user.name || user.handle }}</span>
|
|
48001
|
+
@if (user.handle) {
|
|
48002
|
+
<span class="result-card__handle">@{{ user.handle }}</span>
|
|
48003
|
+
}
|
|
48004
|
+
</div>
|
|
48005
|
+
<div class="result-card__check">
|
|
48006
|
+
@if (isSelected(user.userId)) {
|
|
48007
|
+
<ion-icon name="checkmark-circle-outline" class="check-on" />
|
|
48008
|
+
} @else {
|
|
48009
|
+
<div class="check-off"></div>
|
|
48010
|
+
}
|
|
48011
|
+
</div>
|
|
48012
|
+
</div>
|
|
48013
|
+
}
|
|
48014
|
+
</div>
|
|
48015
|
+
}
|
|
48016
|
+
|
|
48017
|
+
@if (selectedUsers().length > 0) {
|
|
48018
|
+
<div class="selected-section">
|
|
48019
|
+
<span class="selected-section__label">{{ t('selectedLabel') }} ({{ selectedUsers().length }})</span>
|
|
48020
|
+
<div class="selected-chips">
|
|
48021
|
+
@for (user of selectedUsers(); track user.userId) {
|
|
48022
|
+
<div class="selected-chip">
|
|
48023
|
+
<span>{{ user.handle ? '@' + user.handle : user.name || user.userId }}</span>
|
|
48024
|
+
<button class="selected-chip__remove" (click)="toggleUser(user)" aria-label="Quitar">×</button>
|
|
48025
|
+
</div>
|
|
48026
|
+
}
|
|
48027
|
+
</div>
|
|
48028
|
+
<val-button [props]="sendSelectedProps()" (click)="onSendSelected()" />
|
|
48029
|
+
</div>
|
|
48030
|
+
}
|
|
48031
|
+
</div>
|
|
48032
|
+
</ion-content>
|
|
48033
|
+
`, isInline: true, styles: ["ion-content{--padding-bottom: 24px}val-display{margin-bottom:4px;display:block}val-title{margin-bottom:8px;display:block}.role-select-wrap{margin-bottom:12px}.role-select{width:100%}val-searchbar{display:block;margin-bottom:4px}.search-section{display:flex;flex-direction:column;gap:14px}.search-state{display:flex;align-items:center;gap:10px;padding:12px 0;color:var(--ion-color-medium);font-size:.88rem}.email-invite-card{display:flex;align-items:center;gap:14px;padding:14px 16px;border-radius:12px;border:1.5px solid var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 8%,transparent)}.email-icon{font-size:24px;color:var(--ion-color-primary);flex-shrink:0}.email-invite-text{display:flex;flex-direction:column;gap:2px}.email-invite-label{font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.email-invite-address{font-weight:600;font-size:.95rem;color:var(--ion-color-dark);word-break:break-all}.results-list{display:flex;flex-direction:column;gap:8px}.result-card{display:flex;align-items:center;gap:14px;padding:12px 14px;border-radius:12px;background:var(--ion-color-light, #f4f5f8);cursor:pointer;transition:opacity .15s}.result-card:active{opacity:.7}:host-context(body.dark) .result-card,:host-context(html.ion-palette-dark) .result-card,:host-context([data-theme=\"dark\"]) .result-card{background:#ffffff0d}.result-card__avatar{width:40px;height:40px;border-radius:50%;background:color-mix(in srgb,var(--ion-color-primary) 15%,transparent);display:flex;align-items:center;justify-content:center;font-size:20px;color:var(--ion-color-primary);flex-shrink:0}.result-card__body{display:flex;flex-direction:column;gap:2px}.result-card__name{font-weight:600;font-size:.9rem;color:var(--ion-color-dark)}.result-card__handle{font-size:.78rem;color:var(--ion-color-medium)}.result-card--selected{border:1.5px solid var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 8%,transparent)}.result-card__check{flex-shrink:0;margin-left:auto}.check-on{font-size:22px;color:var(--ion-color-primary)}.check-off{width:22px;height:22px;border-radius:50%;border:2px solid var(--ion-color-medium)}.selected-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08));padding-top:14px;display:flex;flex-direction:column;gap:10px}.selected-section__label{font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.selected-chips{display:flex;flex-wrap:wrap;gap:6px}.selected-chip{display:flex;align-items:center;gap:4px;padding:4px 10px 4px 12px;border-radius:20px;background:color-mix(in srgb,var(--ion-color-primary) 12%,transparent);color:var(--ion-color-primary);font-size:.82rem;font-weight:600}.selected-chip__remove{border:none;background:none;cursor:pointer;font-size:1rem;line-height:1;color:var(--ion-color-primary);padding:0 2px;opacity:.7}.selected-chip__remove:hover{opacity:1}\n"], dependencies: [{ 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: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "component", type: SearchbarComponent, selector: "val-searchbar", inputs: ["preset", "props"], outputs: ["filterEvent", "focusEvent", "blurEvent"] }, { kind: "component", type: SearchSelectorComponent, selector: "val-select-input", inputs: ["preset", "props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
|
|
48034
|
+
}
|
|
48035
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InviteMemberModalComponent, decorators: [{
|
|
48036
|
+
type: Component,
|
|
48037
|
+
args: [{ selector: 'val-invite-member-modal', standalone: true, imports: [
|
|
48038
|
+
IonHeader,
|
|
48039
|
+
IonToolbar,
|
|
48040
|
+
IonButtons,
|
|
48041
|
+
IonButton,
|
|
48042
|
+
IonContent,
|
|
48043
|
+
IonSpinner,
|
|
48044
|
+
IonIcon,
|
|
48045
|
+
ReactiveFormsModule,
|
|
48046
|
+
ButtonComponent,
|
|
48047
|
+
DisplayComponent,
|
|
48048
|
+
EmptyStateComponent,
|
|
48049
|
+
SearchbarComponent,
|
|
48050
|
+
SearchSelectorComponent,
|
|
48051
|
+
TextComponent,
|
|
48052
|
+
TitleComponent,
|
|
48053
|
+
], template: `
|
|
48054
|
+
<ion-header>
|
|
48055
|
+
<ion-toolbar>
|
|
48056
|
+
<ion-buttons slot="end">
|
|
48057
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
48058
|
+
<strong>{{ t('close') }}</strong>
|
|
48059
|
+
</ion-button>
|
|
48060
|
+
</ion-buttons>
|
|
48061
|
+
</ion-toolbar>
|
|
48062
|
+
</ion-header>
|
|
48063
|
+
|
|
48064
|
+
<ion-content class="ion-padding">
|
|
48065
|
+
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
48066
|
+
<val-title
|
|
48067
|
+
[props]="{
|
|
48068
|
+
content: t('subtitle'),
|
|
48069
|
+
size: 'large',
|
|
48070
|
+
color: '',
|
|
48071
|
+
bold: false,
|
|
48072
|
+
}"
|
|
48073
|
+
/>
|
|
48074
|
+
|
|
48075
|
+
<div class="role-select-wrap">
|
|
48076
|
+
<val-title
|
|
48077
|
+
[props]="{
|
|
48078
|
+
content: t('roleLabel'),
|
|
48079
|
+
size: 'small',
|
|
48080
|
+
color: 'dark',
|
|
48081
|
+
bold: false,
|
|
48082
|
+
}"
|
|
48083
|
+
/>
|
|
48084
|
+
<val-select-input data-testid="invite-role-select" [props]="roleSelectProps()" />
|
|
48085
|
+
</div>
|
|
48086
|
+
|
|
48087
|
+
<div class="search-section">
|
|
48088
|
+
<val-searchbar
|
|
48089
|
+
data-testid="invite-search"
|
|
48090
|
+
[props]="{
|
|
48091
|
+
placeholder: t('searchPlaceholder'),
|
|
48092
|
+
debounce: 350,
|
|
48093
|
+
showCancelButton: 'never',
|
|
48094
|
+
}"
|
|
48095
|
+
(filterEvent)="onQueryChange($event)"
|
|
48096
|
+
/>
|
|
48097
|
+
|
|
48098
|
+
@if (searching()) {
|
|
48099
|
+
<div class="search-state">
|
|
48100
|
+
<ion-spinner name="crescent" />
|
|
48101
|
+
<span>{{ t('searchingLabel') }}</span>
|
|
48102
|
+
</div>
|
|
48103
|
+
} @else if (isEmail()) {
|
|
48104
|
+
<div class="email-invite-card">
|
|
48105
|
+
<ion-icon name="mail-outline" class="email-icon" />
|
|
48106
|
+
<div class="email-invite-text">
|
|
48107
|
+
<span class="email-invite-label">{{ t('inviteByEmail') }}</span>
|
|
48108
|
+
<span class="email-invite-address">{{ query().trim() }}</span>
|
|
48109
|
+
</div>
|
|
48110
|
+
</div>
|
|
48111
|
+
<val-button [props]="sendButtonProps()" (click)="onInviteByEmail()" />
|
|
48112
|
+
} @else if (query().length >= 2 && searchResults().length === 0) {
|
|
48113
|
+
<val-empty-state
|
|
48114
|
+
[props]="{
|
|
48115
|
+
variant: 'empty',
|
|
48116
|
+
title: t('noResults'),
|
|
48117
|
+
icon: 'person-outline',
|
|
48118
|
+
}"
|
|
48119
|
+
/>
|
|
48120
|
+
} @else if (searchResults().length > 0) {
|
|
48121
|
+
<div class="results-list">
|
|
48122
|
+
@for (user of searchResults(); track user.userId) {
|
|
48123
|
+
<div
|
|
48124
|
+
class="result-card"
|
|
48125
|
+
[attr.data-testid]="'invite-result-' + user.userId"
|
|
48126
|
+
[class.result-card--selected]="isSelected(user.userId)"
|
|
48127
|
+
(click)="toggleUser(user)"
|
|
48128
|
+
role="checkbox"
|
|
48129
|
+
[attr.aria-checked]="isSelected(user.userId)"
|
|
48130
|
+
>
|
|
48131
|
+
<div class="result-card__avatar">
|
|
48132
|
+
<ion-icon name="person-outline" />
|
|
48133
|
+
</div>
|
|
48134
|
+
<div class="result-card__body">
|
|
48135
|
+
<span class="result-card__name">{{ user.name || user.handle }}</span>
|
|
48136
|
+
@if (user.handle) {
|
|
48137
|
+
<span class="result-card__handle">@{{ user.handle }}</span>
|
|
48138
|
+
}
|
|
48139
|
+
</div>
|
|
48140
|
+
<div class="result-card__check">
|
|
48141
|
+
@if (isSelected(user.userId)) {
|
|
48142
|
+
<ion-icon name="checkmark-circle-outline" class="check-on" />
|
|
48143
|
+
} @else {
|
|
48144
|
+
<div class="check-off"></div>
|
|
48145
|
+
}
|
|
48146
|
+
</div>
|
|
48147
|
+
</div>
|
|
48148
|
+
}
|
|
48149
|
+
</div>
|
|
48150
|
+
}
|
|
48151
|
+
|
|
48152
|
+
@if (selectedUsers().length > 0) {
|
|
48153
|
+
<div class="selected-section">
|
|
48154
|
+
<span class="selected-section__label">{{ t('selectedLabel') }} ({{ selectedUsers().length }})</span>
|
|
48155
|
+
<div class="selected-chips">
|
|
48156
|
+
@for (user of selectedUsers(); track user.userId) {
|
|
48157
|
+
<div class="selected-chip">
|
|
48158
|
+
<span>{{ user.handle ? '@' + user.handle : user.name || user.userId }}</span>
|
|
48159
|
+
<button class="selected-chip__remove" (click)="toggleUser(user)" aria-label="Quitar">×</button>
|
|
48160
|
+
</div>
|
|
48161
|
+
}
|
|
48162
|
+
</div>
|
|
48163
|
+
<val-button [props]="sendSelectedProps()" (click)="onSendSelected()" />
|
|
48164
|
+
</div>
|
|
48165
|
+
}
|
|
48166
|
+
</div>
|
|
48167
|
+
</ion-content>
|
|
48168
|
+
`, styles: ["ion-content{--padding-bottom: 24px}val-display{margin-bottom:4px;display:block}val-title{margin-bottom:8px;display:block}.role-select-wrap{margin-bottom:12px}.role-select{width:100%}val-searchbar{display:block;margin-bottom:4px}.search-section{display:flex;flex-direction:column;gap:14px}.search-state{display:flex;align-items:center;gap:10px;padding:12px 0;color:var(--ion-color-medium);font-size:.88rem}.email-invite-card{display:flex;align-items:center;gap:14px;padding:14px 16px;border-radius:12px;border:1.5px solid var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 8%,transparent)}.email-icon{font-size:24px;color:var(--ion-color-primary);flex-shrink:0}.email-invite-text{display:flex;flex-direction:column;gap:2px}.email-invite-label{font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.email-invite-address{font-weight:600;font-size:.95rem;color:var(--ion-color-dark);word-break:break-all}.results-list{display:flex;flex-direction:column;gap:8px}.result-card{display:flex;align-items:center;gap:14px;padding:12px 14px;border-radius:12px;background:var(--ion-color-light, #f4f5f8);cursor:pointer;transition:opacity .15s}.result-card:active{opacity:.7}:host-context(body.dark) .result-card,:host-context(html.ion-palette-dark) .result-card,:host-context([data-theme=\"dark\"]) .result-card{background:#ffffff0d}.result-card__avatar{width:40px;height:40px;border-radius:50%;background:color-mix(in srgb,var(--ion-color-primary) 15%,transparent);display:flex;align-items:center;justify-content:center;font-size:20px;color:var(--ion-color-primary);flex-shrink:0}.result-card__body{display:flex;flex-direction:column;gap:2px}.result-card__name{font-weight:600;font-size:.9rem;color:var(--ion-color-dark)}.result-card__handle{font-size:.78rem;color:var(--ion-color-medium)}.result-card--selected{border:1.5px solid var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 8%,transparent)}.result-card__check{flex-shrink:0;margin-left:auto}.check-on{font-size:22px;color:var(--ion-color-primary)}.check-off{width:22px;height:22px;border-radius:50%;border:2px solid var(--ion-color-medium)}.selected-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08));padding-top:14px;display:flex;flex-direction:column;gap:10px}.selected-section__label{font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.selected-chips{display:flex;flex-wrap:wrap;gap:6px}.selected-chip{display:flex;align-items:center;gap:4px;padding:4px 10px 4px 12px;border-radius:20px;background:color-mix(in srgb,var(--ion-color-primary) 12%,transparent);color:var(--ion-color-primary);font-size:.82rem;font-weight:600}.selected-chip__remove{border:none;background:none;cursor:pointer;font-size:1rem;line-height:1;color:var(--ion-color-primary);padding:0 2px;opacity:.7}.selected-chip__remove:hover{opacity:1}\n"] }]
|
|
48169
|
+
}], ctorParameters: () => [], propDecorators: { _modalRef: [{
|
|
48170
|
+
type: Input
|
|
48171
|
+
}], orgId: [{
|
|
48172
|
+
type: Input
|
|
48173
|
+
}], existingMemberIds: [{
|
|
48174
|
+
type: Input
|
|
48175
|
+
}], onSuccess: [{
|
|
48176
|
+
type: Input
|
|
48177
|
+
}], i18nNamespace: [{
|
|
48178
|
+
type: Input
|
|
48179
|
+
}] } });
|
|
48180
|
+
|
|
48181
|
+
/**
|
|
48182
|
+
* Defaults i18n (es/en) embebidos en `val-member-detail-modal`. Auto-registrados
|
|
48183
|
+
* en el constructor del componente si el consumer no proveyó el namespace
|
|
48184
|
+
* (`Settings.MemberDetail` por default). SOLO incluye las keys que el modal usa.
|
|
48185
|
+
*/
|
|
48186
|
+
const MEMBER_DETAIL_MODAL_I18N = {
|
|
48187
|
+
es: {
|
|
48188
|
+
close: 'Cerrar',
|
|
48189
|
+
subtitleSelf: 'Tú',
|
|
48190
|
+
roleLabel: 'Rol',
|
|
48191
|
+
roleHint: 'Cambia el rol de este miembro en la organización.',
|
|
48192
|
+
roleReadonly: 'Rol en la organización',
|
|
48193
|
+
roleViewer: 'Visor',
|
|
48194
|
+
roleEditor: 'Editor',
|
|
48195
|
+
roleAdmin: 'Admin',
|
|
48196
|
+
roleSuperAdmin: 'Super Admin',
|
|
48197
|
+
removeCta: 'Quitar de la organización',
|
|
48198
|
+
removeConfirmTitle: 'Quitar miembro',
|
|
48199
|
+
removeConfirmMessage: '¿Quitar a este miembro de la organización?',
|
|
48200
|
+
removeConfirmOk: 'Quitar',
|
|
48201
|
+
cancelCta: 'Cancelar',
|
|
48202
|
+
changeRoleSuccess: 'Rol actualizado.',
|
|
48203
|
+
changeRoleError: 'No se pudo cambiar el rol.',
|
|
48204
|
+
removeSuccess: 'Miembro eliminado.',
|
|
48205
|
+
removeError: 'No se pudo eliminar al miembro.',
|
|
48206
|
+
permissionsTitle: 'Permisos del rol',
|
|
48207
|
+
permAll: 'Todo',
|
|
48208
|
+
permUsers: 'Usuarios',
|
|
48209
|
+
permDocuments: 'Documentos',
|
|
48210
|
+
permRoles: 'Roles',
|
|
48211
|
+
permMedia: 'Media',
|
|
48212
|
+
permTemplates: 'Plantillas',
|
|
48213
|
+
permRead: 'Leer',
|
|
48214
|
+
permWrite: 'Escribir',
|
|
48215
|
+
permCreate: 'Crear',
|
|
48216
|
+
permDelete: 'Eliminar',
|
|
48217
|
+
permManage: 'Gestionar',
|
|
48218
|
+
},
|
|
48219
|
+
en: {
|
|
48220
|
+
close: 'Close',
|
|
48221
|
+
subtitleSelf: 'You',
|
|
48222
|
+
roleLabel: 'Role',
|
|
48223
|
+
roleHint: "Change this member's role in the organization.",
|
|
48224
|
+
roleReadonly: 'Role in the organization',
|
|
48225
|
+
roleViewer: 'Viewer',
|
|
48226
|
+
roleEditor: 'Editor',
|
|
48227
|
+
roleAdmin: 'Admin',
|
|
48228
|
+
roleSuperAdmin: 'Super Admin',
|
|
48229
|
+
removeCta: 'Remove from organization',
|
|
48230
|
+
removeConfirmTitle: 'Remove member',
|
|
48231
|
+
removeConfirmMessage: 'Remove this member from the organization?',
|
|
48232
|
+
removeConfirmOk: 'Remove',
|
|
48233
|
+
cancelCta: 'Cancel',
|
|
48234
|
+
changeRoleSuccess: 'Role updated.',
|
|
48235
|
+
changeRoleError: 'Could not change role.',
|
|
48236
|
+
removeSuccess: 'Member removed.',
|
|
48237
|
+
removeError: 'Could not remove member.',
|
|
48238
|
+
permissionsTitle: 'Role permissions',
|
|
48239
|
+
permAll: 'All',
|
|
48240
|
+
permUsers: 'Users',
|
|
48241
|
+
permDocuments: 'Documents',
|
|
48242
|
+
permRoles: 'Roles',
|
|
48243
|
+
permMedia: 'Media',
|
|
48244
|
+
permTemplates: 'Templates',
|
|
48245
|
+
permRead: 'Read',
|
|
48246
|
+
permWrite: 'Write',
|
|
48247
|
+
permCreate: 'Create',
|
|
48248
|
+
permDelete: 'Delete',
|
|
48249
|
+
permManage: 'Manage',
|
|
48250
|
+
},
|
|
48251
|
+
};
|
|
48252
|
+
|
|
48253
|
+
const DEFAULT_NAMESPACE$2 = 'Settings.MemberDetail';
|
|
48254
|
+
/**
|
|
48255
|
+
* `val-member-detail-modal` — detalle de miembro de organización: perfil, rol y
|
|
48256
|
+
* permisos. Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
48257
|
+
*
|
|
48258
|
+
* Si `canManage` es true, permite cambiar el rol (vía `OrgService.changeMemberRole`)
|
|
48259
|
+
* y quitar al miembro (vía `OrgService.removeMember`, con confirmación destructiva).
|
|
48260
|
+
* Los permisos del rol actual se muestran cuando el consumer los provee en
|
|
48261
|
+
* `availableRoles`.
|
|
48262
|
+
*
|
|
48263
|
+
* Abierto vía `ModalService` — recibe `_modalRef` por `componentProps` + callback
|
|
48264
|
+
* `onChanged`. Header canónico (Regla #5): sin `ion-title`, botón Cerrar texto en
|
|
48265
|
+
* `slot=end`, identidad en el body con `val-user-avatar`/`val-display`/`val-title`.
|
|
48266
|
+
*
|
|
48267
|
+
* Auto-registra sus defaults i18n (es/en) si el consumer no proveyó el namespace
|
|
48268
|
+
* `Settings.MemberDetail`.
|
|
48269
|
+
*/
|
|
48270
|
+
class MemberDetailModalComponent {
|
|
48271
|
+
constructor() {
|
|
48272
|
+
this.orgService = inject(OrgService);
|
|
48273
|
+
this.toast = inject(ToastService);
|
|
48274
|
+
this.errors = inject(ValtechErrorService);
|
|
48275
|
+
this.i18n = inject(I18nService);
|
|
48276
|
+
this.confirmDialog = inject(ConfirmationDialogService);
|
|
48277
|
+
/** Organización a la que pertenece el miembro. */
|
|
48278
|
+
this.orgId = '';
|
|
48279
|
+
/** Si true, habilita cambio de rol y eliminación. */
|
|
48280
|
+
this.canManage = false;
|
|
48281
|
+
/** Si true, el miembro mostrado es el propio usuario. */
|
|
48282
|
+
this.isSelf = false;
|
|
48283
|
+
/** Roles disponibles (con permisos opcionales) para el selector de rol. */
|
|
48284
|
+
this.availableRoles = [
|
|
48285
|
+
{ id: 'viewer', name: 'viewer' },
|
|
48286
|
+
{ id: 'editor', name: 'editor' },
|
|
48287
|
+
{ id: 'admin', name: 'admin' },
|
|
48288
|
+
];
|
|
48289
|
+
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
48290
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$2;
|
|
48291
|
+
this.working = signal(false);
|
|
48292
|
+
this.currentRole = signal('');
|
|
48293
|
+
this.roleControl = new FormControl('');
|
|
48294
|
+
this.displayName = computed(() => this.member?.name || this.member?.handle || this.member?.email || '—');
|
|
48295
|
+
this.subtitle = computed(() => {
|
|
48296
|
+
if (this.isSelf)
|
|
48297
|
+
return this.t('subtitleSelf');
|
|
48298
|
+
if (this.member?.handle)
|
|
48299
|
+
return '@' + this.member.handle;
|
|
48300
|
+
return this.member?.email ?? '';
|
|
48301
|
+
});
|
|
48302
|
+
this.currentRoleLabel = computed(() => this.roleLabel(this.currentRole()));
|
|
48303
|
+
this.permissionsForCurrentRole = computed(() => {
|
|
48304
|
+
const roleId = this.currentRole();
|
|
48305
|
+
const role = this.availableRoles.find(r => r.id === roleId || r.name === roleId);
|
|
48306
|
+
return role?.permissions ?? [];
|
|
48307
|
+
});
|
|
48308
|
+
this.roleSelectProps = computed(() => ({
|
|
48309
|
+
control: this.roleControl,
|
|
48310
|
+
label: '',
|
|
48311
|
+
placeholder: this.t('roleLabel'),
|
|
48312
|
+
type: InputType.SELECT,
|
|
48313
|
+
state: this.working() ? ComponentStates.DISABLED : ComponentStates.ENABLED,
|
|
48314
|
+
options: this.availableRoles.map(r => ({
|
|
48315
|
+
id: r.id,
|
|
48316
|
+
name: this.roleLabel(r.name),
|
|
48317
|
+
order: 0,
|
|
48318
|
+
})),
|
|
48319
|
+
}));
|
|
48320
|
+
this.removeButtonProps = computed(() => ({
|
|
48321
|
+
text: this.t('removeCta'),
|
|
48322
|
+
token: 'member-remove',
|
|
48323
|
+
color: 'danger',
|
|
48324
|
+
fill: 'outline',
|
|
48325
|
+
expand: 'block',
|
|
48326
|
+
size: 'default',
|
|
48327
|
+
type: 'button',
|
|
48328
|
+
state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
48329
|
+
}));
|
|
48330
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$2)) {
|
|
48331
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$2, MEMBER_DETAIL_MODAL_I18N);
|
|
48332
|
+
}
|
|
48333
|
+
}
|
|
48334
|
+
ngOnInit() {
|
|
48335
|
+
if (!this.i18n.hasNamespace(this.i18nNamespace)) {
|
|
48336
|
+
this.i18n.registerContent(this.i18nNamespace, MEMBER_DETAIL_MODAL_I18N);
|
|
48337
|
+
}
|
|
48338
|
+
const roleName = this.member?.roles?.[0] ?? '';
|
|
48339
|
+
// Resolve the role name to its UUID -- the select uses r.id (UUID) as value
|
|
48340
|
+
const matched = this.availableRoles.find(r => r.name === roleName);
|
|
48341
|
+
const roleId = matched?.id ?? roleName;
|
|
48342
|
+
this.currentRole.set(roleId);
|
|
48343
|
+
this.roleControl.setValue(roleId, { emitEvent: false });
|
|
48344
|
+
this.roleSub = this.roleControl.valueChanges.subscribe(v => {
|
|
48345
|
+
if (v && v !== this.currentRole())
|
|
48346
|
+
void this.onChangeRole(v);
|
|
48347
|
+
});
|
|
48348
|
+
}
|
|
48349
|
+
permissionLabel(perm) {
|
|
48350
|
+
if (perm === '*:*')
|
|
48351
|
+
return this.t('permAll');
|
|
48352
|
+
const [resource, action] = perm.split(':');
|
|
48353
|
+
const resKey = {
|
|
48354
|
+
users: 'permUsers',
|
|
48355
|
+
documents: 'permDocuments',
|
|
48356
|
+
roles: 'permRoles',
|
|
48357
|
+
media: 'permMedia',
|
|
48358
|
+
templates: 'permTemplates',
|
|
48359
|
+
'*': 'permAll',
|
|
48360
|
+
};
|
|
48361
|
+
const actKey = {
|
|
48362
|
+
read: 'permRead',
|
|
48363
|
+
write: 'permWrite',
|
|
48364
|
+
create: 'permCreate',
|
|
48365
|
+
delete: 'permDelete',
|
|
48366
|
+
manage: 'permManage',
|
|
48367
|
+
'*': 'permAll',
|
|
48368
|
+
};
|
|
48369
|
+
const res = this.t(resKey[resource] ?? resource);
|
|
48370
|
+
const act = this.t(actKey[action] ?? action);
|
|
48371
|
+
return `${res}: ${act}`;
|
|
48372
|
+
}
|
|
48373
|
+
async onChangeRole(newRoleId) {
|
|
48374
|
+
if (this.working())
|
|
48375
|
+
return;
|
|
48376
|
+
this.working.set(true);
|
|
48377
|
+
try {
|
|
48378
|
+
await firstValueFrom(this.orgService.changeMemberRole(this.orgId, this.member.userId, newRoleId));
|
|
48379
|
+
this.currentRole.set(newRoleId);
|
|
48380
|
+
this.member = { ...this.member, roles: [newRoleId] };
|
|
48381
|
+
this.toast.show({
|
|
48382
|
+
message: this.t('changeRoleSuccess'),
|
|
48383
|
+
color: 'dark',
|
|
48384
|
+
duration: 3000,
|
|
48385
|
+
});
|
|
48386
|
+
this.onChanged?.();
|
|
48387
|
+
}
|
|
48388
|
+
catch (err) {
|
|
48389
|
+
this.roleControl.setValue(this.currentRole(), { emitEvent: false });
|
|
48390
|
+
this.errors.handle(err, {
|
|
48391
|
+
context: 'memberDetail.changeRole',
|
|
48392
|
+
fallbackKey: 'changeRoleError',
|
|
48393
|
+
i18nNamespace: this.i18nNamespace,
|
|
48394
|
+
});
|
|
48395
|
+
}
|
|
48396
|
+
finally {
|
|
48397
|
+
this.working.set(false);
|
|
48398
|
+
}
|
|
48399
|
+
}
|
|
48400
|
+
async onRemove() {
|
|
48401
|
+
if (this.working())
|
|
48402
|
+
return;
|
|
48403
|
+
const result = await this.confirmDialog.confirmDestructive({
|
|
48404
|
+
title: this.t('removeConfirmTitle'),
|
|
48405
|
+
message: this.t('removeConfirmMessage'),
|
|
48406
|
+
confirmButton: { text: this.t('removeConfirmOk') },
|
|
48407
|
+
cancelButton: { text: this.t('cancelCta') },
|
|
48408
|
+
});
|
|
48409
|
+
if (!result.confirmed)
|
|
48410
|
+
return;
|
|
48411
|
+
this.working.set(true);
|
|
48412
|
+
try {
|
|
48413
|
+
await firstValueFrom(this.orgService.removeMember(this.orgId, this.member.userId));
|
|
48414
|
+
this.toast.show({
|
|
48415
|
+
message: this.t('removeSuccess'),
|
|
48416
|
+
color: 'dark',
|
|
48417
|
+
duration: 3000,
|
|
48418
|
+
});
|
|
48419
|
+
this.onChanged?.();
|
|
48420
|
+
this.dismiss();
|
|
48421
|
+
}
|
|
48422
|
+
catch (err) {
|
|
48423
|
+
this.errors.handle(err, {
|
|
48424
|
+
context: 'memberDetail.remove',
|
|
48425
|
+
fallbackKey: 'removeError',
|
|
48426
|
+
i18nNamespace: this.i18nNamespace,
|
|
48427
|
+
});
|
|
48428
|
+
}
|
|
48429
|
+
finally {
|
|
48430
|
+
this.working.set(false);
|
|
48431
|
+
}
|
|
48432
|
+
}
|
|
48433
|
+
roleLabel(roleIdOrName) {
|
|
48434
|
+
// Try to find by id first (post-change, UUID stored), then by name (initial load)
|
|
48435
|
+
const found = this.availableRoles.find(r => r.id === roleIdOrName || r.name === roleIdOrName);
|
|
48436
|
+
const name = found?.name ?? roleIdOrName;
|
|
48437
|
+
const keyMap = {
|
|
48438
|
+
viewer: 'roleViewer',
|
|
48439
|
+
editor: 'roleEditor',
|
|
48440
|
+
admin: 'roleAdmin',
|
|
48441
|
+
super_admin: 'roleSuperAdmin',
|
|
48442
|
+
};
|
|
48443
|
+
const key = keyMap[name] ?? name;
|
|
48444
|
+
const label = this.t(key);
|
|
48445
|
+
return label && !label.startsWith('[') ? label : name;
|
|
48446
|
+
}
|
|
48447
|
+
ngOnDestroy() {
|
|
48448
|
+
this.roleSub?.unsubscribe();
|
|
48449
|
+
}
|
|
48450
|
+
dismiss() {
|
|
48451
|
+
this._modalRef?.dismiss(null, 'cancel');
|
|
48452
|
+
}
|
|
48453
|
+
t(key) {
|
|
48454
|
+
return this.i18n.t(key, this.i18nNamespace);
|
|
48455
|
+
}
|
|
48456
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberDetailModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
48457
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MemberDetailModalComponent, isStandalone: true, selector: "val-member-detail-modal", inputs: { _modalRef: "_modalRef", member: "member", orgId: "orgId", canManage: "canManage", isSelf: "isSelf", availableRoles: "availableRoles", onChanged: "onChanged", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
|
|
48458
|
+
<ion-header>
|
|
48459
|
+
<ion-toolbar>
|
|
48460
|
+
<ion-buttons slot="end">
|
|
48461
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
48462
|
+
<strong>{{ t('close') }}</strong>
|
|
48463
|
+
</ion-button>
|
|
48464
|
+
</ion-buttons>
|
|
48465
|
+
</ion-toolbar>
|
|
48466
|
+
</ion-header>
|
|
48467
|
+
|
|
48468
|
+
<ion-content class="ion-padding">
|
|
48469
|
+
<div class="member-head">
|
|
48470
|
+
<val-user-avatar
|
|
48471
|
+
[props]="{
|
|
48472
|
+
name: member.name,
|
|
48473
|
+
avatarUrl: member.avatar,
|
|
48474
|
+
email: member.email,
|
|
48475
|
+
size: 'large',
|
|
48476
|
+
}"
|
|
48477
|
+
/>
|
|
48478
|
+
<val-display [props]="{ content: displayName(), size: 'small', color: 'dark' }" />
|
|
48479
|
+
<val-title
|
|
48480
|
+
[props]="{
|
|
48481
|
+
content: subtitle(),
|
|
48482
|
+
size: 'large',
|
|
48483
|
+
color: '',
|
|
48484
|
+
bold: false,
|
|
48485
|
+
}"
|
|
48486
|
+
/>
|
|
48487
|
+
</div>
|
|
48488
|
+
|
|
48489
|
+
<div class="role-section">
|
|
48490
|
+
@if (canManage) {
|
|
48491
|
+
<val-title
|
|
48492
|
+
[props]="{
|
|
48493
|
+
content: t('roleLabel'),
|
|
48494
|
+
size: 'small',
|
|
48495
|
+
color: 'dark',
|
|
48496
|
+
bold: false,
|
|
48497
|
+
}"
|
|
48498
|
+
/>
|
|
48499
|
+
<val-select-input data-testid="member-role-select" [props]="roleSelectProps()" />
|
|
48500
|
+
<val-text
|
|
48501
|
+
[props]="{
|
|
48502
|
+
content: t('roleHint'),
|
|
48503
|
+
size: 'small',
|
|
48504
|
+
color: 'medium',
|
|
48505
|
+
bold: false,
|
|
48506
|
+
}"
|
|
48507
|
+
/>
|
|
48508
|
+
} @else {
|
|
48509
|
+
<span class="role-readonly-label">{{ t('roleReadonly') }}</span>
|
|
48510
|
+
<span class="member-role-badge">{{ currentRoleLabel() }}</span>
|
|
48511
|
+
}
|
|
48512
|
+
</div>
|
|
48513
|
+
|
|
48514
|
+
@if (permissionsForCurrentRole().length > 0) {
|
|
48515
|
+
<div class="perms-section">
|
|
48516
|
+
<span class="perms-label">{{ t('permissionsTitle') }}</span>
|
|
48517
|
+
<div class="perms-list">
|
|
48518
|
+
@for (perm of permissionsForCurrentRole(); track perm) {
|
|
48519
|
+
<span class="perm-chip">{{ permissionLabel(perm) }}</span>
|
|
48520
|
+
}
|
|
48521
|
+
</div>
|
|
48522
|
+
</div>
|
|
48523
|
+
}
|
|
48524
|
+
|
|
48525
|
+
@if (canManage) {
|
|
48526
|
+
<div class="remove-section">
|
|
48527
|
+
<val-button [props]="removeButtonProps()" (click)="onRemove()" />
|
|
48528
|
+
</div>
|
|
48529
|
+
}
|
|
48530
|
+
</ion-content>
|
|
48531
|
+
`, isInline: true, styles: ["ion-content{--padding-bottom: 24px}.member-head{display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;margin-bottom:20px}val-display{display:block}val-title{display:block}.role-section{display:flex;flex-direction:column;gap:8px;margin-bottom:24px}.role-readonly-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.member-role-badge{font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:4px 12px;border-radius:20px;width:fit-content;background:color-mix(in srgb,var(--ion-color-primary) 12%,transparent);color:var(--ion-color-primary)}.perms-section{display:flex;flex-direction:column;gap:10px;margin-bottom:24px}.perms-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.perms-list{display:flex;flex-wrap:wrap;gap:6px}.perm-chip{font-size:.75rem;font-weight:500;padding:3px 10px;border-radius:20px;border:1px solid var(--val-border-color, rgba(0, 0, 0, .12));color:var(--ion-color-dark);background:transparent}.remove-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08));padding-top:20px}\n"], dependencies: [{ 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: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: SearchSelectorComponent, selector: "val-select-input", inputs: ["preset", "props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: UserAvatarComponent, selector: "val-user-avatar", inputs: ["props"], outputs: ["onClick"] }] }); }
|
|
48532
|
+
}
|
|
48533
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberDetailModalComponent, decorators: [{
|
|
48534
|
+
type: Component,
|
|
48535
|
+
args: [{ selector: 'val-member-detail-modal', standalone: true, imports: [
|
|
48536
|
+
IonHeader,
|
|
48537
|
+
IonToolbar,
|
|
48538
|
+
IonButtons,
|
|
48539
|
+
IonButton,
|
|
48540
|
+
IonContent,
|
|
48541
|
+
ReactiveFormsModule,
|
|
48542
|
+
ButtonComponent,
|
|
48543
|
+
DisplayComponent,
|
|
48544
|
+
SearchSelectorComponent,
|
|
48545
|
+
TextComponent,
|
|
48546
|
+
TitleComponent,
|
|
48547
|
+
UserAvatarComponent,
|
|
48548
|
+
], template: `
|
|
48549
|
+
<ion-header>
|
|
48550
|
+
<ion-toolbar>
|
|
48551
|
+
<ion-buttons slot="end">
|
|
48552
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
48553
|
+
<strong>{{ t('close') }}</strong>
|
|
48554
|
+
</ion-button>
|
|
48555
|
+
</ion-buttons>
|
|
48556
|
+
</ion-toolbar>
|
|
48557
|
+
</ion-header>
|
|
48558
|
+
|
|
48559
|
+
<ion-content class="ion-padding">
|
|
48560
|
+
<div class="member-head">
|
|
48561
|
+
<val-user-avatar
|
|
48562
|
+
[props]="{
|
|
48563
|
+
name: member.name,
|
|
48564
|
+
avatarUrl: member.avatar,
|
|
48565
|
+
email: member.email,
|
|
48566
|
+
size: 'large',
|
|
48567
|
+
}"
|
|
48568
|
+
/>
|
|
48569
|
+
<val-display [props]="{ content: displayName(), size: 'small', color: 'dark' }" />
|
|
48570
|
+
<val-title
|
|
48571
|
+
[props]="{
|
|
48572
|
+
content: subtitle(),
|
|
48573
|
+
size: 'large',
|
|
48574
|
+
color: '',
|
|
48575
|
+
bold: false,
|
|
48576
|
+
}"
|
|
48577
|
+
/>
|
|
48578
|
+
</div>
|
|
48579
|
+
|
|
48580
|
+
<div class="role-section">
|
|
48581
|
+
@if (canManage) {
|
|
48582
|
+
<val-title
|
|
48583
|
+
[props]="{
|
|
48584
|
+
content: t('roleLabel'),
|
|
48585
|
+
size: 'small',
|
|
48586
|
+
color: 'dark',
|
|
48587
|
+
bold: false,
|
|
48588
|
+
}"
|
|
48589
|
+
/>
|
|
48590
|
+
<val-select-input data-testid="member-role-select" [props]="roleSelectProps()" />
|
|
48591
|
+
<val-text
|
|
48592
|
+
[props]="{
|
|
48593
|
+
content: t('roleHint'),
|
|
48594
|
+
size: 'small',
|
|
48595
|
+
color: 'medium',
|
|
48596
|
+
bold: false,
|
|
48597
|
+
}"
|
|
48598
|
+
/>
|
|
48599
|
+
} @else {
|
|
48600
|
+
<span class="role-readonly-label">{{ t('roleReadonly') }}</span>
|
|
48601
|
+
<span class="member-role-badge">{{ currentRoleLabel() }}</span>
|
|
48602
|
+
}
|
|
48603
|
+
</div>
|
|
48604
|
+
|
|
48605
|
+
@if (permissionsForCurrentRole().length > 0) {
|
|
48606
|
+
<div class="perms-section">
|
|
48607
|
+
<span class="perms-label">{{ t('permissionsTitle') }}</span>
|
|
48608
|
+
<div class="perms-list">
|
|
48609
|
+
@for (perm of permissionsForCurrentRole(); track perm) {
|
|
48610
|
+
<span class="perm-chip">{{ permissionLabel(perm) }}</span>
|
|
48611
|
+
}
|
|
48612
|
+
</div>
|
|
48613
|
+
</div>
|
|
48614
|
+
}
|
|
48615
|
+
|
|
48616
|
+
@if (canManage) {
|
|
48617
|
+
<div class="remove-section">
|
|
48618
|
+
<val-button [props]="removeButtonProps()" (click)="onRemove()" />
|
|
48619
|
+
</div>
|
|
48620
|
+
}
|
|
48621
|
+
</ion-content>
|
|
48622
|
+
`, styles: ["ion-content{--padding-bottom: 24px}.member-head{display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;margin-bottom:20px}val-display{display:block}val-title{display:block}.role-section{display:flex;flex-direction:column;gap:8px;margin-bottom:24px}.role-readonly-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.member-role-badge{font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:4px 12px;border-radius:20px;width:fit-content;background:color-mix(in srgb,var(--ion-color-primary) 12%,transparent);color:var(--ion-color-primary)}.perms-section{display:flex;flex-direction:column;gap:10px;margin-bottom:24px}.perms-label{font-size:.74rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ion-color-medium)}.perms-list{display:flex;flex-wrap:wrap;gap:6px}.perm-chip{font-size:.75rem;font-weight:500;padding:3px 10px;border-radius:20px;border:1px solid var(--val-border-color, rgba(0, 0, 0, .12));color:var(--ion-color-dark);background:transparent}.remove-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08));padding-top:20px}\n"] }]
|
|
48623
|
+
}], ctorParameters: () => [], propDecorators: { _modalRef: [{
|
|
48624
|
+
type: Input
|
|
48625
|
+
}], member: [{
|
|
48626
|
+
type: Input
|
|
48627
|
+
}], orgId: [{
|
|
48628
|
+
type: Input
|
|
48629
|
+
}], canManage: [{
|
|
48630
|
+
type: Input
|
|
48631
|
+
}], isSelf: [{
|
|
48632
|
+
type: Input
|
|
48633
|
+
}], availableRoles: [{
|
|
48634
|
+
type: Input
|
|
48635
|
+
}], onChanged: [{
|
|
48636
|
+
type: Input
|
|
48637
|
+
}], i18nNamespace: [{
|
|
48638
|
+
type: Input
|
|
48639
|
+
}] } });
|
|
48640
|
+
|
|
48641
|
+
/**
|
|
48642
|
+
* Defaults i18n (es/en) embebidos en `val-transfer-ownership-modal`.
|
|
48643
|
+
* Auto-registrados en el constructor del componente si el consumer no proveyó el
|
|
48644
|
+
* namespace (`Settings.TransferModal` por default). SOLO incluye las keys que el
|
|
48645
|
+
* modal usa.
|
|
48646
|
+
*/
|
|
48647
|
+
const TRANSFER_OWNERSHIP_MODAL_I18N = {
|
|
48648
|
+
es: {
|
|
48649
|
+
title: 'Transferir propiedad',
|
|
48650
|
+
hint: 'Selecciona el nuevo propietario de la organización.',
|
|
48651
|
+
searchPlaceholder: 'Buscar miembro...',
|
|
48652
|
+
empty: 'No hay otros miembros disponibles.',
|
|
48653
|
+
noResults: 'Sin resultados para',
|
|
48654
|
+
confirmCta: 'Confirmar transferencia',
|
|
48655
|
+
close: 'Cancelar',
|
|
48656
|
+
},
|
|
48657
|
+
en: {
|
|
48658
|
+
title: 'Transfer ownership',
|
|
48659
|
+
hint: 'Select the new owner of the organization.',
|
|
48660
|
+
searchPlaceholder: 'Search member...',
|
|
48661
|
+
empty: 'No other members available.',
|
|
48662
|
+
noResults: 'No results for',
|
|
48663
|
+
confirmCta: 'Confirm transfer',
|
|
48664
|
+
close: 'Cancel',
|
|
48665
|
+
},
|
|
48666
|
+
};
|
|
48667
|
+
|
|
48668
|
+
const DEFAULT_NAMESPACE$1 = 'Settings.TransferModal';
|
|
48669
|
+
/**
|
|
48670
|
+
* `val-transfer-ownership-modal` — selector de miembro para transferir la
|
|
48671
|
+
* propiedad de la organización. Promovido desde `showcase` bajo el proceso de
|
|
48672
|
+
* ADR-021.
|
|
48673
|
+
*
|
|
48674
|
+
* Recibe la lista de candidatos por `members` (todos excepto el owner actual),
|
|
48675
|
+
* permite filtrarlos con `val-searchbar` y, al confirmar, ejecuta el callback
|
|
48676
|
+
* `onConfirm(newOwnerId)`. La ejecución real de la transferencia (vía
|
|
48677
|
+
* `OrgService.transferOwnership`) queda en el consumer.
|
|
48678
|
+
*
|
|
48679
|
+
* Abierto vía `ModalService` — recibe `_modalRef` por `componentProps`. Header
|
|
48680
|
+
* canónico (Regla #5): sin `ion-title`, botón Cerrar texto en `slot=end`,
|
|
48681
|
+
* título/subtítulo en el body con `val-display`/`val-title`.
|
|
48682
|
+
*
|
|
48683
|
+
* Auto-registra sus defaults i18n (es/en) si el consumer no proveyó el namespace
|
|
48684
|
+
* `Settings.TransferModal`.
|
|
48685
|
+
*/
|
|
48686
|
+
class TransferOwnershipModalComponent {
|
|
48687
|
+
constructor() {
|
|
48688
|
+
this.i18n = inject(I18nService);
|
|
48689
|
+
/** Lista de miembros transferibles (todos excepto el owner actual). */
|
|
48690
|
+
this.members = [];
|
|
48691
|
+
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
48692
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$1;
|
|
48693
|
+
this.query = signal('');
|
|
48694
|
+
this.selectedId = signal('');
|
|
48695
|
+
this.filtered = computed(() => {
|
|
48696
|
+
const q = this.query().trim().toLowerCase();
|
|
48697
|
+
if (!q)
|
|
48698
|
+
return this.members;
|
|
48699
|
+
return this.members.filter(m => m.name?.toLowerCase().includes(q) ||
|
|
48700
|
+
m.email?.toLowerCase().includes(q) ||
|
|
48701
|
+
m.userId.toLowerCase().includes(q));
|
|
48702
|
+
});
|
|
48703
|
+
this.noResultsLabel = computed(() => `${this.t('noResults')} "${this.query()}"`);
|
|
48704
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$1)) {
|
|
48705
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$1, TRANSFER_OWNERSHIP_MODAL_I18N);
|
|
48706
|
+
}
|
|
48707
|
+
}
|
|
48708
|
+
ngOnInit() {
|
|
48709
|
+
if (!this.i18n.hasNamespace(this.i18nNamespace)) {
|
|
48710
|
+
this.i18n.registerContent(this.i18nNamespace, TRANSFER_OWNERSHIP_MODAL_I18N);
|
|
48711
|
+
}
|
|
48712
|
+
}
|
|
48713
|
+
confirm() {
|
|
48714
|
+
const id = this.selectedId();
|
|
48715
|
+
if (!id)
|
|
48716
|
+
return;
|
|
48717
|
+
this.onConfirm?.(id);
|
|
48718
|
+
this.dismiss();
|
|
48719
|
+
}
|
|
48720
|
+
dismiss() {
|
|
48721
|
+
this._modalRef?.dismiss(null, 'cancel');
|
|
48722
|
+
}
|
|
48723
|
+
t(key) {
|
|
48724
|
+
return this.i18n.t(key, this.i18nNamespace);
|
|
48725
|
+
}
|
|
48726
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TransferOwnershipModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
48727
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: TransferOwnershipModalComponent, isStandalone: true, selector: "val-transfer-ownership-modal", inputs: { members: "members", onConfirm: "onConfirm", _modalRef: "_modalRef", i18nNamespace: "i18nNamespace" }, ngImport: i0, template: `
|
|
48728
|
+
<ion-header>
|
|
48729
|
+
<ion-toolbar>
|
|
48730
|
+
<ion-buttons slot="end">
|
|
48731
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
48732
|
+
<strong>{{ t('close') }}</strong>
|
|
48733
|
+
</ion-button>
|
|
48734
|
+
</ion-buttons>
|
|
48735
|
+
</ion-toolbar>
|
|
48736
|
+
</ion-header>
|
|
48737
|
+
|
|
48738
|
+
<ion-content class="ion-padding">
|
|
48739
|
+
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
48740
|
+
<val-title
|
|
48741
|
+
[props]="{
|
|
48742
|
+
content: t('hint'),
|
|
48743
|
+
size: 'large',
|
|
48744
|
+
color: '',
|
|
48745
|
+
bold: false,
|
|
48746
|
+
}"
|
|
48747
|
+
/>
|
|
48748
|
+
|
|
48749
|
+
<val-searchbar
|
|
48750
|
+
[props]="{ placeholder: t('searchPlaceholder'), debounce: 150 }"
|
|
48751
|
+
(filterEvent)="query.set($event)"
|
|
48752
|
+
/>
|
|
48753
|
+
|
|
48754
|
+
@if (members.length === 0) {
|
|
48755
|
+
<val-text
|
|
48756
|
+
[props]="{
|
|
48757
|
+
content: t('empty'),
|
|
48758
|
+
size: 'medium',
|
|
48759
|
+
color: 'medium',
|
|
48760
|
+
bold: false,
|
|
48761
|
+
}"
|
|
48762
|
+
/>
|
|
48763
|
+
} @else if (filtered().length === 0) {
|
|
48764
|
+
<val-text
|
|
48765
|
+
[props]="{
|
|
48766
|
+
content: noResultsLabel(),
|
|
48767
|
+
size: 'medium',
|
|
48768
|
+
color: 'medium',
|
|
48769
|
+
bold: false,
|
|
48770
|
+
}"
|
|
48771
|
+
/>
|
|
48772
|
+
} @else {
|
|
48773
|
+
<div class="transfer-member-list">
|
|
48774
|
+
@for (m of filtered(); track m.userId) {
|
|
48775
|
+
<button
|
|
48776
|
+
class="transfer-member-row"
|
|
48777
|
+
[class.transfer-member-row--selected]="selectedId() === m.userId"
|
|
48778
|
+
(click)="selectedId.set(m.userId)"
|
|
48779
|
+
type="button"
|
|
48780
|
+
>
|
|
48781
|
+
<span class="transfer-member-name">{{ m.name || m.email || m.userId }}</span>
|
|
48782
|
+
@if (selectedId() === m.userId) {
|
|
48783
|
+
<span class="transfer-member-check">✓</span>
|
|
48784
|
+
}
|
|
48785
|
+
</button>
|
|
48786
|
+
}
|
|
48787
|
+
</div>
|
|
48788
|
+
}
|
|
48789
|
+
|
|
48790
|
+
<div class="transfer-confirm-row">
|
|
48791
|
+
<ion-button expand="block" color="dark" [disabled]="!selectedId()" (click)="confirm()">
|
|
48792
|
+
{{ t('confirmCta') }}
|
|
48793
|
+
</ion-button>
|
|
48794
|
+
</div>
|
|
48795
|
+
</ion-content>
|
|
48796
|
+
`, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:16px}val-searchbar{display:block;margin-bottom:12px}.transfer-member-list{display:flex;flex-direction:column;gap:4px}.transfer-member-row{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-radius:10px;border:1.5px solid var(--ion-color-light);background:transparent;cursor:pointer;width:100%;text-align:left;font-size:15px;color:var(--ion-color-dark);transition:border-color .15s}.transfer-member-row--selected{border-color:var(--ion-color-dark);background:var(--ion-color-light)}.transfer-member-check{font-size:17px;color:var(--ion-color-dark)}.transfer-confirm-row{margin-top:24px}\n"], dependencies: [{ 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: SearchbarComponent, selector: "val-searchbar", inputs: ["preset", "props"], outputs: ["filterEvent", "focusEvent", "blurEvent"] }, { 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"] }] }); }
|
|
48797
|
+
}
|
|
48798
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TransferOwnershipModalComponent, decorators: [{
|
|
48799
|
+
type: Component,
|
|
48800
|
+
args: [{ selector: 'val-transfer-ownership-modal', standalone: true, imports: [
|
|
48801
|
+
IonHeader,
|
|
48802
|
+
IonToolbar,
|
|
48803
|
+
IonButtons,
|
|
48804
|
+
IonButton,
|
|
48805
|
+
IonContent,
|
|
48806
|
+
SearchbarComponent,
|
|
48807
|
+
DisplayComponent,
|
|
48808
|
+
TextComponent,
|
|
48809
|
+
TitleComponent,
|
|
48810
|
+
], template: `
|
|
48811
|
+
<ion-header>
|
|
48812
|
+
<ion-toolbar>
|
|
48813
|
+
<ion-buttons slot="end">
|
|
48814
|
+
<ion-button fill="clear" color="dark" (click)="dismiss()">
|
|
48815
|
+
<strong>{{ t('close') }}</strong>
|
|
48816
|
+
</ion-button>
|
|
48817
|
+
</ion-buttons>
|
|
48818
|
+
</ion-toolbar>
|
|
48819
|
+
</ion-header>
|
|
48820
|
+
|
|
48821
|
+
<ion-content class="ion-padding">
|
|
48822
|
+
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
48823
|
+
<val-title
|
|
48824
|
+
[props]="{
|
|
48825
|
+
content: t('hint'),
|
|
48826
|
+
size: 'large',
|
|
48827
|
+
color: '',
|
|
48828
|
+
bold: false,
|
|
48829
|
+
}"
|
|
48830
|
+
/>
|
|
48831
|
+
|
|
48832
|
+
<val-searchbar
|
|
48833
|
+
[props]="{ placeholder: t('searchPlaceholder'), debounce: 150 }"
|
|
48834
|
+
(filterEvent)="query.set($event)"
|
|
48835
|
+
/>
|
|
48836
|
+
|
|
48837
|
+
@if (members.length === 0) {
|
|
48838
|
+
<val-text
|
|
48839
|
+
[props]="{
|
|
48840
|
+
content: t('empty'),
|
|
48841
|
+
size: 'medium',
|
|
48842
|
+
color: 'medium',
|
|
48843
|
+
bold: false,
|
|
48844
|
+
}"
|
|
48845
|
+
/>
|
|
48846
|
+
} @else if (filtered().length === 0) {
|
|
48847
|
+
<val-text
|
|
48848
|
+
[props]="{
|
|
48849
|
+
content: noResultsLabel(),
|
|
48850
|
+
size: 'medium',
|
|
48851
|
+
color: 'medium',
|
|
48852
|
+
bold: false,
|
|
48853
|
+
}"
|
|
48854
|
+
/>
|
|
48855
|
+
} @else {
|
|
48856
|
+
<div class="transfer-member-list">
|
|
48857
|
+
@for (m of filtered(); track m.userId) {
|
|
48858
|
+
<button
|
|
48859
|
+
class="transfer-member-row"
|
|
48860
|
+
[class.transfer-member-row--selected]="selectedId() === m.userId"
|
|
48861
|
+
(click)="selectedId.set(m.userId)"
|
|
48862
|
+
type="button"
|
|
48863
|
+
>
|
|
48864
|
+
<span class="transfer-member-name">{{ m.name || m.email || m.userId }}</span>
|
|
48865
|
+
@if (selectedId() === m.userId) {
|
|
48866
|
+
<span class="transfer-member-check">✓</span>
|
|
48867
|
+
}
|
|
48868
|
+
</button>
|
|
48869
|
+
}
|
|
48870
|
+
</div>
|
|
48871
|
+
}
|
|
48872
|
+
|
|
48873
|
+
<div class="transfer-confirm-row">
|
|
48874
|
+
<ion-button expand="block" color="dark" [disabled]="!selectedId()" (click)="confirm()">
|
|
48875
|
+
{{ t('confirmCta') }}
|
|
48876
|
+
</ion-button>
|
|
48877
|
+
</div>
|
|
48878
|
+
</ion-content>
|
|
48879
|
+
`, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:16px}val-searchbar{display:block;margin-bottom:12px}.transfer-member-list{display:flex;flex-direction:column;gap:4px}.transfer-member-row{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-radius:10px;border:1.5px solid var(--ion-color-light);background:transparent;cursor:pointer;width:100%;text-align:left;font-size:15px;color:var(--ion-color-dark);transition:border-color .15s}.transfer-member-row--selected{border-color:var(--ion-color-dark);background:var(--ion-color-light)}.transfer-member-check{font-size:17px;color:var(--ion-color-dark)}.transfer-confirm-row{margin-top:24px}\n"] }]
|
|
48880
|
+
}], ctorParameters: () => [], propDecorators: { members: [{
|
|
48881
|
+
type: Input
|
|
48882
|
+
}], onConfirm: [{
|
|
48883
|
+
type: Input
|
|
48884
|
+
}], _modalRef: [{
|
|
48885
|
+
type: Input
|
|
48886
|
+
}], i18nNamespace: [{
|
|
48887
|
+
type: Input
|
|
48888
|
+
}] } });
|
|
48889
|
+
|
|
48890
|
+
/**
|
|
48891
|
+
* Defaults i18n (es/en) embebidos en `val-organization-view`. Auto-registrados en
|
|
48892
|
+
* el constructor del componente si el consumer no proveyó el namespace
|
|
48893
|
+
* (`Settings.Organization` por default). SOLO incluye las keys que la vista usa.
|
|
48894
|
+
*
|
|
48895
|
+
* Portado de `ORG_I18N_CONTENT` (showcase) bajo el proceso de ADR-021.
|
|
48896
|
+
*/
|
|
48897
|
+
const ORGANIZATION_VIEW_I18N = {
|
|
48898
|
+
es: {
|
|
48899
|
+
pageTitle: 'Organización',
|
|
48900
|
+
pageDescription: 'Gestiona tu organización activa',
|
|
48901
|
+
infoTitle: 'Información',
|
|
48902
|
+
infoName: 'Nombre',
|
|
48903
|
+
infoDescription: 'Descripción',
|
|
48904
|
+
infoPlan: 'Plan',
|
|
48905
|
+
infoType: 'Tipo',
|
|
48906
|
+
editCta: 'Editar',
|
|
48907
|
+
saveCta: 'Guardar',
|
|
48908
|
+
cancelCta: 'Cancelar',
|
|
48909
|
+
membersTitle: 'Miembros',
|
|
48910
|
+
membersEmpty: 'Sin miembros aún.',
|
|
48911
|
+
memberRoles: 'Roles',
|
|
48912
|
+
inviteTitle: 'Invitar',
|
|
48913
|
+
inviteEmail: 'Correo electrónico',
|
|
48914
|
+
inviteRole: 'Rol',
|
|
48915
|
+
inviteCta: 'Invitar',
|
|
48916
|
+
inviteSuccess: 'Invitación enviada.',
|
|
48917
|
+
inviteError: 'No se pudo enviar la invitación.',
|
|
48918
|
+
membersError: 'No se pudieron cargar los miembros.',
|
|
48919
|
+
leaveTitle: 'Salir de la organización',
|
|
48920
|
+
leaveHint: 'Dejarás de tener acceso a esta organización.',
|
|
48921
|
+
leaveCta: 'Salir',
|
|
48922
|
+
leaveOwnerHint: 'Eres el propietario. Transfiere la propiedad antes de salir.',
|
|
48923
|
+
leaveConfirm: '¿Salir de la organización?',
|
|
48924
|
+
planFree: 'Gratuito',
|
|
48925
|
+
planPro: 'Pro',
|
|
48926
|
+
planEnterprise: 'Empresa',
|
|
48927
|
+
saveSuccess: 'Organización actualizada.',
|
|
48928
|
+
leaveSuccess: 'Has salido de la organización.',
|
|
48929
|
+
errorTitle: 'No se pudo cargar la organización',
|
|
48930
|
+
offlineTitle: 'Sin conexión',
|
|
48931
|
+
offlineHint: 'Verifica tu conexión e intenta de nuevo.',
|
|
48932
|
+
retry: 'Reintentar',
|
|
48933
|
+
saveError: 'No se pudo guardar. Intenta de nuevo.',
|
|
48934
|
+
leaveError: 'No se pudo salir. Intenta de nuevo.',
|
|
48935
|
+
transferTitle: 'Transferir propiedad',
|
|
48936
|
+
transferHint: 'Elige un miembro para transferirle la propiedad de la organización.',
|
|
48937
|
+
transferSelect: 'Seleccionar nuevo propietario',
|
|
48938
|
+
transferCta: 'Transferir',
|
|
48939
|
+
transferConfirm: '¿Transferir la propiedad a este miembro? Perderás el rol de propietario.',
|
|
48940
|
+
transferSuccess: 'Propiedad transferida.',
|
|
48941
|
+
transferError: 'No se pudo transferir la propiedad.',
|
|
48942
|
+
inviteCtaTitle: '¿Deseas agregar a alguien?',
|
|
48943
|
+
inviteCtaHint: 'Invita a usuarios a unirse a tu organización.',
|
|
48944
|
+
inviteOpen: 'Invitar',
|
|
48945
|
+
viewMemberCta: 'Ver miembro',
|
|
48946
|
+
roleViewer: 'Visor',
|
|
48947
|
+
roleEditor: 'Editor',
|
|
48948
|
+
roleAdmin: 'Admin',
|
|
48949
|
+
roleSuperAdmin: 'Super Admin',
|
|
48950
|
+
rolesSectionTitle: 'Roles y permisos',
|
|
48951
|
+
rolesHint: 'Tu organización tiene roles que controlan lo que cada miembro puede ver y hacer. Cada rol lleva un conjunto de permisos asociados.',
|
|
48952
|
+
rolesViewPerms: 'Ver roles y permisos',
|
|
48953
|
+
rolesSystemBadge: 'Sistema',
|
|
48954
|
+
permAll: 'Todo',
|
|
48955
|
+
permUsers: 'Usuarios',
|
|
48956
|
+
permDocuments: 'Documentos',
|
|
48957
|
+
permRoles: 'Roles',
|
|
48958
|
+
permMedia: 'Media',
|
|
48959
|
+
permTemplates: 'Plantillas',
|
|
48960
|
+
permRead: 'Leer',
|
|
48961
|
+
permWrite: 'Escribir',
|
|
48962
|
+
permCreate: 'Crear',
|
|
48963
|
+
permDelete: 'Eliminar',
|
|
48964
|
+
permManage: 'Gestionar',
|
|
48965
|
+
membersShowMore: 'Ver más',
|
|
48966
|
+
},
|
|
48967
|
+
en: {
|
|
48968
|
+
pageTitle: 'Organization',
|
|
48969
|
+
pageDescription: 'Manage your active organization',
|
|
48970
|
+
infoTitle: 'Information',
|
|
48971
|
+
infoName: 'Name',
|
|
48972
|
+
infoDescription: 'Description',
|
|
48973
|
+
infoPlan: 'Plan',
|
|
48974
|
+
infoType: 'Type',
|
|
48975
|
+
editCta: 'Edit',
|
|
48976
|
+
saveCta: 'Save',
|
|
48977
|
+
cancelCta: 'Cancel',
|
|
48978
|
+
membersTitle: 'Members',
|
|
48979
|
+
membersEmpty: 'No members yet.',
|
|
48980
|
+
memberRoles: 'Roles',
|
|
48981
|
+
inviteTitle: 'Invite',
|
|
48982
|
+
inviteEmail: 'Email address',
|
|
48983
|
+
inviteRole: 'Role',
|
|
48984
|
+
inviteCta: 'Invite',
|
|
48985
|
+
inviteSuccess: 'Invitation sent.',
|
|
48986
|
+
inviteError: 'Could not send invitation.',
|
|
48987
|
+
membersError: 'Could not load members.',
|
|
48988
|
+
leaveTitle: 'Leave organization',
|
|
48989
|
+
leaveHint: 'You will lose access to this organization.',
|
|
48990
|
+
leaveCta: 'Leave',
|
|
48991
|
+
leaveOwnerHint: 'You are the owner. Transfer ownership before leaving.',
|
|
48992
|
+
leaveConfirm: 'Leave this organization?',
|
|
48993
|
+
planFree: 'Free',
|
|
48994
|
+
planPro: 'Pro',
|
|
48995
|
+
planEnterprise: 'Enterprise',
|
|
48996
|
+
saveSuccess: 'Organization updated.',
|
|
48997
|
+
leaveSuccess: 'You have left the organization.',
|
|
48998
|
+
errorTitle: 'Could not load organization',
|
|
48999
|
+
offlineTitle: 'No connection',
|
|
49000
|
+
offlineHint: 'Check your connection and try again.',
|
|
49001
|
+
retry: 'Retry',
|
|
49002
|
+
saveError: 'Could not save. Please try again.',
|
|
49003
|
+
leaveError: 'Could not leave. Please try again.',
|
|
49004
|
+
transferTitle: 'Transfer ownership',
|
|
49005
|
+
transferHint: 'Choose a member to transfer ownership of this organization.',
|
|
49006
|
+
transferSelect: 'Select new owner',
|
|
49007
|
+
transferCta: 'Transfer',
|
|
49008
|
+
transferConfirm: 'Transfer ownership to this member? You will lose the owner role.',
|
|
49009
|
+
transferSuccess: 'Ownership transferred.',
|
|
49010
|
+
transferError: 'Could not transfer ownership.',
|
|
49011
|
+
inviteCtaTitle: 'Want to add someone?',
|
|
49012
|
+
inviteCtaHint: 'Invite users to join your organization.',
|
|
49013
|
+
inviteOpen: 'Invite',
|
|
49014
|
+
viewMemberCta: 'View member',
|
|
49015
|
+
roleViewer: 'Viewer',
|
|
49016
|
+
roleEditor: 'Editor',
|
|
49017
|
+
roleAdmin: 'Admin',
|
|
49018
|
+
roleSuperAdmin: 'Super Admin',
|
|
49019
|
+
rolesSectionTitle: 'Roles & permissions',
|
|
49020
|
+
rolesHint: 'Your organization has roles that control what each member can see and do. Each role carries a set of associated permissions.',
|
|
49021
|
+
rolesViewPerms: 'View roles & permissions',
|
|
49022
|
+
rolesSystemBadge: 'System',
|
|
49023
|
+
permAll: 'All',
|
|
49024
|
+
permUsers: 'Users',
|
|
49025
|
+
permDocuments: 'Documents',
|
|
49026
|
+
permRoles: 'Roles',
|
|
49027
|
+
permMedia: 'Media',
|
|
49028
|
+
permTemplates: 'Templates',
|
|
49029
|
+
permRead: 'Read',
|
|
49030
|
+
permWrite: 'Write',
|
|
49031
|
+
permCreate: 'Create',
|
|
49032
|
+
permDelete: 'Delete',
|
|
49033
|
+
permManage: 'Manage',
|
|
49034
|
+
membersShowMore: 'Show more',
|
|
49035
|
+
},
|
|
49036
|
+
};
|
|
49037
|
+
|
|
49038
|
+
const DEFAULT_NAMESPACE = 'Settings.Organization';
|
|
49039
|
+
const DEFAULT_VIEW_PERMISSIONS_ROUTE = '/app/settings/permissions';
|
|
49040
|
+
const DEFAULT_LEAVE_REDIRECT_ROUTE = '/app/settings/account';
|
|
49041
|
+
/**
|
|
49042
|
+
* `val-organization-view` — vista Gestión de organización full-feature
|
|
49043
|
+
* autocontenida (organism). Promovida desde `showcase` bajo el proceso de
|
|
49044
|
+
* ADR-021. Reúne:
|
|
49045
|
+
* - **Información** — nombre, descripción, plan y tipo de la org activa; editar
|
|
49046
|
+
* vía modal `val-edit-org-modal` (solo si `canEditOrg()`).
|
|
49047
|
+
* - **Miembros** — lista paginada (`val-member-card`) enriquecida con perfiles
|
|
49048
|
+
* públicos (Firestore); ver detalle vía `val-member-detail-modal`.
|
|
49049
|
+
* - **Roles y permisos** — sección explicativa con CTA a la ruta de permisos.
|
|
49050
|
+
* - **Invitar** — CTA que abre `val-invite-member-modal` (gateada por `users:*`).
|
|
49051
|
+
* - **Transferir propiedad** — `val-transfer-ownership-modal` (solo owner).
|
|
49052
|
+
* - **Salir de la organización** — `OrgService.leaveOrg` con confirmación
|
|
49053
|
+
* destructiva.
|
|
49054
|
+
*
|
|
49055
|
+
* **RBAC interno** (decisión CTO, ADR-021): el gating fino se resuelve dentro de
|
|
49056
|
+
* la vista vía `AuthService.hasPermission()` + `*valHasPermission` + computeds
|
|
49057
|
+
* `isOwner()`/`canManageMembers()`/`canEditOrg()`. NO se expone como config.
|
|
49058
|
+
*
|
|
49059
|
+
* **Auto-reload en org-switch**: un `effect` reactiona a `activeOrgId()` y recarga
|
|
49060
|
+
* org + miembros cuando el usuario cambia de organización activa mientras la vista
|
|
49061
|
+
* está montada. El effect cubre también la carga inicial (sin doble carga).
|
|
49062
|
+
*
|
|
49063
|
+
* NO renderiza ion-content — vive dentro de val-page-wrapper. `ActivatedRoute` se
|
|
49064
|
+
* inyecta `{ optional: true }` solo para leer el route data de config (no-op
|
|
49065
|
+
* embebido). Auto-registra sus defaults i18n (es/en) si el consumer no proveyó el
|
|
49066
|
+
* namespace (default `Settings.Organization`).
|
|
49067
|
+
*/
|
|
49068
|
+
class OrganizationViewComponent {
|
|
49069
|
+
/** Namespace i18n resuelto (capturado para llamadas no-reactivas). */
|
|
49070
|
+
get ns() {
|
|
49071
|
+
return this.resolvedConfig().i18nNamespace;
|
|
49072
|
+
}
|
|
49073
|
+
constructor() {
|
|
49074
|
+
this.nav = inject(NavigationService);
|
|
49075
|
+
this.i18n = inject(I18nService);
|
|
49076
|
+
this.auth = inject(AuthService);
|
|
49077
|
+
this.orgService = inject(OrgService);
|
|
49078
|
+
this.toast = inject(ToastService);
|
|
49079
|
+
this.errors = inject(ValtechErrorService);
|
|
49080
|
+
this.modalService = inject(ModalService);
|
|
49081
|
+
this.firestore = inject(Firestore);
|
|
49082
|
+
this.confirmDialog = inject(ConfirmationDialogService);
|
|
49083
|
+
this.route = inject(ActivatedRoute, { optional: true });
|
|
49084
|
+
this.isDebug = isDevMode();
|
|
49085
|
+
this.resolvedConfig = computed(() => {
|
|
49086
|
+
const fromRoute = (this.route?.snapshot.data['organizationConfig'] ?? {});
|
|
49087
|
+
const merged = { ...fromRoute, ...(this.config ?? {}) };
|
|
49088
|
+
return {
|
|
49089
|
+
showInfo: merged.showInfo ?? true,
|
|
49090
|
+
showMembers: merged.showMembers ?? true,
|
|
49091
|
+
showRoles: merged.showRoles ?? true,
|
|
49092
|
+
showInvite: merged.showInvite ?? true,
|
|
49093
|
+
showTransferOwnership: merged.showTransferOwnership ?? true,
|
|
49094
|
+
showLeave: merged.showLeave ?? true,
|
|
49095
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
49096
|
+
viewPermissionsRoute: merged.viewPermissionsRoute ?? DEFAULT_VIEW_PERMISSIONS_ROUTE,
|
|
49097
|
+
leaveRedirectRoute: merged.leaveRedirectRoute ?? DEFAULT_LEAVE_REDIRECT_ROUTE,
|
|
49098
|
+
onOrgEdited: merged.onOrgEdited,
|
|
49099
|
+
onMemberInvited: merged.onMemberInvited,
|
|
49100
|
+
onOwnershipTransferred: merged.onOwnershipTransferred,
|
|
49101
|
+
onLeftOrg: merged.onLeftOrg,
|
|
49102
|
+
};
|
|
49103
|
+
});
|
|
49104
|
+
this.org = signal(null);
|
|
49105
|
+
this.loading = signal(false);
|
|
49106
|
+
this.leaving = signal(false);
|
|
49107
|
+
this.orgLoadError = signal(null);
|
|
49108
|
+
this.orgErrorState = computed(() => {
|
|
49109
|
+
this.i18n.lang();
|
|
49110
|
+
const err = this.orgLoadError();
|
|
49111
|
+
if (!err)
|
|
49112
|
+
return null;
|
|
49113
|
+
return createErrorStateProps(err, {
|
|
49114
|
+
title: { offline: this.tt('offlineTitle'), error: this.tt('errorTitle') },
|
|
49115
|
+
description: {
|
|
49116
|
+
offline: this.tt('offlineHint'),
|
|
49117
|
+
error: interpretError(err).message,
|
|
49118
|
+
},
|
|
49119
|
+
retryLabel: this.tt('retry'),
|
|
49120
|
+
onRetry: () => this.loadOrg(),
|
|
49121
|
+
retrying: this.loading(),
|
|
49122
|
+
});
|
|
49123
|
+
});
|
|
49124
|
+
this.members = signal([]);
|
|
49125
|
+
this.membersLoading = signal(false);
|
|
49126
|
+
this.membersLoadError = signal(null);
|
|
49127
|
+
this.membersNextToken = signal(null);
|
|
49128
|
+
this.loadingMoreMembers = signal(false);
|
|
49129
|
+
this.showAllMembers = signal(false);
|
|
49130
|
+
this.visibleMembers = computed(() => (this.showAllMembers() ? this.members() : this.members().slice(0, 3)));
|
|
49131
|
+
this.transferring = signal(false);
|
|
49132
|
+
this.availableRoles = signal([
|
|
49133
|
+
{ id: 'viewer', name: 'viewer' },
|
|
49134
|
+
{ id: 'editor', name: 'editor' },
|
|
49135
|
+
{ id: 'admin', name: 'admin' },
|
|
49136
|
+
]);
|
|
49137
|
+
this.activeOrgId = computed(() => {
|
|
49138
|
+
const u = this.auth.user();
|
|
49139
|
+
return u?.activeOrgId ?? u?.activeOrg ?? '';
|
|
49140
|
+
});
|
|
49141
|
+
this.userId = computed(() => this.auth.user()?.userId ?? '');
|
|
49142
|
+
this.isOwner = computed(() => {
|
|
49143
|
+
const o = this.org();
|
|
49144
|
+
const uid = this.userId();
|
|
49145
|
+
return !!o && !!uid && o.ownerId === uid;
|
|
49146
|
+
});
|
|
49147
|
+
this.canEditOrg = computed(() => this.isOwner() || this.auth.hasPermission('org:manage'));
|
|
49148
|
+
this.canManageMembers = computed(() => {
|
|
49149
|
+
if (this.isOwner())
|
|
49150
|
+
return true;
|
|
49151
|
+
const uid = this.userId();
|
|
49152
|
+
const me = this.members().find(m => m.userId === uid);
|
|
49153
|
+
if (!me)
|
|
49154
|
+
return false;
|
|
49155
|
+
return me.roles.some(r => r === 'admin' || r === 'super_admin');
|
|
49156
|
+
});
|
|
49157
|
+
this.pageTitle = computed(() => this.tt('pageTitle'));
|
|
49158
|
+
this.pageDescription = computed(() => this.tt('pageDescription'));
|
|
49159
|
+
this.planLabel = computed(() => {
|
|
49160
|
+
const plan = this.org()?.plan;
|
|
49161
|
+
if (plan === 'pro')
|
|
49162
|
+
return this.tt('planPro');
|
|
49163
|
+
if (plan === 'enterprise')
|
|
49164
|
+
return this.tt('planEnterprise');
|
|
49165
|
+
return this.tt('planFree');
|
|
49166
|
+
});
|
|
49167
|
+
this.editButtonProps = computed(() => ({
|
|
49168
|
+
text: this.tt('editCta'),
|
|
49169
|
+
color: 'primary',
|
|
49170
|
+
fill: 'outline',
|
|
49171
|
+
size: 'small',
|
|
49172
|
+
shape: 'round',
|
|
49173
|
+
type: 'button',
|
|
49174
|
+
}));
|
|
49175
|
+
this.leaveButtonProps = computed(() => ({
|
|
49176
|
+
text: this.tt('leaveCta'),
|
|
49177
|
+
token: 'org-leave',
|
|
49178
|
+
color: 'dark',
|
|
49179
|
+
fill: 'outline',
|
|
49180
|
+
size: 'default',
|
|
49181
|
+
shape: 'round',
|
|
49182
|
+
type: 'button',
|
|
49183
|
+
state: this.isOwner() || this.leaving() ? ComponentStates.DISABLED : ComponentStates.ENABLED,
|
|
49184
|
+
}));
|
|
49185
|
+
this.transferableMembers = computed(() => this.members().filter(m => m.userId !== this.userId()));
|
|
49186
|
+
this.viewPermissionsButtonProps = computed(() => ({
|
|
49187
|
+
text: this.tt('rolesViewPerms'),
|
|
49188
|
+
color: 'primary',
|
|
49189
|
+
fill: 'outline',
|
|
49190
|
+
size: 'default',
|
|
49191
|
+
shape: 'round',
|
|
49192
|
+
type: 'button',
|
|
49193
|
+
token: 'org-view-permissions',
|
|
49194
|
+
}));
|
|
49195
|
+
this.transferButtonProps = computed(() => ({
|
|
49196
|
+
text: this.tt('transferCta'),
|
|
49197
|
+
token: 'org-transfer-submit',
|
|
49198
|
+
color: 'dark',
|
|
49199
|
+
fill: 'outline',
|
|
49200
|
+
size: 'default',
|
|
49201
|
+
shape: 'round',
|
|
49202
|
+
type: 'button',
|
|
49203
|
+
state: this.transferring() ? ComponentStates.DISABLED : ComponentStates.ENABLED,
|
|
49204
|
+
}));
|
|
49205
|
+
this.openInviteButtonProps = computed(() => ({
|
|
49206
|
+
text: this.tt('inviteOpen'),
|
|
49207
|
+
color: 'primary',
|
|
49208
|
+
fill: 'solid',
|
|
49209
|
+
size: 'default',
|
|
49210
|
+
shape: 'round',
|
|
49211
|
+
type: 'button',
|
|
49212
|
+
}));
|
|
49213
|
+
/** Guarda para detectar el primer disparo del effect (evita doble carga). */
|
|
49214
|
+
this.lastLoadedOrgId = null;
|
|
49215
|
+
const ns = this.ns;
|
|
49216
|
+
if (!this.i18n.hasNamespace(ns)) {
|
|
49217
|
+
this.i18n.registerContent(ns, ORGANIZATION_VIEW_I18N);
|
|
49218
|
+
}
|
|
49219
|
+
this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
|
|
49220
|
+
// Auto-reload en org-switch: reacciona a activeOrgId() y recarga org + miembros
|
|
49221
|
+
// cuando el usuario cambia de organización activa mientras la vista está
|
|
49222
|
+
// montada. Cubre también la carga inicial — `lastLoadedOrgId` evita recargar
|
|
49223
|
+
// dos veces el mismo id (p.ej. si el effect re-corre por otra dependencia).
|
|
49224
|
+
// `allowSignalWrites` porque las load fns escriben signals síncronamente
|
|
49225
|
+
// (loading/error/token) — Angular 18 bloquea writes en effect sin el flag.
|
|
49226
|
+
effect(() => {
|
|
49227
|
+
const orgId = this.activeOrgId();
|
|
49228
|
+
if (orgId === this.lastLoadedOrgId)
|
|
49229
|
+
return;
|
|
49230
|
+
this.lastLoadedOrgId = orgId;
|
|
49231
|
+
this.loadOrg();
|
|
49232
|
+
this.loadMembers();
|
|
49233
|
+
this.loadRoles();
|
|
49234
|
+
}, { allowSignalWrites: true });
|
|
49235
|
+
connectPageRefresh(async () => {
|
|
49236
|
+
await Promise.all([this.loadOrg(), this.loadMembers()]);
|
|
49237
|
+
});
|
|
49238
|
+
}
|
|
49239
|
+
onEdit() {
|
|
49240
|
+
void this.modalService.open({
|
|
49241
|
+
component: EditOrgModalComponent,
|
|
49242
|
+
componentProps: {
|
|
49243
|
+
org: this.org(),
|
|
49244
|
+
onSuccess: (updated) => {
|
|
49245
|
+
this.org.set(updated);
|
|
49246
|
+
this.resolvedConfig().onOrgEdited?.();
|
|
49247
|
+
},
|
|
49248
|
+
},
|
|
49249
|
+
breakpoints: {
|
|
49250
|
+
breakpoints: [0, 0.85],
|
|
49251
|
+
initialBreakpoint: 0.85,
|
|
49252
|
+
showHandle: true,
|
|
49253
|
+
},
|
|
49254
|
+
backdropDismiss: true,
|
|
49255
|
+
});
|
|
49256
|
+
}
|
|
49257
|
+
async onLeave() {
|
|
49258
|
+
if (this.isOwner() || this.leaving())
|
|
49259
|
+
return;
|
|
49260
|
+
const result = await this.confirmDialog.confirmDestructive({
|
|
49261
|
+
title: this.tt('leaveTitle'),
|
|
49262
|
+
message: this.tt('leaveConfirm'),
|
|
49263
|
+
confirmButton: { text: this.tt('leaveCta') },
|
|
49264
|
+
cancelButton: { text: this.tt('cancelCta') },
|
|
49265
|
+
});
|
|
49266
|
+
if (!result.confirmed)
|
|
49267
|
+
return;
|
|
49268
|
+
const orgId = this.activeOrgId();
|
|
49269
|
+
if (!orgId)
|
|
49270
|
+
return;
|
|
49271
|
+
this.leaving.set(true);
|
|
49272
|
+
try {
|
|
49273
|
+
await firstValueFrom(this.orgService.leaveOrg(orgId));
|
|
49274
|
+
this.toast.show({
|
|
49275
|
+
message: this.tt('leaveSuccess'),
|
|
49276
|
+
color: 'dark',
|
|
49277
|
+
duration: 3500,
|
|
49278
|
+
});
|
|
49279
|
+
const cfg = this.resolvedConfig();
|
|
49280
|
+
this.nav.navigateByUrl(cfg.leaveRedirectRoute);
|
|
49281
|
+
cfg.onLeftOrg?.();
|
|
49282
|
+
}
|
|
49283
|
+
catch (err) {
|
|
49284
|
+
this.errors.handle(err, {
|
|
49285
|
+
context: 'organization.leave',
|
|
49286
|
+
fallbackKey: 'leaveError',
|
|
49287
|
+
i18nNamespace: this.ns,
|
|
49288
|
+
});
|
|
49289
|
+
}
|
|
49290
|
+
finally {
|
|
49291
|
+
this.leaving.set(false);
|
|
49292
|
+
}
|
|
49293
|
+
}
|
|
49294
|
+
onOpenInvite() {
|
|
49295
|
+
void this.modalService.open({
|
|
49296
|
+
component: InviteMemberModalComponent,
|
|
49297
|
+
componentProps: {
|
|
49298
|
+
orgId: this.activeOrgId(),
|
|
49299
|
+
existingMemberIds: this.members().map(m => m.userId),
|
|
49300
|
+
onSuccess: () => {
|
|
49301
|
+
this.loadMembers();
|
|
49302
|
+
this.resolvedConfig().onMemberInvited?.();
|
|
49303
|
+
},
|
|
49304
|
+
},
|
|
49305
|
+
breakpoints: {
|
|
49306
|
+
breakpoints: [0, 0.92],
|
|
49307
|
+
initialBreakpoint: 0.92,
|
|
49308
|
+
showHandle: true,
|
|
49309
|
+
},
|
|
49310
|
+
backdropDismiss: true,
|
|
49311
|
+
});
|
|
49312
|
+
}
|
|
49313
|
+
onViewPermissions() {
|
|
49314
|
+
this.nav.navigateByUrl(this.resolvedConfig().viewPermissionsRoute);
|
|
49315
|
+
}
|
|
49316
|
+
onOpenTransfer() {
|
|
49317
|
+
void this.modalService.open({
|
|
49318
|
+
component: TransferOwnershipModalComponent,
|
|
49319
|
+
componentProps: {
|
|
49320
|
+
members: this.transferableMembers(),
|
|
49321
|
+
onConfirm: (newOwnerId) => void this.onTransfer(newOwnerId),
|
|
49322
|
+
},
|
|
49323
|
+
breakpoints: {
|
|
49324
|
+
breakpoints: [0, 0.75],
|
|
49325
|
+
initialBreakpoint: 0.75,
|
|
49326
|
+
showHandle: true,
|
|
49327
|
+
},
|
|
49328
|
+
backdropDismiss: true,
|
|
49329
|
+
});
|
|
49330
|
+
}
|
|
49331
|
+
async onTransfer(newOwnerId) {
|
|
49332
|
+
if (!newOwnerId || this.transferring())
|
|
49333
|
+
return;
|
|
49334
|
+
const result = await this.confirmDialog.confirmDestructive({
|
|
49335
|
+
title: this.tt('transferTitle'),
|
|
49336
|
+
message: this.tt('transferConfirm'),
|
|
49337
|
+
confirmButton: { text: this.tt('transferCta') },
|
|
49338
|
+
cancelButton: { text: this.tt('cancelCta') },
|
|
49339
|
+
});
|
|
49340
|
+
if (!result.confirmed)
|
|
49341
|
+
return;
|
|
49342
|
+
const orgId = this.activeOrgId();
|
|
49343
|
+
if (!orgId)
|
|
49344
|
+
return;
|
|
49345
|
+
this.transferring.set(true);
|
|
49346
|
+
try {
|
|
49347
|
+
await firstValueFrom(this.orgService.transferOwnership(orgId, newOwnerId));
|
|
49348
|
+
this.toast.show({
|
|
49349
|
+
message: this.tt('transferSuccess'),
|
|
49350
|
+
color: 'dark',
|
|
49351
|
+
duration: 3500,
|
|
49352
|
+
});
|
|
49353
|
+
this.loadOrg();
|
|
49354
|
+
this.loadMembers();
|
|
49355
|
+
this.resolvedConfig().onOwnershipTransferred?.();
|
|
49356
|
+
}
|
|
49357
|
+
catch (err) {
|
|
49358
|
+
this.errors.handle(err, {
|
|
49359
|
+
context: 'organization.transfer',
|
|
49360
|
+
fallbackKey: 'transferError',
|
|
49361
|
+
i18nNamespace: this.ns,
|
|
49362
|
+
});
|
|
49363
|
+
}
|
|
49364
|
+
finally {
|
|
49365
|
+
this.transferring.set(false);
|
|
49366
|
+
}
|
|
49367
|
+
}
|
|
49368
|
+
loadOrg() {
|
|
49369
|
+
const orgId = this.activeOrgId();
|
|
49370
|
+
if (!orgId)
|
|
49371
|
+
return Promise.resolve();
|
|
49372
|
+
this.loading.set(true);
|
|
49373
|
+
this.orgLoadError.set(null);
|
|
49374
|
+
return new Promise(resolve => {
|
|
49375
|
+
this.orgService.getOrg(orgId).subscribe({
|
|
49376
|
+
next: org => {
|
|
49377
|
+
this.org.set(org);
|
|
49378
|
+
this.loading.set(false);
|
|
49379
|
+
resolve();
|
|
49380
|
+
},
|
|
49381
|
+
error: err => {
|
|
49382
|
+
this.orgLoadError.set(err);
|
|
49383
|
+
this.loading.set(false);
|
|
49384
|
+
resolve();
|
|
49385
|
+
},
|
|
49386
|
+
});
|
|
49387
|
+
});
|
|
49388
|
+
}
|
|
49389
|
+
loadMembers() {
|
|
49390
|
+
const orgId = this.activeOrgId();
|
|
49391
|
+
if (!orgId)
|
|
49392
|
+
return Promise.resolve();
|
|
49393
|
+
this.membersLoading.set(true);
|
|
49394
|
+
this.membersNextToken.set(null);
|
|
49395
|
+
this.showAllMembers.set(false);
|
|
49396
|
+
return new Promise(resolve => {
|
|
49397
|
+
this.orgService.getOrgMembers(orgId, { limit: 20 }).subscribe({
|
|
49398
|
+
next: async (res) => {
|
|
49399
|
+
const cards = await this.enrichWithProfiles(res.members);
|
|
49400
|
+
this.members.set(cards);
|
|
49401
|
+
this.membersNextToken.set(res.nextToken ?? null);
|
|
49402
|
+
this.membersLoading.set(false);
|
|
49403
|
+
resolve();
|
|
49404
|
+
},
|
|
49405
|
+
error: err => {
|
|
49406
|
+
this.membersLoadError.set(err);
|
|
49407
|
+
this.membersLoading.set(false);
|
|
49408
|
+
resolve();
|
|
49409
|
+
},
|
|
49410
|
+
});
|
|
49411
|
+
});
|
|
49412
|
+
}
|
|
49413
|
+
loadMoreMembers() {
|
|
49414
|
+
const orgId = this.activeOrgId();
|
|
49415
|
+
const token = this.membersNextToken();
|
|
49416
|
+
if (!orgId || !token || this.loadingMoreMembers())
|
|
49417
|
+
return;
|
|
49418
|
+
this.loadingMoreMembers.set(true);
|
|
49419
|
+
this.orgService.getOrgMembers(orgId, { limit: 20, nextToken: token }).subscribe({
|
|
49420
|
+
next: async (res) => {
|
|
49421
|
+
const cards = await this.enrichWithProfiles(res.members);
|
|
49422
|
+
this.members.update(prev => [...prev, ...cards]);
|
|
49423
|
+
this.membersNextToken.set(res.nextToken ?? null);
|
|
49424
|
+
this.loadingMoreMembers.set(false);
|
|
49425
|
+
},
|
|
49426
|
+
error: err => {
|
|
49427
|
+
this.errors.handle(err, {
|
|
49428
|
+
context: 'organization.loadMoreMembers',
|
|
49429
|
+
fallbackKey: 'membersError',
|
|
49430
|
+
i18nNamespace: this.ns,
|
|
49431
|
+
});
|
|
49432
|
+
this.loadingMoreMembers.set(false);
|
|
49433
|
+
},
|
|
49434
|
+
});
|
|
49435
|
+
}
|
|
49436
|
+
async enrichWithProfiles(members) {
|
|
49437
|
+
const results = await Promise.allSettled(members.map(async (m) => {
|
|
49438
|
+
try {
|
|
49439
|
+
const snap = await getDoc(doc(this.firestore, `profiles/${m.userId}`));
|
|
49440
|
+
const p = snap.exists() ? snap.data() : {};
|
|
49441
|
+
return {
|
|
49442
|
+
...m,
|
|
49443
|
+
handle: p['handle'],
|
|
49444
|
+
avatar: (p['avatarUrl'] ?? p['avatar']),
|
|
49445
|
+
name: p['name'] || m.name,
|
|
49446
|
+
};
|
|
49447
|
+
}
|
|
49448
|
+
catch {
|
|
49449
|
+
return m;
|
|
49450
|
+
}
|
|
49451
|
+
}));
|
|
49452
|
+
return results.map(r => (r.status === 'fulfilled' ? r.value : {}));
|
|
49453
|
+
}
|
|
49454
|
+
loadRoles() {
|
|
49455
|
+
const orgId = this.activeOrgId();
|
|
49456
|
+
if (!orgId)
|
|
49457
|
+
return;
|
|
49458
|
+
this.orgService.listOrgRoles(orgId).subscribe({
|
|
49459
|
+
next: roles => {
|
|
49460
|
+
if (roles.length)
|
|
49461
|
+
this.availableRoles.set(roles);
|
|
49462
|
+
},
|
|
49463
|
+
});
|
|
49464
|
+
}
|
|
49465
|
+
roleLabel(roleIdOrName) {
|
|
49466
|
+
// Try to find by id first (UUID), then by name
|
|
49467
|
+
const found = this.availableRoles().find(r => r.id === roleIdOrName || r.name === roleIdOrName);
|
|
49468
|
+
const name = found?.name ?? roleIdOrName;
|
|
49469
|
+
const keyMap = {
|
|
49470
|
+
viewer: 'roleViewer',
|
|
49471
|
+
editor: 'roleEditor',
|
|
49472
|
+
admin: 'roleAdmin',
|
|
49473
|
+
super_admin: 'roleSuperAdmin',
|
|
49474
|
+
};
|
|
49475
|
+
const key = keyMap[name] ?? name;
|
|
49476
|
+
const label = this.tt(key);
|
|
49477
|
+
return label && !label.startsWith('[') ? label : name;
|
|
49478
|
+
}
|
|
49479
|
+
permissionLabel(perm) {
|
|
49480
|
+
if (perm === '*:*')
|
|
49481
|
+
return this.tt('permAll');
|
|
49482
|
+
const [resource, action] = perm.split(':');
|
|
49483
|
+
const resKey = {
|
|
49484
|
+
users: 'permUsers',
|
|
49485
|
+
documents: 'permDocuments',
|
|
49486
|
+
roles: 'permRoles',
|
|
49487
|
+
media: 'permMedia',
|
|
49488
|
+
templates: 'permTemplates',
|
|
49489
|
+
'*': 'permAll',
|
|
49490
|
+
};
|
|
49491
|
+
const actKey = {
|
|
49492
|
+
read: 'permRead',
|
|
49493
|
+
write: 'permWrite',
|
|
49494
|
+
create: 'permCreate',
|
|
49495
|
+
delete: 'permDelete',
|
|
49496
|
+
manage: 'permManage',
|
|
49497
|
+
'*': 'permAll',
|
|
49498
|
+
};
|
|
49499
|
+
const res = this.tt(resKey[resource] ?? resource);
|
|
49500
|
+
const act = this.tt(actKey[action] ?? action);
|
|
49501
|
+
return `${res}: ${act}`;
|
|
49502
|
+
}
|
|
49503
|
+
onViewMember(member) {
|
|
49504
|
+
void this.modalService.open({
|
|
49505
|
+
component: MemberDetailModalComponent,
|
|
49506
|
+
componentProps: {
|
|
49507
|
+
member,
|
|
49508
|
+
orgId: this.activeOrgId(),
|
|
49509
|
+
availableRoles: this.availableRoles(),
|
|
49510
|
+
canManage: this.canManageMembers() && member.userId !== this.userId(),
|
|
49511
|
+
isSelf: member.userId === this.userId(),
|
|
49512
|
+
onChanged: () => this.loadMembers(),
|
|
49513
|
+
},
|
|
49514
|
+
breakpoints: {
|
|
49515
|
+
breakpoints: [0, 0.7],
|
|
49516
|
+
initialBreakpoint: 0.7,
|
|
49517
|
+
showHandle: true,
|
|
49518
|
+
},
|
|
49519
|
+
backdropDismiss: true,
|
|
49520
|
+
});
|
|
49521
|
+
}
|
|
49522
|
+
tt(key) {
|
|
49523
|
+
return this.i18n.t(key, this.ns);
|
|
49524
|
+
}
|
|
49525
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrganizationViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
49526
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: OrganizationViewComponent, isStandalone: true, selector: "val-organization-view", inputs: { config: "config" }, ngImport: i0, template: `
|
|
49527
|
+
<div class="page">
|
|
49528
|
+
<header class="page-header">
|
|
49529
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
|
|
49530
|
+
<val-title
|
|
49531
|
+
[props]="{
|
|
49532
|
+
size: 'large',
|
|
49533
|
+
color: 'dark',
|
|
49534
|
+
bold: false,
|
|
49535
|
+
content: pageDescription(),
|
|
49536
|
+
}"
|
|
49537
|
+
/>
|
|
49538
|
+
</header>
|
|
49539
|
+
|
|
49540
|
+
@if (resolvedConfig().showInfo) {
|
|
49541
|
+
<!-- Org Info -->
|
|
49542
|
+
<section class="settings-section">
|
|
49543
|
+
<div class="section-header-row">
|
|
49544
|
+
<val-title
|
|
49545
|
+
[props]="{
|
|
49546
|
+
size: 'medium',
|
|
49547
|
+
color: 'dark',
|
|
49548
|
+
bold: true,
|
|
49549
|
+
content: tt('infoTitle'),
|
|
49550
|
+
}"
|
|
49551
|
+
/>
|
|
49552
|
+
@if (canEditOrg()) {
|
|
49553
|
+
<val-button [props]="editButtonProps()" (click)="onEdit()" />
|
|
49554
|
+
}
|
|
49555
|
+
</div>
|
|
49556
|
+
|
|
49557
|
+
<div class="section-body">
|
|
49558
|
+
@if (loading()) {
|
|
49559
|
+
<val-skeleton-layout [props]="{ preset: 'detail', rows: 4 }" />
|
|
49560
|
+
} @else if (orgErrorState()) {
|
|
49561
|
+
<val-empty-state [props]="orgErrorState()!" />
|
|
49562
|
+
} @else {
|
|
49563
|
+
@if (org(); as o) {
|
|
49564
|
+
<div class="org-info-card">
|
|
49565
|
+
<div class="org-info-field org-info-field--full">
|
|
49566
|
+
<span class="org-info-label">{{ tt('infoName') }}</span>
|
|
49567
|
+
<span class="org-info-value">{{ o.name }}</span>
|
|
49568
|
+
</div>
|
|
49569
|
+
<div class="org-info-field org-info-field--full">
|
|
49570
|
+
<span class="org-info-label">{{ tt('infoDescription') }}</span>
|
|
49571
|
+
<span class="org-info-value org-info-value--muted">{{ o.description || '—' }}</span>
|
|
49572
|
+
</div>
|
|
49573
|
+
<div class="org-info-row">
|
|
49574
|
+
<div class="org-info-field">
|
|
49575
|
+
<span class="org-info-label">{{ tt('infoPlan') }}</span>
|
|
49576
|
+
<span class="plan-badge plan-badge--{{ o.plan }}">{{ planLabel() }}</span>
|
|
49577
|
+
</div>
|
|
49578
|
+
<div class="org-info-field">
|
|
49579
|
+
<span class="org-info-label">{{ tt('infoType') }}</span>
|
|
49580
|
+
<span class="org-info-value">{{ o.type }}</span>
|
|
49581
|
+
</div>
|
|
49582
|
+
</div>
|
|
49583
|
+
</div>
|
|
49584
|
+
}
|
|
49585
|
+
}
|
|
49586
|
+
</div>
|
|
49587
|
+
</section>
|
|
49588
|
+
}
|
|
49589
|
+
|
|
49590
|
+
@if (resolvedConfig().showMembers) {
|
|
49591
|
+
<!-- Members -->
|
|
49592
|
+
<section class="settings-section">
|
|
49593
|
+
<val-title
|
|
49594
|
+
[props]="{
|
|
49595
|
+
size: 'medium',
|
|
49596
|
+
color: 'dark',
|
|
49597
|
+
bold: true,
|
|
49598
|
+
content: tt('membersTitle'),
|
|
49599
|
+
}"
|
|
49600
|
+
/>
|
|
49601
|
+
<div class="section-body">
|
|
49602
|
+
@if (membersLoading()) {
|
|
49603
|
+
<val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
|
|
49604
|
+
} @else if (members().length === 0) {
|
|
49605
|
+
<val-text
|
|
49606
|
+
[props]="{
|
|
49607
|
+
content: tt('membersEmpty'),
|
|
49608
|
+
size: 'medium',
|
|
49609
|
+
color: 'dark',
|
|
49610
|
+
bold: false,
|
|
49611
|
+
}"
|
|
49612
|
+
/>
|
|
49613
|
+
} @else {
|
|
49614
|
+
<div class="members-list">
|
|
49615
|
+
@for (m of visibleMembers(); track m.userId) {
|
|
49616
|
+
<val-member-card
|
|
49617
|
+
[attr.data-testid]="'org-member-' + m.userId"
|
|
49618
|
+
[props]="{
|
|
49619
|
+
id: m.userId,
|
|
49620
|
+
name: m.name,
|
|
49621
|
+
handle: m.handle,
|
|
49622
|
+
email: m.email,
|
|
49623
|
+
avatarUrl: m.avatar,
|
|
49624
|
+
roleLabel: m.roles[0] ? roleLabel(m.roles[0]) : '',
|
|
49625
|
+
actionAriaLabel: tt('viewMemberCta'),
|
|
49626
|
+
}"
|
|
49627
|
+
(onAction)="onViewMember(m)"
|
|
49628
|
+
/>
|
|
49629
|
+
}
|
|
49630
|
+
</div>
|
|
49631
|
+
@if (!showAllMembers() && members().length > 3) {
|
|
49632
|
+
<button class="members-show-more" (click)="showAllMembers.set(true)">
|
|
49633
|
+
{{ tt('membersShowMore') }} ({{ members().length - 3 }})
|
|
49634
|
+
</button>
|
|
49635
|
+
}
|
|
49636
|
+
@if (showAllMembers() && membersNextToken()) {
|
|
49637
|
+
<button class="members-show-more" [disabled]="loadingMoreMembers()" (click)="loadMoreMembers()">
|
|
49638
|
+
{{ loadingMoreMembers() ? '…' : tt('membersShowMore') }}
|
|
49639
|
+
</button>
|
|
49640
|
+
}
|
|
49641
|
+
}
|
|
49642
|
+
</div>
|
|
49643
|
+
</section>
|
|
49644
|
+
}
|
|
49645
|
+
|
|
49646
|
+
@if (resolvedConfig().showRoles) {
|
|
49647
|
+
<!-- Roles y permisos: seccion explicativa, detalle en la ruta de permisos -->
|
|
49648
|
+
<section class="settings-section">
|
|
49649
|
+
<val-title
|
|
49650
|
+
[props]="{
|
|
49651
|
+
size: 'medium',
|
|
49652
|
+
color: 'dark',
|
|
49653
|
+
bold: true,
|
|
49654
|
+
content: tt('rolesSectionTitle'),
|
|
49655
|
+
}"
|
|
49656
|
+
/>
|
|
49657
|
+
<div class="section-body">
|
|
49658
|
+
<val-text
|
|
49659
|
+
[props]="{
|
|
49660
|
+
content: tt('rolesHint'),
|
|
49661
|
+
size: 'medium',
|
|
49662
|
+
color: 'dark',
|
|
49663
|
+
bold: false,
|
|
49664
|
+
}"
|
|
49665
|
+
/>
|
|
49666
|
+
<div class="row-actions">
|
|
49667
|
+
<val-button [props]="viewPermissionsButtonProps()" (click)="onViewPermissions()" />
|
|
49668
|
+
</div>
|
|
49669
|
+
</div>
|
|
49670
|
+
</section>
|
|
49671
|
+
}
|
|
49672
|
+
|
|
49673
|
+
@if (resolvedConfig().showInvite) {
|
|
49674
|
+
<!-- Invite: solo admin+ (gating RBAC interno) -->
|
|
49675
|
+
<section *valHasPermission="'users:*'" class="settings-section" data-testid="org-invite-section">
|
|
49676
|
+
<div class="invite-cta">
|
|
49677
|
+
<div class="invite-cta__text">
|
|
49678
|
+
<val-title
|
|
49679
|
+
[props]="{
|
|
49680
|
+
size: 'medium',
|
|
49681
|
+
color: 'dark',
|
|
49682
|
+
bold: true,
|
|
49683
|
+
content: tt('inviteCtaTitle'),
|
|
49684
|
+
}"
|
|
49685
|
+
/>
|
|
49686
|
+
<val-text
|
|
49687
|
+
[props]="{
|
|
49688
|
+
size: 'medium',
|
|
49689
|
+
color: 'dark',
|
|
49690
|
+
bold: false,
|
|
49691
|
+
content: tt('inviteCtaHint'),
|
|
49692
|
+
}"
|
|
49693
|
+
/>
|
|
49694
|
+
</div>
|
|
49695
|
+
<val-button [props]="openInviteButtonProps()" (click)="onOpenInvite()" />
|
|
49696
|
+
</div>
|
|
49697
|
+
</section>
|
|
49698
|
+
}
|
|
49699
|
+
|
|
49700
|
+
@if (resolvedConfig().showTransferOwnership && isOwner()) {
|
|
49701
|
+
<!-- Transfer ownership: solo owner (gating RBAC interno) -->
|
|
49702
|
+
<section class="settings-section" data-testid="org-transfer-section">
|
|
49703
|
+
<val-title
|
|
49704
|
+
[props]="{
|
|
49705
|
+
size: 'medium',
|
|
49706
|
+
color: 'dark',
|
|
49707
|
+
bold: true,
|
|
49708
|
+
content: tt('transferTitle'),
|
|
49709
|
+
}"
|
|
49710
|
+
/>
|
|
49711
|
+
<div class="section-body">
|
|
49712
|
+
<val-text
|
|
49713
|
+
[props]="{
|
|
49714
|
+
content: tt('transferHint'),
|
|
49715
|
+
size: 'medium',
|
|
49716
|
+
color: 'dark',
|
|
49717
|
+
bold: false,
|
|
49718
|
+
}"
|
|
49719
|
+
/>
|
|
49720
|
+
@if (transferableMembers().length > 0) {
|
|
49721
|
+
<div class="row-actions">
|
|
49722
|
+
<val-button [props]="transferButtonProps()" (click)="onOpenTransfer()" />
|
|
49723
|
+
</div>
|
|
49724
|
+
} @else {
|
|
49725
|
+
<val-text
|
|
49726
|
+
[props]="{
|
|
49727
|
+
content: tt('membersEmpty'),
|
|
49728
|
+
size: 'medium',
|
|
49729
|
+
color: 'dark',
|
|
49730
|
+
bold: false,
|
|
49731
|
+
}"
|
|
49732
|
+
/>
|
|
49733
|
+
}
|
|
49734
|
+
</div>
|
|
49735
|
+
</section>
|
|
49736
|
+
}
|
|
49737
|
+
|
|
49738
|
+
@if (resolvedConfig().showLeave) {
|
|
49739
|
+
<!-- Leave org -->
|
|
49740
|
+
<section class="settings-section">
|
|
49741
|
+
<val-title
|
|
49742
|
+
[props]="{
|
|
49743
|
+
size: 'medium',
|
|
49744
|
+
color: 'dark',
|
|
49745
|
+
bold: true,
|
|
49746
|
+
content: tt('leaveTitle'),
|
|
49747
|
+
}"
|
|
49748
|
+
/>
|
|
49749
|
+
<div class="section-body">
|
|
49750
|
+
<val-text
|
|
49751
|
+
[props]="{
|
|
49752
|
+
content: isOwner() ? tt('leaveOwnerHint') : tt('leaveHint'),
|
|
49753
|
+
size: 'medium',
|
|
49754
|
+
color: 'dark',
|
|
49755
|
+
bold: false,
|
|
49756
|
+
}"
|
|
49757
|
+
/>
|
|
49758
|
+
<div class="row-actions">
|
|
49759
|
+
<val-button [props]="leaveButtonProps()" (click)="onLeave()" />
|
|
49760
|
+
</div>
|
|
49761
|
+
</div>
|
|
49762
|
+
</section>
|
|
49763
|
+
}
|
|
49764
|
+
|
|
49765
|
+
@if (isDebug) {
|
|
49766
|
+
<section class="settings-section rbac-debug">
|
|
49767
|
+
<div class="section-header-row">
|
|
49768
|
+
<val-title
|
|
49769
|
+
[props]="{
|
|
49770
|
+
content: 'RBAC (live)',
|
|
49771
|
+
size: 'small',
|
|
49772
|
+
color: 'medium',
|
|
49773
|
+
bold: false,
|
|
49774
|
+
}"
|
|
49775
|
+
/>
|
|
49776
|
+
</div>
|
|
49777
|
+
<div class="rbac-debug__body">
|
|
49778
|
+
<div class="rbac-debug__row">
|
|
49779
|
+
<span class="rbac-debug__label">Roles</span>
|
|
49780
|
+
<span class="rbac-debug__value">{{ auth.roles().join(', ') || '—' }}</span>
|
|
49781
|
+
</div>
|
|
49782
|
+
<div class="rbac-debug__row">
|
|
49783
|
+
<span class="rbac-debug__label">Permisos</span>
|
|
49784
|
+
<span class="rbac-debug__value rbac-debug__value--perms">{{ auth.permissions().join(', ') || '—' }}</span>
|
|
49785
|
+
</div>
|
|
49786
|
+
</div>
|
|
49787
|
+
</section>
|
|
49788
|
+
}
|
|
49789
|
+
</div>
|
|
49790
|
+
`, 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"] }] }); }
|
|
49791
|
+
}
|
|
49792
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrganizationViewComponent, decorators: [{
|
|
49793
|
+
type: Component,
|
|
49794
|
+
args: [{ selector: 'val-organization-view', standalone: true, imports: [
|
|
49795
|
+
DisplayComponent,
|
|
49796
|
+
EmptyStateComponent,
|
|
49797
|
+
HasPermissionDirective,
|
|
49798
|
+
SkeletonLayoutComponent,
|
|
49799
|
+
TitleComponent,
|
|
49800
|
+
TextComponent,
|
|
49801
|
+
ButtonComponent,
|
|
49802
|
+
MemberCardComponent,
|
|
49803
|
+
TransferOwnershipModalComponent,
|
|
49804
|
+
], template: `
|
|
49805
|
+
<div class="page">
|
|
49806
|
+
<header class="page-header">
|
|
49807
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
|
|
49808
|
+
<val-title
|
|
49809
|
+
[props]="{
|
|
49810
|
+
size: 'large',
|
|
49811
|
+
color: 'dark',
|
|
49812
|
+
bold: false,
|
|
49813
|
+
content: pageDescription(),
|
|
49814
|
+
}"
|
|
49815
|
+
/>
|
|
49816
|
+
</header>
|
|
49817
|
+
|
|
49818
|
+
@if (resolvedConfig().showInfo) {
|
|
49819
|
+
<!-- Org Info -->
|
|
49820
|
+
<section class="settings-section">
|
|
49821
|
+
<div class="section-header-row">
|
|
49822
|
+
<val-title
|
|
49823
|
+
[props]="{
|
|
49824
|
+
size: 'medium',
|
|
49825
|
+
color: 'dark',
|
|
49826
|
+
bold: true,
|
|
49827
|
+
content: tt('infoTitle'),
|
|
49828
|
+
}"
|
|
49829
|
+
/>
|
|
49830
|
+
@if (canEditOrg()) {
|
|
49831
|
+
<val-button [props]="editButtonProps()" (click)="onEdit()" />
|
|
49832
|
+
}
|
|
49833
|
+
</div>
|
|
49834
|
+
|
|
49835
|
+
<div class="section-body">
|
|
49836
|
+
@if (loading()) {
|
|
49837
|
+
<val-skeleton-layout [props]="{ preset: 'detail', rows: 4 }" />
|
|
49838
|
+
} @else if (orgErrorState()) {
|
|
49839
|
+
<val-empty-state [props]="orgErrorState()!" />
|
|
49840
|
+
} @else {
|
|
49841
|
+
@if (org(); as o) {
|
|
49842
|
+
<div class="org-info-card">
|
|
49843
|
+
<div class="org-info-field org-info-field--full">
|
|
49844
|
+
<span class="org-info-label">{{ tt('infoName') }}</span>
|
|
49845
|
+
<span class="org-info-value">{{ o.name }}</span>
|
|
49846
|
+
</div>
|
|
49847
|
+
<div class="org-info-field org-info-field--full">
|
|
49848
|
+
<span class="org-info-label">{{ tt('infoDescription') }}</span>
|
|
49849
|
+
<span class="org-info-value org-info-value--muted">{{ o.description || '—' }}</span>
|
|
49850
|
+
</div>
|
|
49851
|
+
<div class="org-info-row">
|
|
49852
|
+
<div class="org-info-field">
|
|
49853
|
+
<span class="org-info-label">{{ tt('infoPlan') }}</span>
|
|
49854
|
+
<span class="plan-badge plan-badge--{{ o.plan }}">{{ planLabel() }}</span>
|
|
49855
|
+
</div>
|
|
49856
|
+
<div class="org-info-field">
|
|
49857
|
+
<span class="org-info-label">{{ tt('infoType') }}</span>
|
|
49858
|
+
<span class="org-info-value">{{ o.type }}</span>
|
|
49859
|
+
</div>
|
|
49860
|
+
</div>
|
|
49861
|
+
</div>
|
|
49862
|
+
}
|
|
49863
|
+
}
|
|
49864
|
+
</div>
|
|
49865
|
+
</section>
|
|
49866
|
+
}
|
|
49867
|
+
|
|
49868
|
+
@if (resolvedConfig().showMembers) {
|
|
49869
|
+
<!-- Members -->
|
|
49870
|
+
<section class="settings-section">
|
|
49871
|
+
<val-title
|
|
49872
|
+
[props]="{
|
|
49873
|
+
size: 'medium',
|
|
49874
|
+
color: 'dark',
|
|
49875
|
+
bold: true,
|
|
49876
|
+
content: tt('membersTitle'),
|
|
49877
|
+
}"
|
|
49878
|
+
/>
|
|
49879
|
+
<div class="section-body">
|
|
49880
|
+
@if (membersLoading()) {
|
|
49881
|
+
<val-skeleton-layout [props]="{ preset: 'list', rows: 3 }" />
|
|
49882
|
+
} @else if (members().length === 0) {
|
|
49883
|
+
<val-text
|
|
49884
|
+
[props]="{
|
|
49885
|
+
content: tt('membersEmpty'),
|
|
49886
|
+
size: 'medium',
|
|
49887
|
+
color: 'dark',
|
|
49888
|
+
bold: false,
|
|
49889
|
+
}"
|
|
49890
|
+
/>
|
|
49891
|
+
} @else {
|
|
49892
|
+
<div class="members-list">
|
|
49893
|
+
@for (m of visibleMembers(); track m.userId) {
|
|
49894
|
+
<val-member-card
|
|
49895
|
+
[attr.data-testid]="'org-member-' + m.userId"
|
|
49896
|
+
[props]="{
|
|
49897
|
+
id: m.userId,
|
|
49898
|
+
name: m.name,
|
|
49899
|
+
handle: m.handle,
|
|
49900
|
+
email: m.email,
|
|
49901
|
+
avatarUrl: m.avatar,
|
|
49902
|
+
roleLabel: m.roles[0] ? roleLabel(m.roles[0]) : '',
|
|
49903
|
+
actionAriaLabel: tt('viewMemberCta'),
|
|
49904
|
+
}"
|
|
49905
|
+
(onAction)="onViewMember(m)"
|
|
49906
|
+
/>
|
|
49907
|
+
}
|
|
49908
|
+
</div>
|
|
49909
|
+
@if (!showAllMembers() && members().length > 3) {
|
|
49910
|
+
<button class="members-show-more" (click)="showAllMembers.set(true)">
|
|
49911
|
+
{{ tt('membersShowMore') }} ({{ members().length - 3 }})
|
|
49912
|
+
</button>
|
|
49913
|
+
}
|
|
49914
|
+
@if (showAllMembers() && membersNextToken()) {
|
|
49915
|
+
<button class="members-show-more" [disabled]="loadingMoreMembers()" (click)="loadMoreMembers()">
|
|
49916
|
+
{{ loadingMoreMembers() ? '…' : tt('membersShowMore') }}
|
|
49917
|
+
</button>
|
|
49918
|
+
}
|
|
49919
|
+
}
|
|
49920
|
+
</div>
|
|
49921
|
+
</section>
|
|
49922
|
+
}
|
|
49923
|
+
|
|
49924
|
+
@if (resolvedConfig().showRoles) {
|
|
49925
|
+
<!-- Roles y permisos: seccion explicativa, detalle en la ruta de permisos -->
|
|
49926
|
+
<section class="settings-section">
|
|
49927
|
+
<val-title
|
|
49928
|
+
[props]="{
|
|
49929
|
+
size: 'medium',
|
|
49930
|
+
color: 'dark',
|
|
49931
|
+
bold: true,
|
|
49932
|
+
content: tt('rolesSectionTitle'),
|
|
49933
|
+
}"
|
|
49934
|
+
/>
|
|
49935
|
+
<div class="section-body">
|
|
49936
|
+
<val-text
|
|
49937
|
+
[props]="{
|
|
49938
|
+
content: tt('rolesHint'),
|
|
49939
|
+
size: 'medium',
|
|
49940
|
+
color: 'dark',
|
|
49941
|
+
bold: false,
|
|
49942
|
+
}"
|
|
49943
|
+
/>
|
|
49944
|
+
<div class="row-actions">
|
|
49945
|
+
<val-button [props]="viewPermissionsButtonProps()" (click)="onViewPermissions()" />
|
|
49946
|
+
</div>
|
|
49947
|
+
</div>
|
|
49948
|
+
</section>
|
|
49949
|
+
}
|
|
49950
|
+
|
|
49951
|
+
@if (resolvedConfig().showInvite) {
|
|
49952
|
+
<!-- Invite: solo admin+ (gating RBAC interno) -->
|
|
49953
|
+
<section *valHasPermission="'users:*'" class="settings-section" data-testid="org-invite-section">
|
|
49954
|
+
<div class="invite-cta">
|
|
49955
|
+
<div class="invite-cta__text">
|
|
49956
|
+
<val-title
|
|
49957
|
+
[props]="{
|
|
49958
|
+
size: 'medium',
|
|
49959
|
+
color: 'dark',
|
|
49960
|
+
bold: true,
|
|
49961
|
+
content: tt('inviteCtaTitle'),
|
|
49962
|
+
}"
|
|
49963
|
+
/>
|
|
49964
|
+
<val-text
|
|
49965
|
+
[props]="{
|
|
49966
|
+
size: 'medium',
|
|
49967
|
+
color: 'dark',
|
|
49968
|
+
bold: false,
|
|
49969
|
+
content: tt('inviteCtaHint'),
|
|
49970
|
+
}"
|
|
49971
|
+
/>
|
|
49972
|
+
</div>
|
|
49973
|
+
<val-button [props]="openInviteButtonProps()" (click)="onOpenInvite()" />
|
|
49974
|
+
</div>
|
|
49975
|
+
</section>
|
|
49976
|
+
}
|
|
49977
|
+
|
|
49978
|
+
@if (resolvedConfig().showTransferOwnership && isOwner()) {
|
|
49979
|
+
<!-- Transfer ownership: solo owner (gating RBAC interno) -->
|
|
49980
|
+
<section class="settings-section" data-testid="org-transfer-section">
|
|
49981
|
+
<val-title
|
|
49982
|
+
[props]="{
|
|
49983
|
+
size: 'medium',
|
|
49984
|
+
color: 'dark',
|
|
49985
|
+
bold: true,
|
|
49986
|
+
content: tt('transferTitle'),
|
|
49987
|
+
}"
|
|
49988
|
+
/>
|
|
49989
|
+
<div class="section-body">
|
|
49990
|
+
<val-text
|
|
49991
|
+
[props]="{
|
|
49992
|
+
content: tt('transferHint'),
|
|
49993
|
+
size: 'medium',
|
|
49994
|
+
color: 'dark',
|
|
49995
|
+
bold: false,
|
|
49996
|
+
}"
|
|
49997
|
+
/>
|
|
49998
|
+
@if (transferableMembers().length > 0) {
|
|
49999
|
+
<div class="row-actions">
|
|
50000
|
+
<val-button [props]="transferButtonProps()" (click)="onOpenTransfer()" />
|
|
50001
|
+
</div>
|
|
50002
|
+
} @else {
|
|
50003
|
+
<val-text
|
|
50004
|
+
[props]="{
|
|
50005
|
+
content: tt('membersEmpty'),
|
|
50006
|
+
size: 'medium',
|
|
50007
|
+
color: 'dark',
|
|
50008
|
+
bold: false,
|
|
50009
|
+
}"
|
|
50010
|
+
/>
|
|
50011
|
+
}
|
|
50012
|
+
</div>
|
|
50013
|
+
</section>
|
|
50014
|
+
}
|
|
50015
|
+
|
|
50016
|
+
@if (resolvedConfig().showLeave) {
|
|
50017
|
+
<!-- Leave org -->
|
|
50018
|
+
<section class="settings-section">
|
|
50019
|
+
<val-title
|
|
50020
|
+
[props]="{
|
|
50021
|
+
size: 'medium',
|
|
50022
|
+
color: 'dark',
|
|
50023
|
+
bold: true,
|
|
50024
|
+
content: tt('leaveTitle'),
|
|
50025
|
+
}"
|
|
50026
|
+
/>
|
|
50027
|
+
<div class="section-body">
|
|
50028
|
+
<val-text
|
|
50029
|
+
[props]="{
|
|
50030
|
+
content: isOwner() ? tt('leaveOwnerHint') : tt('leaveHint'),
|
|
50031
|
+
size: 'medium',
|
|
50032
|
+
color: 'dark',
|
|
50033
|
+
bold: false,
|
|
50034
|
+
}"
|
|
50035
|
+
/>
|
|
50036
|
+
<div class="row-actions">
|
|
50037
|
+
<val-button [props]="leaveButtonProps()" (click)="onLeave()" />
|
|
50038
|
+
</div>
|
|
50039
|
+
</div>
|
|
50040
|
+
</section>
|
|
50041
|
+
}
|
|
50042
|
+
|
|
50043
|
+
@if (isDebug) {
|
|
50044
|
+
<section class="settings-section rbac-debug">
|
|
50045
|
+
<div class="section-header-row">
|
|
50046
|
+
<val-title
|
|
50047
|
+
[props]="{
|
|
50048
|
+
content: 'RBAC (live)',
|
|
50049
|
+
size: 'small',
|
|
50050
|
+
color: 'medium',
|
|
50051
|
+
bold: false,
|
|
50052
|
+
}"
|
|
50053
|
+
/>
|
|
50054
|
+
</div>
|
|
50055
|
+
<div class="rbac-debug__body">
|
|
50056
|
+
<div class="rbac-debug__row">
|
|
50057
|
+
<span class="rbac-debug__label">Roles</span>
|
|
50058
|
+
<span class="rbac-debug__value">{{ auth.roles().join(', ') || '—' }}</span>
|
|
50059
|
+
</div>
|
|
50060
|
+
<div class="rbac-debug__row">
|
|
50061
|
+
<span class="rbac-debug__label">Permisos</span>
|
|
50062
|
+
<span class="rbac-debug__value rbac-debug__value--perms">{{ auth.permissions().join(', ') || '—' }}</span>
|
|
50063
|
+
</div>
|
|
50064
|
+
</div>
|
|
50065
|
+
</section>
|
|
50066
|
+
}
|
|
50067
|
+
</div>
|
|
50068
|
+
`, 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"] }]
|
|
50069
|
+
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
50070
|
+
type: Input
|
|
50071
|
+
}] } });
|
|
50072
|
+
|
|
50073
|
+
/**
|
|
50074
|
+
* Helper para montar la vista Gestión de organización (`val-organization-view`)
|
|
50075
|
+
* como ruta en una app del factory. El `config` se pasa por route `data`
|
|
50076
|
+
* (`organizationConfig`) y el componente lo lee como fallback de su
|
|
50077
|
+
* `@Input() config`.
|
|
50078
|
+
*
|
|
50079
|
+
* @example
|
|
50080
|
+
* ```ts
|
|
50081
|
+
* // settings.routes.ts
|
|
50082
|
+
* export const settingsRoutes: Routes = [
|
|
50083
|
+
* ...provideValtechOrganizationRoutes(), // → /app/settings/organization
|
|
50084
|
+
* { path: 'preferences', loadComponent: () => ... },
|
|
50085
|
+
* ];
|
|
50086
|
+
*
|
|
50087
|
+
* // con config acotada:
|
|
50088
|
+
* ...provideValtechOrganizationRoutes({ config: { showLeave: false } }),
|
|
50089
|
+
* ...provideValtechOrganizationRoutes({ config: { viewPermissionsRoute: '/app/roles' } }),
|
|
50090
|
+
* ```
|
|
50091
|
+
*/
|
|
50092
|
+
function provideValtechOrganizationRoutes(opts) {
|
|
50093
|
+
return [
|
|
50094
|
+
{
|
|
50095
|
+
path: opts?.path ?? 'organization',
|
|
50096
|
+
component: OrganizationViewComponent,
|
|
50097
|
+
data: { organizationConfig: opts?.config },
|
|
50098
|
+
},
|
|
50099
|
+
];
|
|
50100
|
+
}
|
|
50101
|
+
|
|
47377
50102
|
/** Built-in label sets for the three platform locales. */
|
|
47378
50103
|
const CALLOUT_LABELS = {
|
|
47379
50104
|
es: {
|
|
@@ -57698,5 +60423,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
57698
60423
|
* Generated bundle index. Do not edit.
|
|
57699
60424
|
*/
|
|
57700
60425
|
|
|
57701
|
-
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, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
60426
|
+
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, NotificationsService, 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, 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, provideValtechOrganizationRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
57702
60427
|
//# sourceMappingURL=valtech-components.mjs.map
|