diff --git a/apps/client/src/app/components/header/header.component.html b/apps/client/src/app/components/header/header.component.html index 283e930e0..ae3121861 100644 --- a/apps/client/src/app/components/header/header.component.html +++ b/apps/client/src/app/components/header/header.component.html @@ -1,14 +1,14 @@ - @if (user) { -
+ @if (user()) { + @@ -20,11 +20,11 @@ mat-button [class]="{ 'font-weight-bold': - currentRoute === internalRoutes.home.path || - currentRoute === internalRoutes.zen.path, + currentRoute() === internalRoutes.home.path || + currentRoute() === internalRoutes.zen.path, 'text-decoration-underline': - currentRoute === internalRoutes.home.path || - currentRoute === internalRoutes.zen.path + currentRoute() === internalRoutes.home.path || + currentRoute() === internalRoutes.zen.path }" [routerLink]="['/']" >OverviewPortfolioAccountsAdmin ControlResources @if ( - hasPermissionForSubscription && user?.subscription?.type === 'Basic' + hasPermissionForSubscription && user()?.subscription?.type === 'Basic' ) {
  • Pricing - @if (currentRoute !== routePricing && hasPromotion) { + @if (currentRoute() !== routePricing && hasPromotion()) { % } @@ -116,8 +117,8 @@ i18n mat-button [class]="{ - 'font-weight-bold': currentRoute === routeAbout, - 'text-decoration-underline': currentRoute === routeAbout + 'font-weight-bold': currentRoute() === routeAbout, + 'text-decoration-underline': currentRoute() === routeAbout }" [routerLink]="routerLinkAbout" >About @if ( - hasPermissionForSubscription && user?.subscription?.type === 'Basic' + hasPermissionForSubscription && + user()?.subscription?.type === 'Basic' ) { - @if (user.subscription.offer.isRenewal) { + @if (user().subscription.offer.isRenewal) { Renew Plan } @else { Upgrade Plan @@ -199,7 +203,7 @@ >
    } - @if (user?.access?.length > 0) { + @if (user()?.access?.length > 0) { - @for (accessItem of user?.access; track accessItem) { + @for (accessItem of user()?.access; track accessItem) {
  • } - @if (user === null) { -
    + @if (user() === null) { + @@ -349,8 +354,8 @@ i18n mat-button [class]="{ - 'font-weight-bold': currentRoute === routeFeatures, - 'text-decoration-underline': currentRoute === routeFeatures + 'font-weight-bold': currentRoute() === routeFeatures, + 'text-decoration-underline': currentRoute() === routeFeatures }" [routerLink]="routerLinkFeatures" >FeaturesAbout Pricing - @if (currentRoute !== routePricing && hasPromotion) { + @if (currentRoute() !== routePricing && hasPromotion()) { % } @@ -396,8 +401,8 @@ i18n mat-button [class]="{ - 'font-weight-bold': currentRoute === routeMarkets, - 'text-decoration-underline': currentRoute === routeMarkets + 'font-weight-bold': currentRoute() === routeMarkets, + 'text-decoration-underline': currentRoute() === routeMarkets }" [routerLink]="routerLinkMarkets" >MarketsSign in - @if (currentRoute !== 'register' && hasPermissionToCreateUser) { + @if (currentRoute() !== 'register' && hasPermissionToCreateUser) {
  • (); - - @ViewChild('assistant') assistantElement: GfAssistantComponent; - @ViewChild('assistantTrigger') assistentMenuTriggerElement: MatMenuTrigger; - - public hasFilters: boolean; - public hasImpersonationId: boolean; - public hasPermissionForAuthGoogle: boolean; - public hasPermissionForAuthOidc: boolean; - public hasPermissionForAuthToken: boolean; - public hasPermissionForSubscription: boolean; - public hasPermissionToAccessAdminControl: boolean; - public hasPermissionToAccessAssistant: boolean; - public hasPermissionToAccessFearAndGreedIndex: boolean; - public hasPermissionToCreateUser: boolean; - public impersonationId: string; - public internalRoutes = internalRoutes; - public isMenuOpen: boolean; - public routeAbout = publicRoutes.about.path; - public routeFeatures = publicRoutes.features.path; - public routeMarkets = publicRoutes.markets.path; - public routePricing = publicRoutes.pricing.path; - public routeResources = publicRoutes.resources.path; - public routerLinkAbout = publicRoutes.about.routerLink; - public routerLinkAccount = internalRoutes.account.routerLink; - public routerLinkAccounts = internalRoutes.accounts.routerLink; - public routerLinkAdminControl = internalRoutes.adminControl.routerLink; - public routerLinkFeatures = publicRoutes.features.routerLink; - public routerLinkMarkets = publicRoutes.markets.routerLink; - public routerLinkPortfolio = internalRoutes.portfolio.routerLink; - public routerLinkPricing = publicRoutes.pricing.routerLink; - public routerLinkRegister = publicRoutes.register.routerLink; - public routerLinkResources = publicRoutes.resources.routerLink; - - public constructor( - private dataService: DataService, - private destroyRef: DestroyRef, - private dialog: MatDialog, - private impersonationStorageService: ImpersonationStorageService, - private layoutService: LayoutService, - private notificationService: NotificationService, - private router: Router, - private settingsStorageService: SettingsStorageService, - private tokenStorageService: TokenStorageService, - private userService: UserService - ) { + public readonly currentRoute = input.required(); + public readonly deviceType = input.required(); + public readonly hasPermissionToChangeDateRange = input.required(); + public readonly hasPermissionToChangeFilters = input.required(); + public readonly hasPromotion = input.required(); + public readonly hasTabs = input.required(); + public readonly info = input.required(); + public readonly pageTitle = input.required(); + public readonly user = input.required(); + + public readonly signOut = output(); + + protected readonly assistantElement = + viewChild.required('assistant'); + protected readonly assistentMenuTriggerElement = + viewChild.required('assistantTrigger'); + + protected hasFilters: boolean; + protected hasImpersonationId: boolean; + protected hasPermissionForAuthGoogle: boolean; + protected hasPermissionForAuthOidc: boolean; + protected hasPermissionForAuthToken: boolean; + protected hasPermissionForSubscription: boolean; + protected hasPermissionToAccessAdminControl: boolean; + protected hasPermissionToAccessAssistant: boolean; + protected hasPermissionToAccessFearAndGreedIndex: boolean; + protected hasPermissionToCreateUser: boolean; + protected impersonationId: string; + protected readonly internalRoutes = internalRoutes; + protected isMenuOpen: boolean; + protected readonly routeAbout = publicRoutes.about.path; + protected readonly routeFeatures = publicRoutes.features.path; + protected readonly routeMarkets = publicRoutes.markets.path; + protected readonly routePricing = publicRoutes.pricing.path; + protected readonly routeResources = publicRoutes.resources.path; + protected readonly routerLinkAbout = publicRoutes.about.routerLink; + protected readonly routerLinkAccount = internalRoutes.account.routerLink; + protected readonly routerLinkAccounts = internalRoutes.accounts.routerLink; + protected readonly routerLinkAdminControl = + internalRoutes.adminControl.routerLink; + protected readonly routerLinkFeatures = publicRoutes.features.routerLink; + protected readonly routerLinkMarkets = publicRoutes.markets.routerLink; + protected readonly routerLinkPortfolio = internalRoutes.portfolio.routerLink; + protected readonly routerLinkPricing = publicRoutes.pricing.routerLink; + protected readonly routerLinkRegister = publicRoutes.register.routerLink; + protected readonly routerLinkResources = publicRoutes.resources.routerLink; + + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly dialog = inject(MatDialog); + private readonly impersonationStorageService = inject( + ImpersonationStorageService + ); + private readonly layoutService = inject(LayoutService); + private readonly notificationService = inject(NotificationService); + private readonly router = inject(Router); + private readonly settingsStorageService = inject(SettingsStorageService); + private readonly tokenStorageService = inject(TokenStorageService); + private readonly userService = inject(UserService); + + public constructor() { this.impersonationStorageService .onChangeHasImpersonation() .pipe(takeUntilDestroyed(this.destroyRef)) @@ -162,55 +154,71 @@ export class GfHeaderComponent implements OnChanges { }); } + @HostListener('window:keydown', ['$event']) + protected openAssistantWithHotKey(event: KeyboardEvent) { + if ( + event.key === '/' && + event.target instanceof Element && + event.target?.nodeName?.toLowerCase() !== 'input' && + event.target?.nodeName?.toLowerCase() !== 'textarea' && + this.hasPermissionToAccessAssistant + ) { + this.assistantElement().setIsOpen(true); + this.assistentMenuTriggerElement().openMenu(); + + event.preventDefault(); + } + } + public ngOnChanges() { this.hasFilters = this.userService.hasFilters(); this.hasPermissionForAuthGoogle = hasPermission( - this.info?.globalPermissions, + this.info()?.globalPermissions, permissions.enableAuthGoogle ); this.hasPermissionForAuthOidc = hasPermission( - this.info?.globalPermissions, + this.info()?.globalPermissions, permissions.enableAuthOidc ); this.hasPermissionForAuthToken = hasPermission( - this.info?.globalPermissions, + this.info()?.globalPermissions, permissions.enableAuthToken ); this.hasPermissionForSubscription = hasPermission( - this.info?.globalPermissions, + this.info()?.globalPermissions, permissions.enableSubscription ); this.hasPermissionToAccessAdminControl = hasPermission( - this.user?.permissions, + this.user()?.permissions, permissions.accessAdminControl ); this.hasPermissionToAccessAssistant = hasPermission( - this.user?.permissions, + this.user()?.permissions, permissions.accessAssistant ); this.hasPermissionToAccessFearAndGreedIndex = hasPermission( - this.info?.globalPermissions, + this.info()?.globalPermissions, permissions.enableFearAndGreedIndex ); this.hasPermissionToCreateUser = hasPermission( - this.info?.globalPermissions, + this.info()?.globalPermissions, permissions.createUserAccount ); } - public closeAssistant() { - this.assistentMenuTriggerElement?.closeMenu(); + protected closeAssistant() { + this.assistentMenuTriggerElement().closeMenu(); } - public impersonateAccount(aId: string) { + protected impersonateAccount(aId: string) { if (aId) { this.impersonationStorageService.setId(aId); } else { @@ -220,7 +228,7 @@ export class GfHeaderComponent implements OnChanges { window.location.reload(); } - public onDateRangeChange(dateRange: DateRange) { + protected onDateRangeChange(dateRange: DateRange) { this.dataService .putUserSetting({ dateRange }) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -232,7 +240,7 @@ export class GfHeaderComponent implements OnChanges { }); } - public onFiltersChanged(filters: Filter[]) { + protected onFiltersChanged(filters: Filter[]) { const userSetting: UpdateUserSettingDto = {}; for (const filter of filters) { @@ -260,32 +268,33 @@ export class GfHeaderComponent implements OnChanges { }); } - public onLogoClick() { - if (['home', 'zen'].includes(this.currentRoute)) { + protected onLogoClick() { + if (['home', 'zen'].includes(this.currentRoute())) { this.layoutService.getShouldReloadSubject().next(); } } - public onMenuClosed() { + protected onMenuClosed() { this.isMenuOpen = false; } - public onMenuOpened() { + protected onMenuOpened() { this.isMenuOpen = true; } - public onOpenAssistant() { - this.assistantElement.initialize(); + protected onOpenAssistant() { + this.assistantElement().initialize(); } - public onSignOut() { - this.signOut.next(); + protected onSignOut() { + this.signOut.emit(); } - public openLoginDialog() { + protected openLoginDialog() { const dialogRef = this.dialog.open< GfLoginWithAccessTokenDialogComponent, - LoginWithAccessTokenDialogParams + LoginWithAccessTokenDialogParams, + LoginWithAccessTokenDialogResult >(GfLoginWithAccessTokenDialogComponent, { autoFocus: false, data: { @@ -322,7 +331,7 @@ export class GfHeaderComponent implements OnChanges { }); } - public setToken(aToken: string) { + private setToken(aToken: string) { this.tokenStorageService.saveToken( aToken, this.settingsStorageService.getSetting(KEY_STAY_SIGNED_IN) === 'true' diff --git a/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts index e9222e142..b903fcfef 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts @@ -5,3 +5,7 @@ export interface LoginWithAccessTokenDialogParams { hasPermissionToUseAuthToken: boolean; title: string; } + +export interface LoginWithAccessTokenDialogResult { + accessToken: string | null; +} diff --git a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts index d79c7a675..d6ce28c96 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts +++ b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts @@ -22,7 +22,10 @@ import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { eyeOffOutline, eyeOutline } from 'ionicons/icons'; -import { LoginWithAccessTokenDialogParams } from './interfaces/interfaces'; +import { + LoginWithAccessTokenDialogParams, + LoginWithAccessTokenDialogResult +} from './interfaces/interfaces'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -49,7 +52,10 @@ export class GfLoginWithAccessTokenDialogComponent { public constructor( @Inject(MAT_DIALOG_DATA) public data: LoginWithAccessTokenDialogParams, - public dialogRef: MatDialogRef, + public dialogRef: MatDialogRef< + GfLoginWithAccessTokenDialogComponent, + LoginWithAccessTokenDialogResult + >, private settingsStorageService: SettingsStorageService ) { addIcons({ eyeOffOutline, eyeOutline }); diff --git a/libs/common/src/lib/dtos/update-user-setting.dto.ts b/libs/common/src/lib/dtos/update-user-setting.dto.ts index f2781835c..d46982e70 100644 --- a/libs/common/src/lib/dtos/update-user-setting.dto.ts +++ b/libs/common/src/lib/dtos/update-user-setting.dto.ts @@ -58,23 +58,23 @@ export class UpdateUserSettingDto { @IsArray() @IsOptional() - 'filters.accounts'?: string[]; + 'filters.accounts'?: string[] | null; @IsArray() @IsOptional() - 'filters.assetClasses'?: string[]; + 'filters.assetClasses'?: string[] | null; @IsString() @IsOptional() - 'filters.dataSource'?: string; + 'filters.dataSource'?: string | null; @IsString() @IsOptional() - 'filters.symbol'?: string; + 'filters.symbol'?: string | null; @IsArray() @IsOptional() - 'filters.tags'?: string[]; + 'filters.tags'?: string[] | null; @IsIn(['CHART', 'TABLE'] as HoldingsViewMode[]) @IsOptional()