UNPKG

@tapsellorg/angular-material-library

Version:

Angular library for Tapsell

792 lines (778 loc) 62.9 kB
import * as i0 from '@angular/core'; import { InjectionToken, inject, Inject, Injectable, input, HostListener, Directive, signal, ChangeDetectionStrategy, ViewEncapsulation, Component, Pipe, NgModule, output } from '@angular/core'; import * as i1$1 from '@tapsellorg/angular-material-library/src/lib/common'; import { PghStorageFactory, withDestroy, UrlUtils, ObjectUtils } from '@tapsellorg/angular-material-library/src/lib/common'; import { Subject, interval, timer, BehaviorSubject, race, takeWhile, last, concatMap, of, catchError as catchError$1, throwError, take as take$1, switchMap as switchMap$1, EMPTY } from 'rxjs'; import { takeUntil, map, filter, switchMap, catchError, take, tap } from 'rxjs/operators'; import * as i1$2 from '@angular/common/http'; import { HttpParams, HttpClient, HttpHeaders } from '@angular/common/http'; import Cookies from 'js-cookie'; import * as i1 from '@angular/router'; import { RouterModule } from '@angular/router'; import * as i2 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i3 from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon'; import * as i4 from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button'; import * as i5 from '@tapsellorg/angular-material-library/src/lib/overlay'; import { PghOverlayModule } from '@tapsellorg/angular-material-library/src/lib/overlay'; import * as i6 from '@angular/cdk/overlay'; import * as i7 from '@tapsellorg/angular-material-library/src/lib/status-label'; import { PghStatusLabelModule } from '@tapsellorg/angular-material-library/src/lib/status-label'; import * as i8 from '@angular/material/core'; import { MatRippleModule } from '@angular/material/core'; import * as i10 from '@tapsellorg/angular-material-library/src/lib/indicator'; import { PghIndicatorModule } from '@tapsellorg/angular-material-library/src/lib/indicator'; import * as i11 from '@tapsellorg/angular-material-library/media'; import { PghMediaModule } from '@tapsellorg/angular-material-library/media'; import * as i12 from '@tapsellorg/angular-material-library/src/lib/translate'; import { TranslateModule } from '@tapsellorg/angular-material-library/src/lib/translate'; import * as i10$1 from '@tapsellorg/angular-material-library'; import { UrlUtils as UrlUtils$1, PghGravatarModule } from '@tapsellorg/angular-material-library'; import * as i5$1 from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu'; const PGH_REFRESH_TOKEN_REQUEST = (http, refreshToken, config) => { const refreshTokenUrl = `${config.ssoApiBaseUrl}/sso/flow/refresh_token`; const payload = new HttpParams().set('refreshToken', refreshToken); return http.post(refreshTokenUrl, payload); }; const PGH_GET_UUID_REQUEST = (http, authData, config) => { const payload = new HttpParams() .set('refresh_token', authData.refresh_token) .set('access_token', authData.access_token); const getUUidUrl = `${config.ssoApiBaseUrl}/sso/token2uuid`; return http.post(getUUidUrl, payload, { withCredentials: true, }); }; const PGH_GET_TOKEN_REQUEST = (http, uuid, config) => { const getUUidUrl = `${config.ssoApiBaseUrl}/sso/uuid2token/${uuid}`; return http.get(getUUidUrl); }; const PGH_TRUSTED_DOMAINS = [ 'https://web.tapsell.com', 'https://app.tapsell.com', 'https://admin-web.tapsell.com', 'https://admin-app.tapsell.com', 'https://gravity.pegah.tech', ]; const PGH_EXPIRE_TOKEN_AFTER_LOGOUT = (http, authData, config) => { const payload = new HttpParams() .set('refresh_token', authData.refresh_token) .set('access_token', authData.access_token); const getUUidUrl = `${config.ssoApiBaseUrl}/sso/logout`; return http.post(getUUidUrl, payload, { withCredentials: true, }); }; const PGH_SSO_CONFIG = new InjectionToken('sso-config', { providedIn: 'root', factory: () => ({}), }); /** * PGH_SSO_CONFIG is a partial version of PGH_SSO_ENRICHED_CONFIG so that it'll merge it with the default values */ const PGH_SSO_ENRICHED_CONFIG = new InjectionToken('sso-enriched-config', { providedIn: 'root', factory: () => { const httpClient = inject(HttpClient); const ssoConfig = inject(PGH_SSO_CONFIG); const result = { ssoPanelUrl: 'https://sso-panel.pegah.tech', ssoApiBaseUrl: 'https://sso-api.pegah.tech', restrictedUrls: ['https://unified-api.pegah.tech'], impersonateApiUrl: 'https://sso-api.pegah.tech/impersonate/login', sharedIframeUrl: 'https://auth-client.tapsell.ir', isInsideSsoPanel: false, changePasswordRoute: '/change-password', profileRoute: '/profile', trustedDomains: PGH_TRUSTED_DOMAINS, isDevMode: false, redirectToSsoAfterLogout: true, pghCookieStoreDomain: 'tapsell.ir', pghRedirectUrlParam: 'redirect', refreshTokenRequest: refreshToken => PGH_REFRESH_TOKEN_REQUEST(httpClient, refreshToken, result), getUUidRequest: authData => PGH_GET_UUID_REQUEST(httpClient, authData, result), getTokenRequest: uuid => PGH_GET_TOKEN_REQUEST(httpClient, uuid, result), expireTokenAfterLogout: authData => PGH_EXPIRE_TOKEN_AFTER_LOGOUT(httpClient, authData, result), accessTokenExpiredError: err => err === 'webApi.unAuthorized.ExpiredAccessToken', refreshTokenExpiredError: err => err === 'iam.unAuthorized.ExpiredAccessToken', ...ssoConfig, }; return result; }, }); // @dynamic class PghSsoUtils { static { this.AUTH_DATA_REQUIRED_KEYS = [ 'accessToken', 'refreshToken', ]; } static { this.AUTH_DATA_KEYS = ['accessToken', 'refreshToken']; } static { this.UUID_PARAM = 'uuid'; } static validateAuthData(data) { return (this.AUTH_DATA_REQUIRED_KEYS.reduce((res, key) => res && !!data[key], true) && data.accessToken?.split('.').length === 3); } static isTokenMessage(data) { return data.type === 'sso:auth'; } static isLogoutMessage(data) { return data.type === 'sso:logout'; } static decodeJwtToken(accessToken) { const base64Url = accessToken.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = decodeURIComponent(atob(base64) .split('') .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) .join('')); return JSON.parse(jsonPayload); } static parseError(error) { if (typeof error === 'string') { try { return JSON.parse(error); } catch { throw new Error('Invalid JSON error response'); } } if (typeof error === 'object' && error !== null) { return error; } throw new Error('Unsupported error format'); } } class PghSsoStorageService { constructor(ssoConfig) { this.ssoConfig = ssoConfig; this.localStorageFactory = PghStorageFactory.localStorageFactory(); this.iframeIncomingMessages$ = new Subject(); this.isIframeLoaded = false; this.IFRAME_ID = 'tapsell-sso-shared'; this.iframeMessageListener = ({ data, origin, source }) => { if (origin !== this.ssoConfig.sharedIframeUrl || source !== this.sharedIframe.contentWindow) return; // console.log('#ee data, origin, source', data, origin, source); this.iframeIncomingMessages$.next(data.tapsell); }; this.setupIframeEventListeners(); this.initSharedIframe(); this.sharedIframe.addEventListener('load', () => (this.isIframeLoaded = true)); } getDataFromStorage(key) { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing return this.localStorageFactory.getItem(key) || Cookies.get(key); } setDataInStorage(key, value) { if (!value) return; if (this.getDataFromStorage(key) === value) return; this.storeCookieSecureCrossSite(key, value); this.storeInLocalStorage(key, value); } deleteDataFromStorage(key) { this.deleteCookie(key); this.deleteFromLocalStorage(key); } storeCookieSecureCrossSite(key, value) { Cookies.set(key, value, { domain: this.ssoConfig.pghCookieStoreDomain, path: '/', sameSite: 'None', secure: true, expires: new Date(9999, 0), }); } deleteCookie(key) { Cookies.remove(key, { domain: this.ssoConfig.pghCookieStoreDomain, path: '/', sameSite: 'None', secure: true, }); } storeInLocalStorage(key, value) { this.localStorageFactory.setItem(key, value); } deleteFromLocalStorage(key) { this.localStorageFactory.removeItem(key); } initSharedIframe() { this.sharedIframe = document.getElementById(this.IFRAME_ID); if (this.sharedIframe) { this.isIframeLoaded = true; return; } this.sharedIframe = document.createElement('iframe'); this.sharedIframe.src = this.ssoConfig.sharedIframeUrl; this.sharedIframe.style.setProperty('display', 'none'); this.sharedIframe.id = this.IFRAME_ID; this.sharedIframe.height = '0'; this.sharedIframe.name = this.IFRAME_ID; this.sharedIframe.setAttribute('role', 'none'); document.body.appendChild(this.sharedIframe); } postMessageToIframe(data) { if (!this.isIframeLoaded) { const _interval$ = interval(50) .pipe(takeUntil(timer(1000))) .subscribe(() => { if (this.isIframeLoaded) { this.postMessageToIframe(data); _interval$.unsubscribe(); } }); return; } this.sharedIframe.contentWindow?.postMessage({ tapsell: data }, '*'); // todo targetOrigin } setupIframeEventListeners() { window.addEventListener('message', this.iframeMessageListener); } ngOnDestroy() { window.removeEventListener('message', this.iframeMessageListener); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoStorageService, deps: [{ token: PGH_SSO_ENRICHED_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoStorageService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoStorageService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [PGH_SSO_ENRICHED_CONFIG] }] }] }); class PghSsoService extends withDestroy() { constructor(ssoConfig, ssoStorageService, router) { super(); this.ssoConfig = ssoConfig; this.ssoStorageService = ssoStorageService; this.router = router; this._authData$ = new BehaviorSubject(undefined); this.authData$ = this._authData$.pipe(map(authData => { if (!authData || !PghSsoUtils.validateAuthData(authData)) return; const { userAccountData, adminAccountData, sub: email, } = PghSsoUtils.decodeJwtToken(authData.accessToken); return { ...authData, userId: userAccountData?.userId, email, impersonatedByAdmin: !!adminAccountData?.impersonated, }; })); this.isLoggedIn$ = race(this.authData$.pipe(filter(d => !!d)), interval(100).pipe(takeWhile(() => !this.ssoStorageService.isIframeLoaded, true), takeUntil(timer(1000)), last())).pipe(switchMap(_ => this.authData$.pipe(map(d => !!d)))); this.lastAuthStorePriority = 0; this.setupIframeEventListeners(); this.retrieveTokenFromStorage(); this.retrieveTokenFromUrl(); this.authData$.pipe(takeUntil(this._destroyed$)).subscribe(data => { this.authData = data; }); } retrieveTokenFromStorage() { const partialAuthData = { accessToken: this.ssoStorageService.getDataFromStorage('accessToken'), refreshToken: this.ssoStorageService.getDataFromStorage('refreshToken'), }; if (PghSsoUtils.validateAuthData(partialAuthData)) { this._authData$.next(partialAuthData); } } retrieveTokenFromUrl() { const params = {}; new URLSearchParams(location.search).forEach((value, key) => { params[key] = value; }); if (!PghSsoUtils.validateAuthData(params)) return; this.storeAuthData(params, 1); UrlUtils.removeQueryParams(this.router, PghSsoUtils.AUTH_DATA_KEYS); } setupIframeEventListeners() { let priority = 1; this.ssoStorageService.iframeIncomingMessages$ .pipe(takeUntil(this._destroyed$), concatMap((value, index) => { priority = index === 0 ? 0 : 1; // first emit has less priority return of(value); })) .subscribe(data => { // console.log('#ee iframe message', data); if (PghSsoUtils.isTokenMessage(data)) { if (!data.value) return; if (!PghSsoUtils.validateAuthData(data.value)) return; this.storeAuthData(data.value, priority); } else if (PghSsoUtils.isLogoutMessage(data)) { // Check for UUID in the URL const uuid = new URLSearchParams(location.search).get(PghSsoUtils.UUID_PARAM); if (!uuid) { this.logout(priority); } } }); } storeAuthData(data, priority = 1) { // console.log('#ee store auth data', data, priority, this.lastAuthStorePriority); if (!PghSsoUtils.validateAuthData(data)) return; if (priority < this.lastAuthStorePriority) return; this.lastAuthStorePriority = priority; if (this.authData && ObjectUtils.isEqualObject(data, this.authData)) return; this._authData$.next(data); // console.log('#ee data', data); this.ssoStorageService.postMessageToIframe({ type: 'sso:auth', value: data, }); PghSsoUtils.AUTH_DATA_KEYS.forEach(key => { this.ssoStorageService.setDataInStorage(key, data[key]); }); } logout(priority = 1) { // console.log('#ee logout', priority, this.lastAuthStorePriority); if (priority < this.lastAuthStorePriority) return; this.lastAuthStorePriority = priority; if (!this.authData) return; this.ssoConfig .expireTokenAfterLogout({ access_token: this.authData.accessToken, refresh_token: this.authData.refreshToken, }) .pipe(switchMap(() => { this._authData$.next(undefined); this.ssoStorageService.postMessageToIframe({ type: 'sso:logout', value: {}, }); PghSsoUtils.AUTH_DATA_KEYS.forEach(key => { this.ssoStorageService.deleteDataFromStorage(key); }); if (this.ssoConfig.redirectToSsoAfterLogout) { this.redirectToLoginPageInSso(); } return of(null); })) .subscribe(); } redirectToLoginPageInSso() { if (this.ssoConfig.isInsideSsoPanel) { this.router.navigate(['/login']); } else { location.href = `${this.ssoConfig.ssoPanelUrl}?${this.ssoConfig.pghRedirectUrlParam}=${encodeURIComponent(location.href)}`; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoService, deps: [{ token: PGH_SSO_ENRICHED_CONFIG }, { token: PghSsoStorageService }, { token: i1.Router }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [PGH_SSO_ENRICHED_CONFIG] }] }, { type: PghSsoStorageService }, { type: i1.Router }] }); const PGH_SERVICES_SHORTCUTS_DEFAULT = [ { link: 'https://sso-panel.pegah.tech', name: 'SSO', desc: 'مدیریت حساب', logo: 'https://cdn.tapture.ir/tapsell/web/front/web-logo/tapsell-new/icons/circle/colored/color/Icon+50px.svg', quickLinks: [ { link: 'https://sso-panel.pegah.tech/profile', name: 'پروفایل و حساب', exposeTokenInUrl: false, }, { link: 'https://sso-panel.pegah.tech/financial-management', name: 'مدیریت مالی', exposeTokenInUrl: false, }, { link: 'https://sso-panel.pegah.tech/financial-management', name: 'افزایش اعتبار', exposeTokenInUrl: false, }, ], exposeTokenInUrl: false, }, { link: 'https://dashboard.tapsell.ir', name: 'تپسل', desc: 'پلتفرم تبلیغات موبایل', logo: 'https://cdn.tapture.ir/tapsell/web/front/web-logo/tapsell-new/icons/circle/colored/blue/Icon+50px.svg', nightLogo: 'https://cdn.tapture.ir/tapsell/web/front/web-logo/tapsell-new/icons/circle/colored/blue/Icon+50px.svg', quickLinks: [ { link: 'https://dashboard.tapsell.ir/campaigns', name: 'کمپین‌ها', exposeTokenInUrl: false }, { link: 'https://dashboard.tapsell.ir/apps', name: 'اپلیکیشن‌ها', exposeTokenInUrl: false }, { link: 'https://college.tapsell.ir/', name: 'کالج', exposeTokenInUrl: false }, ], exposeTokenInUrl: false, }, { link: 'https://panel.mediaad.org', name: 'مدیااد', desc: 'پلتفرم تبلیغات وب', logo: 'https://cdn.tapture.ir/tapsell/web/front/web-logo/tapsell-new/icons/circle/colored/green/Icon+50px.svg', nightLogo: 'https://cdn.tapture.ir/tapsell/web/front/web-logo/tapsell-new/icons/circle/colored/green/Icon+50px.svg', quickLinks: [ { link: 'https://panel.mediaad.org/campaigns', name: 'کمپین‌ها', exposeTokenInUrl: false }, ], exposeTokenInUrl: false, }, ]; class PghAuthorizedLinkDirective { constructor(router, ssoService, elementRef, ssoConfig) { this.router = router; this.ssoService = ssoService; this.elementRef = elementRef; this.ssoConfig = ssoConfig; this.exposeTokenInUrl = input(true); } onClick(event) { event.preventDefault(); const linkElement = this.elementRef.nativeElement; const href = linkElement.getAttribute('href'); const target = linkElement.getAttribute('target') ?? '_self'; if (!href || !this.ssoService.authData) return; const { accessToken, refreshToken } = this.ssoService.authData; const isInternal = linkElement.hostname === window.location.hostname; const mappedTokenData = { access_token: accessToken, refresh_token: refreshToken, }; if (isInternal) { const url = UrlUtils.removeOrigin(href); this.router.navigateByUrl(url).then(); return; } (this.exposeTokenInUrl() ? of(UrlUtils.mergeParamsToUrl(href, { accessToken, refreshToken })) : this.ssoConfig .getUUidRequest(mappedTokenData) .pipe(map(uuid => UrlUtils.mergeParamsToUrl(href, { [PghSsoUtils.UUID_PARAM]: uuid })))).subscribe({ next: newUrl => window.open(newUrl, target), error: () => { console.error('Failed to fetch UUID for authorized link.'); }, }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghAuthorizedLinkDirective, deps: [{ token: i1.Router }, { token: PghSsoService }, { token: i0.ElementRef }, { token: PGH_SSO_ENRICHED_CONFIG }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.13", type: PghAuthorizedLinkDirective, isStandalone: false, selector: "a[pghAuthorizedLink]", inputs: { exposeTokenInUrl: { classPropertyName: "exposeTokenInUrl", publicName: "exposeTokenInUrl", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick($event)" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghAuthorizedLinkDirective, decorators: [{ type: Directive, args: [{ selector: 'a[pghAuthorizedLink]', standalone: false, }] }], ctorParameters: () => [{ type: i1.Router }, { type: PghSsoService }, { type: i0.ElementRef }, { type: undefined, decorators: [{ type: Inject, args: [PGH_SSO_ENRICHED_CONFIG] }] }], propDecorators: { onClick: [{ type: HostListener, args: ['click', ['$event']] }] } }); class PghServicesShortcutComponent { constructor(darkModeService) { this.darkModeService = darkModeService; this.hideIndicator = input(false); this.indicatorColor = input('primary'); this.servicesShortcuts = input(PGH_SERVICES_SHORTCUTS_DEFAULT); this.isAdmin = input(false); this.services = signal([]); this.indicatorPersistData = signal({ persistId: 'services-shortcut', persistDays: 7, }); } ngOnInit() { this.setServicesShortcuts(); } ngOnChanges(changes) { if (changes.servicesShortcuts) { this.setServicesShortcuts(); } } setServicesShortcuts() { this.services.set(this.servicesShortcuts().map(service => ({ ...service, isCurrentService: service.link.includes(location.hostname), }))); } get isDarkModeEnabled() { return this.darkModeService.isDarkModeEnabled; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghServicesShortcutComponent, deps: [{ token: i1$1.PghDarkModeService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.13", type: PghServicesShortcutComponent, isStandalone: false, selector: "pgh-services-shortcut", inputs: { hideIndicator: { classPropertyName: "hideIndicator", publicName: "hideIndicator", isSignal: true, isRequired: false, transformFunction: null }, indicatorColor: { classPropertyName: "indicatorColor", publicName: "indicatorColor", isSignal: true, isRequired: false, transformFunction: null }, servicesShortcuts: { classPropertyName: "servicesShortcuts", publicName: "servicesShortcuts", isSignal: true, isRequired: false, transformFunction: null }, isAdmin: { classPropertyName: "isAdmin", publicName: "isAdmin", isSignal: true, isRequired: false, transformFunction: null } }, usesOnChanges: true, ngImport: i0, template: "<pgh-overlay\n [origin]=\"servicesMenuOriginRef\"\n #servicesMenuRef\n [panelClasses]=\"['pgh-services-shortcut-menu']\"\n>\n <ng-template #pghOverlayContent>\n <a\n *ngFor=\"let s of services()\"\n pghAuthorizedLink\n [href]=\"s.link\"\n [exposeTokenInUrl]=\"s.exposeTokenInUrl\"\n target=\"_blank\"\n class=\"pgh-service-shortcut-item text-gray\"\n [class.is-active]=\"s.isCurrentService\"\n matRipple\n >\n <div class=\"pgh-service-item-img\">\n <img\n class=\"max-width-100\"\n [src]=\"isDarkModeEnabled ? (s.nightLogo ? s.nightLogo : s.logo) : s.logo\"\n [alt]=\"s.name\"\n />\n </div>\n <div class=\"pgh-service-item-name d-flex align-items-center\">\n <h2 class=\"mb-0 font-weight-bold text-gray-700\">{{ s.name }}</h2>\n <ng-container *ngIf=\"s.desc\">\n <span class=\"mx-2\">-</span>\n <span class=\"text-gray\">{{ s.desc }}</span>\n </ng-container>\n </div>\n <div class=\"pgh-service-item-quick-links d-flex\">\n <a\n *ngFor=\"let q of s.quickLinks\"\n pghAuthorizedLink\n [href]=\"q.link\"\n target=\"_blank\"\n [exposeTokenInUrl]=\"s.exposeTokenInUrl\"\n >\n <pgh-status-label\n [showIcon]=\"false\"\n [label]=\"q.name\"\n labelSize=\"mini\"\n type=\"gray\"\n ></pgh-status-label>\n </a>\n </div>\n </a>\n </ng-template>\n</pgh-overlay>\n\n<button\n mat-button\n cdkOverlayOrigin\n #servicesMenuOriginRef=\"cdkOverlayOrigin\"\n (click)=\"servicesMenuRef.open()\"\n>\n <mat-icon\n svgIcon=\"apps\"\n pghIndicator\n [pghIndicatorHidden]=\"hideIndicator()\"\n [pghIndicatorColor]=\"indicatorColor()\"\n [pghIndicatorPersistData]=\"indicatorPersistData()\"\n [pghIndicatorPosition]=\"'top start'\"\n ></mat-icon>\n <ng-container *ngIf=\"isAdmin(); else userServices\">\n <span *ifMedia=\"'sm'\">{{ 'SSO_ADMIN_SERVICES_SHORTCUT_TITLE' | translate }}</span>\n </ng-container>\n <ng-template #userServices>\n <span *ifMedia=\"'sm'\">{{ 'SSO_SERVICES_SHORTCUT_TITLE' | translate }}</span>\n </ng-template>\n</button>\n", styles: [".pgh-service-shortcut-item{cursor:pointer;padding:1rem;display:grid;grid-template-areas:\"logo title\" \"logo quicklinks\";grid-template-rows:auto auto;grid-template-columns:3.5rem minmax(15rem,auto);align-items:center;grid-gap:0 1rem}.pgh-service-shortcut-item:hover{background:var(--eee)}.pgh-service-shortcut-item:not(:last-child){border-bottom:1px solid var(--light-border-color)}.pgh-service-shortcut-item.is-active{background-color:var(--primary-50)}.pgh-service-item-img{grid-area:logo}.pgh-service-item-name{grid-area:title}.pgh-service-item-quick-links{grid-area:quicklinks}.pgh-services-shortcut-menu.pgh-overlay{padding-block:.5rem;padding-inline:0}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i4.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i5.PghOverlayComponent, selector: "pgh-overlay", inputs: ["origin", "panelClasses"], exportAs: ["pghOverlay"] }, { kind: "directive", type: i6.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: i7.PghStatusLabelComponent, selector: "pgh-status-label", inputs: ["type", "label", "showIcon", "typeMapping", "styleMode", "labelSize"] }, { kind: "directive", type: i8.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }, { kind: "directive", type: PghAuthorizedLinkDirective, selector: "a[pghAuthorizedLink]", inputs: ["exposeTokenInUrl"] }, { kind: "directive", type: i10.PghIndicatorDirective, selector: "[pghIndicator]", inputs: ["pghIndicatorColor", "pghIndicatorPersistData", "pghIndicatorPosition", "pghIndicatorHidden"], outputs: ["pghIndicatorHiddenChange"], exportAs: ["pghIndicator"] }, { kind: "directive", type: i11.PghMediaDirective, selector: "[ifMedia]", inputs: ["ifMedia"] }, { kind: "pipe", type: i12.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghServicesShortcutComponent, decorators: [{ type: Component, args: [{ selector: 'pgh-services-shortcut', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<pgh-overlay\n [origin]=\"servicesMenuOriginRef\"\n #servicesMenuRef\n [panelClasses]=\"['pgh-services-shortcut-menu']\"\n>\n <ng-template #pghOverlayContent>\n <a\n *ngFor=\"let s of services()\"\n pghAuthorizedLink\n [href]=\"s.link\"\n [exposeTokenInUrl]=\"s.exposeTokenInUrl\"\n target=\"_blank\"\n class=\"pgh-service-shortcut-item text-gray\"\n [class.is-active]=\"s.isCurrentService\"\n matRipple\n >\n <div class=\"pgh-service-item-img\">\n <img\n class=\"max-width-100\"\n [src]=\"isDarkModeEnabled ? (s.nightLogo ? s.nightLogo : s.logo) : s.logo\"\n [alt]=\"s.name\"\n />\n </div>\n <div class=\"pgh-service-item-name d-flex align-items-center\">\n <h2 class=\"mb-0 font-weight-bold text-gray-700\">{{ s.name }}</h2>\n <ng-container *ngIf=\"s.desc\">\n <span class=\"mx-2\">-</span>\n <span class=\"text-gray\">{{ s.desc }}</span>\n </ng-container>\n </div>\n <div class=\"pgh-service-item-quick-links d-flex\">\n <a\n *ngFor=\"let q of s.quickLinks\"\n pghAuthorizedLink\n [href]=\"q.link\"\n target=\"_blank\"\n [exposeTokenInUrl]=\"s.exposeTokenInUrl\"\n >\n <pgh-status-label\n [showIcon]=\"false\"\n [label]=\"q.name\"\n labelSize=\"mini\"\n type=\"gray\"\n ></pgh-status-label>\n </a>\n </div>\n </a>\n </ng-template>\n</pgh-overlay>\n\n<button\n mat-button\n cdkOverlayOrigin\n #servicesMenuOriginRef=\"cdkOverlayOrigin\"\n (click)=\"servicesMenuRef.open()\"\n>\n <mat-icon\n svgIcon=\"apps\"\n pghIndicator\n [pghIndicatorHidden]=\"hideIndicator()\"\n [pghIndicatorColor]=\"indicatorColor()\"\n [pghIndicatorPersistData]=\"indicatorPersistData()\"\n [pghIndicatorPosition]=\"'top start'\"\n ></mat-icon>\n <ng-container *ngIf=\"isAdmin(); else userServices\">\n <span *ifMedia=\"'sm'\">{{ 'SSO_ADMIN_SERVICES_SHORTCUT_TITLE' | translate }}</span>\n </ng-container>\n <ng-template #userServices>\n <span *ifMedia=\"'sm'\">{{ 'SSO_SERVICES_SHORTCUT_TITLE' | translate }}</span>\n </ng-template>\n</button>\n", styles: [".pgh-service-shortcut-item{cursor:pointer;padding:1rem;display:grid;grid-template-areas:\"logo title\" \"logo quicklinks\";grid-template-rows:auto auto;grid-template-columns:3.5rem minmax(15rem,auto);align-items:center;grid-gap:0 1rem}.pgh-service-shortcut-item:hover{background:var(--eee)}.pgh-service-shortcut-item:not(:last-child){border-bottom:1px solid var(--light-border-color)}.pgh-service-shortcut-item.is-active{background-color:var(--primary-50)}.pgh-service-item-img{grid-area:logo}.pgh-service-item-name{grid-area:title}.pgh-service-item-quick-links{grid-area:quicklinks}.pgh-services-shortcut-menu.pgh-overlay{padding-block:.5rem;padding-inline:0}\n"] }] }], ctorParameters: () => [{ type: i1$1.PghDarkModeService }] }); class PghAuthorizedLinkPipe { constructor(ssoService) { this.ssoService = ssoService; } transform(link) { if (!link) return link; if (!this.ssoService.authData) return link; return UrlUtils$1.mergeParamsToUrl(link, this.ssoService.authData); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghAuthorizedLinkPipe, deps: [{ token: PghSsoService }], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.13", ngImport: i0, type: PghAuthorizedLinkPipe, isStandalone: false, name: "pghAuthorizedLink" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghAuthorizedLinkPipe, decorators: [{ type: Pipe, args: [{ name: 'pghAuthorizedLink', standalone: false, }] }], ctorParameters: () => [{ type: PghSsoService }] }); class PghSsoAuthorizedLinkModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoAuthorizedLinkModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.13", ngImport: i0, type: PghSsoAuthorizedLinkModule, declarations: [PghAuthorizedLinkDirective, PghAuthorizedLinkPipe], imports: [CommonModule], exports: [PghAuthorizedLinkDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoAuthorizedLinkModule, imports: [CommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoAuthorizedLinkModule, decorators: [{ type: NgModule, args: [{ declarations: [PghAuthorizedLinkDirective, PghAuthorizedLinkPipe], imports: [CommonModule], exports: [PghAuthorizedLinkDirective], }] }] }); class PghServicesShortcutModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghServicesShortcutModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.13", ngImport: i0, type: PghServicesShortcutModule, declarations: [PghServicesShortcutComponent], imports: [CommonModule, MatIconModule, MatButtonModule, PghOverlayModule, PghStatusLabelModule, MatRippleModule, PghSsoAuthorizedLinkModule, PghIndicatorModule, PghMediaModule, TranslateModule], exports: [PghServicesShortcutComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghServicesShortcutModule, imports: [CommonModule, MatIconModule, MatButtonModule, PghOverlayModule, PghStatusLabelModule, MatRippleModule, PghSsoAuthorizedLinkModule, PghIndicatorModule, PghMediaModule, TranslateModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghServicesShortcutModule, decorators: [{ type: NgModule, args: [{ declarations: [PghServicesShortcutComponent], imports: [ CommonModule, MatIconModule, MatButtonModule, PghOverlayModule, PghStatusLabelModule, MatRippleModule, PghSsoAuthorizedLinkModule, PghIndicatorModule, PghMediaModule, TranslateModule, ], exports: [PghServicesShortcutComponent], }] }] }); class PghSsoRedirectService extends withDestroy() { constructor(activatedRoute, router, pghSsoService, ssoConfig) { super(); this.activatedRoute = activatedRoute; this.router = router; this.pghSsoService = pghSsoService; this.ssoConfig = ssoConfig; this.setupRedirectUrlRetriever(); } setupRedirectUrlRetriever() { const params = {}; new URLSearchParams(location.search).forEach((value, key) => { params[key] = value; }); this.retrieveRedirectUrl(params); this.activatedRoute.queryParams.pipe(takeUntil(this._destroyed$)).subscribe(queryParams => { this.retrieveRedirectUrl(queryParams); }); } retrieveRedirectUrl(queryParams) { const { redirect } = queryParams; if (!redirect) return; try { const redirectUrl = new URL(redirect.trim()); const trustedOrigins = this.ssoConfig.trustedDomains.map(url => new URL(url).origin); const { isDevMode } = this.ssoConfig; const isLocalhost = isDevMode && ['localhost', '127.0.0.1', '::1'].includes(redirectUrl.hostname); const isTrusted = isLocalhost || trustedOrigins.includes(redirectUrl.origin); if (!isTrusted) return; this.redirectUrl = redirectUrl.href; return this.redirectUrl; } catch (error) { // If the redirect URL is invalid or malformed, silently ignore return; } } retrieveUuidAndNavigate(tokenData) { this.ssoConfig.getUUidRequest(tokenData).subscribe({ next: uuid => { const uuidParam = { [PghSsoUtils.UUID_PARAM]: uuid }; location.href = UrlUtils.mergeParamsToUrl(this.redirectUrl, uuidParam); }, error: error => { console.error('UUID retrieval failed', error); }, }); } navigateToRetrievedRedirectUrl(useParseUrl = false) { const routerNavigateFunction = (route) => useParseUrl ? this.router.parseUrl(route) : this.router.navigateByUrl(route); if (!this.redirectUrl) { return routerNavigateFunction('/'); } const redirectUrlParsed = new URL(this.redirectUrl); if (redirectUrlParsed.origin === location.origin) { return routerNavigateFunction(redirectUrlParsed.pathname); } if (!this.pghSsoService.authData) { location.href = this.redirectUrl; return false; } const tokenData = { access_token: this.pghSsoService.authData.accessToken, refresh_token: this.pghSsoService.authData.refreshToken, }; this.retrieveUuidAndNavigate(tokenData); return false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoRedirectService, deps: [{ token: i1.ActivatedRoute }, { token: i1.Router }, { token: PghSsoService }, { token: PGH_SSO_ENRICHED_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoRedirectService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoRedirectService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: i1.ActivatedRoute }, { type: i1.Router }, { type: PghSsoService }, { type: undefined, decorators: [{ type: Inject, args: [PGH_SSO_ENRICHED_CONFIG] }] }] }); class PghSsoAuthorizedGuard { constructor(ssoService, pghSsoRedirectService, router, ssoConfig) { this.ssoService = ssoService; this.pghSsoRedirectService = pghSsoRedirectService; this.router = router; this.ssoConfig = ssoConfig; } canActivate(route, _state) { if (this.ssoConfig.isInsideSsoPanel) { this.pghSsoRedirectService.retrieveRedirectUrl(route.queryParams); } const uuid = route.queryParamMap.get(PghSsoUtils.UUID_PARAM); return uuid ? this.authenticateWithUuid(uuid) : this.verifyLoginStatus(); } authenticateWithUuid(uuid) { return this.ssoConfig.getTokenRequest(uuid).pipe(switchMap(authData => this.handleAuthData(authData)), catchError(() => this.redirectToLogin())); } handleAuthData(authData) { if (!authData?.access_token) return this.redirectToLogin(); this.ssoService.storeAuthData({ accessToken: authData.access_token, refreshToken: authData.refresh_token, }); UrlUtils.removeQueryParams(this.router, [PghSsoUtils.UUID_PARAM]); return this.verifyLoginStatus(); } verifyLoginStatus() { return this.ssoService.isLoggedIn$.pipe(take(1), map(authData => { if (authData) { return true; } this.ssoService.redirectToLoginPageInSso(); return false; })); } redirectToLogin() { this.ssoService.redirectToLoginPageInSso(); return of(false); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoAuthorizedGuard, deps: [{ token: PghSsoService }, { token: PghSsoRedirectService }, { token: i1.Router }, { token: PGH_SSO_ENRICHED_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoAuthorizedGuard, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghSsoAuthorizedGuard, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: PghSsoService }, { type: PghSsoRedirectService }, { type: i1.Router }, { type: undefined, decorators: [{ type: Inject, args: [PGH_SSO_ENRICHED_CONFIG] }] }] }); class PghUserMenuComponent extends withDestroy() { constructor(ssoConfig, ssoService, router, cdr, darkModeService) { super(); this.ssoConfig = ssoConfig; this.ssoService = ssoService; this.router = router; this.cdr = cdr; this.darkModeService = darkModeService; this.userEmail = signal(this.ssoService.authData?.email); this.isAvatarNotLoaded = signal(false); this.userName = input(undefined); this.handleLogout = input(true); this.logout = output(); this._destroyed$ = new Subject(); } ngOnInit() { this.ssoService.authData$.pipe(takeUntil(this._destroyed$)).subscribe(data => { this.userEmail.set(data?.email); this.isAvatarNotLoaded.set(false); this.cdr.detectChanges(); }); } onLogout() { this.logout.emit(); if (!this.handleLogout()) return; this.ssoService.logout(); } toggleDarkMode() { this.darkModeService.toggleDarkMode(); } get isDarkModeEnabled() { return this.darkModeService.isDarkModeEnabled; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghUserMenuComponent, deps: [{ token: PGH_SSO_ENRICHED_CONFIG }, { token: PghSsoService }, { token: i1.Router }, { token: i0.ChangeDetectorRef }, { token: i1$1.PghDarkModeService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.13", type: PghUserMenuComponent, isStandalone: false, selector: "pgh-user-menu", inputs: { userName: { classPropertyName: "userName", publicName: "userName", isSignal: true, isRequired: false, transformFunction: null }, handleLogout: { classPropertyName: "handleLogout", publicName: "handleLogout", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { logout: "logout" }, usesInheritance: true, ngImport: i0, template: "<button class=\"py-2\" mat-button [matMenuTriggerFor]=\"menuRef\" [class.px-0]=\"!userName()\">\n <img\n *ngIf=\"userEmail()\"\n [src]=\"userEmail() | pghGravatar : 80\"\n class=\"border-radius-circle\"\n [width]=\"userName() ? 30 : 40\"\n [height]=\"userName() ? 30 : 40\"\n alt=\"\"\n (error)=\"isAvatarNotLoaded.set(true)\"\n />\n <mat-icon\n *ngIf=\"!userEmail() || isAvatarNotLoaded()\"\n svgIcon=\"account_circle\"\n class=\"square-40 text-gray\"\n ></mat-icon>\n <ng-template [ngIf]=\"userName()\">\n <span *ifMedia=\"'sm'\" class=\"ms-2\">{{ userName() }}</span>\n </ng-template>\n</button>\n\n<mat-menu #menuRef=\"matMenu\">\n <a\n [href]=\"ssoConfig.ssoPanelUrl + ssoConfig.profileRoute\"\n pghAuthorizedLink\n [exposeTokenInUrl]=\"false\"\n mat-menu-item\n class=\"text-black\"\n target=\"_blank\"\n >\n <mat-icon svgIcon=\"account_circle\"></mat-icon>\n <span *ifMedia=\"'sm'\">{{ 'SSO_USER_MENU_USER_PROFILE' | translate }}</span>\n <span *ifMedia=\"'max-xs'\">\n {{ userName() ? userName() : ('SSO_USER_MENU_USER_PROFILE' | translate) }}\n </span>\n </a>\n\n <button mat-menu-item (click)=\"toggleDarkMode()\">\n <mat-icon [svgIcon]=\"isDarkModeEnabled ? 'light_mode' : 'dark_mode'\"></mat-icon>\n <span>\n {{ (isDarkModeEnabled ? 'SSO_USER_MENU_LIGHT_MODE' : 'SSO_USER_MENU_DARK_MODE') | translate }}\n </span>\n </button>\n\n <a\n [href]=\"ssoConfig.ssoPanelUrl + ssoConfig.changePasswordRoute\"\n pghAuthorizedLink\n [exposeTokenInUrl]=\"false\"\n mat-menu-item\n class=\"text-black\"\n target=\"_blank\"\n >\n <mat-icon svgIcon=\"lock\"></mat-icon>\n <span>{{ 'SSO_USER_MENU_CHANGE_PASSWORD' | translate }}</span>\n </a>\n\n <button mat-menu-item (click)=\"onLogout()\">\n <mat-icon svgIcon=\"logout\"></mat-icon>\n <span>{{ 'SSO_USER_MENU_LOGOUT' | translate }}</span>\n </button>\n</mat-menu>\n", styles: [""], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i5$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i5$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i5$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PghAuthorizedLinkDirective, selector: "a[pghAuthorizedLink]", inputs: ["exposeTokenInUrl"] }, { kind: "component", type: i4.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "directive", type: i11.PghMediaDirective, selector: "[ifMedia]", inputs: ["ifMedia"] }, { kind: "pipe", type: i10$1.PghGravatarPipe, name: "pghGravatar" }, { kind: "pipe", type: i12.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghUserMenuComponent, decorators: [{ type: Component, args: [{ selector: 'pgh-user-menu', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<button class=\"py-2\" mat-button [matMenuTriggerFor]=\"menuRef\" [class.px-0]=\"!userName()\">\n <img\n *ngIf=\"userEmail()\"\n [src]=\"userEmail() | pghGravatar : 80\"\n class=\"border-radius-circle\"\n [width]=\"userName() ? 30 : 40\"\n [height]=\"userName() ? 30 : 40\"\n alt=\"\"\n (error)=\"isAvatarNotLoaded.set(true)\"\n />\n <mat-icon\n *ngIf=\"!userEmail() || isAvatarNotLoaded()\"\n svgIcon=\"account_circle\"\n class=\"square-40 text-gray\"\n ></mat-icon>\n <ng-template [ngIf]=\"userName()\">\n <span *ifMedia=\"'sm'\" class=\"ms-2\">{{ userName() }}</span>\n </ng-template>\n</button>\n\n<mat-menu #menuRef=\"matMenu\">\n <a\n [href]=\"ssoConfig.ssoPanelUrl + ssoConfig.profileRoute\"\n pghAuthorizedLink\n [exposeTokenInUrl]=\"false\"\n mat-menu-item\n class=\"text-black\"\n target=\"_blank\"\n >\n <mat-icon svgIcon=\"account_circle\"></mat-icon>\n <span *ifMedia=\"'sm'\">{{ 'SSO_USER_MENU_USER