Browse Source

Task/improve type safety of header component (#6913)

* feat(common): allow null for filters

* feat(client): implement login with access token dialog result interface

* feat(client): enforce encapsulation

* feat(client): enforce immutability

* feat(client): replace constructor based DI with inject function

* feat(client): implement view child signals

* feat(client): implement output signals

* feat(client): implement input signals

* fix(client): format html file
pull/6872/head^2
Kenrick Tandrian 2 days ago
committed by GitHub
parent
commit
cde519fa7b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 121
      apps/client/src/app/components/header/header.component.html
  2. 211
      apps/client/src/app/components/header/header.component.ts
  3. 4
      apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts
  4. 10
      apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts
  5. 10
      libs/common/src/lib/dtos/update-user-setting.dto.ts

121
apps/client/src/app/components/header/header.component.html

@ -1,14 +1,14 @@
<mat-toolbar class="px-0">
@if (user) {
<div class="d-flex h-100 logo-container" [class.filled]="hasTabs">
@if (user()) {
<div class="d-flex h-100 logo-container" [class.filled]="hasTabs()">
<a
class="align-items-center h-100 justify-content-start px-2 px-sm-3 rounded-0"
mat-button
[class.w-100]="hasTabs"
[class.w-100]="hasTabs()"
[routerLink]="['/']"
(click)="onLogoClick()"
>
<gf-logo [label]="pageTitle" />
<gf-logo [label]="pageTitle()" />
</a>
</div>
<span class="gf-spacer"></span>
@ -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]="['/']"
>Overview</a
@ -36,9 +36,10 @@
i18n
mat-button
[class]="{
'font-weight-bold': currentRoute === internalRoutes.portfolio.path,
'font-weight-bold':
currentRoute() === internalRoutes.portfolio.path,
'text-decoration-underline':
currentRoute === internalRoutes.portfolio.path
currentRoute() === internalRoutes.portfolio.path
}"
[routerLink]="routerLinkPortfolio"
>Portfolio</a
@ -50,9 +51,9 @@
i18n
mat-button
[class]="{
'font-weight-bold': currentRoute === internalRoutes.accounts.path,
'font-weight-bold': currentRoute() === internalRoutes.accounts.path,
'text-decoration-underline':
currentRoute === internalRoutes.accounts.path
currentRoute() === internalRoutes.accounts.path
}"
[routerLink]="routerLinkAccounts"
>Accounts</a
@ -66,9 +67,9 @@
mat-button
[class]="{
'font-weight-bold':
currentRoute === internalRoutes.adminControl.path,
currentRoute() === internalRoutes.adminControl.path,
'text-decoration-underline':
currentRoute === internalRoutes.adminControl.path
currentRoute() === internalRoutes.adminControl.path
}"
[routerLink]="routerLinkAdminControl"
>Admin Control</a
@ -81,29 +82,29 @@
i18n
mat-button
[class]="{
'font-weight-bold': currentRoute === routeResources,
'text-decoration-underline': currentRoute === routeResources
'font-weight-bold': currentRoute() === routeResources,
'text-decoration-underline': currentRoute() === routeResources
}"
[routerLink]="routerLinkResources"
>Resources</a
>
</li>
@if (
hasPermissionForSubscription && user?.subscription?.type === 'Basic'
hasPermissionForSubscription && user()?.subscription?.type === 'Basic'
) {
<li class="list-inline-item">
<a
class="d-none d-sm-block rounded"
mat-button
[class]="{
'font-weight-bold': currentRoute === routePricing,
'text-decoration-underline': currentRoute === routePricing
'font-weight-bold': currentRoute() === routePricing,
'text-decoration-underline': currentRoute() === routePricing
}"
[routerLink]="routerLinkPricing"
>
<span class="align-items-center d-flex">
<span i18n>Pricing</span>
@if (currentRoute !== routePricing && hasPromotion) {
@if (currentRoute() !== routePricing && hasPromotion()) {
<span class="badge badge-warning ml-1">%</span>
}
</span>
@ -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</a
@ -131,7 +132,7 @@
matBadge="&NoBreak;"
matBadgeSize="small"
matButton
[matBadgeHidden]="!hasFilters || !hasPermissionToChangeFilters"
[matBadgeHidden]="!hasFilters || !hasPermissionToChangeFilters()"
[matMenuTriggerFor]="assistantMenu"
[matMenuTriggerRestoreFocus]="false"
(menuOpened)="onOpenAssistant()"
@ -143,17 +144,19 @@
class="no-max-width"
xPosition="before"
[overlapTrigger]="true"
(closed)="assistantElement?.setIsOpen(false)"
(closed)="assistantElement()?.setIsOpen(false)"
>
<gf-assistant
#assistant
[deviceType]="deviceType"
[deviceType]="deviceType()"
[hasPermissionToAccessAdminControl]="
hasPermissionToAccessAdminControl
"
[hasPermissionToChangeDateRange]="hasPermissionToChangeDateRange"
[hasPermissionToChangeFilters]="hasPermissionToChangeFilters"
[user]="user"
[hasPermissionToChangeDateRange]="
hasPermissionToChangeDateRange()
"
[hasPermissionToChangeFilters]="hasPermissionToChangeFilters()"
[user]="user()"
(closed)="closeAssistant()"
(dateRangeChanged)="onDateRangeChange($event)"
(filtersChanged)="onFiltersChanged($event)"
@ -182,12 +185,13 @@
</button>
<mat-menu #accountMenu="matMenu" xPosition="before">
@if (
hasPermissionForSubscription && user?.subscription?.type === 'Basic'
hasPermissionForSubscription &&
user()?.subscription?.type === 'Basic'
) {
<a class="d-flex" mat-menu-item [routerLink]="routerLinkPricing"
><span class="align-items-center d-flex"
><span>
@if (user.subscription.offer.isRenewal) {
@if (user().subscription.offer.isRenewal) {
<ng-container i18n>Renew Plan</ng-container>
} @else {
<ng-container i18n>Upgrade Plan</ng-container>
@ -199,7 +203,7 @@
></a>
<hr class="m-0" />
}
@if (user?.access?.length > 0) {
@if (user()?.access?.length > 0) {
<button mat-menu-item (click)="impersonateAccount(null)">
<span class="align-items-center d-flex">
<ion-icon
@ -213,7 +217,7 @@
<span i18n>Me</span>
</span>
</button>
@for (accessItem of user?.access; track accessItem) {
@for (accessItem of user()?.access; track accessItem) {
<button mat-menu-item (click)="impersonateAccount(accessItem.id)">
<span class="align-items-center d-flex">
<ion-icon
@ -240,8 +244,8 @@
i18n
mat-menu-item
[class.font-weight-bold]="
currentRoute === internalRoutes.home.path ||
currentRoute === internalRoutes.zen.path
currentRoute() === internalRoutes.home.path ||
currentRoute() === internalRoutes.zen.path
"
[routerLink]="['/']"
>Overview</a
@ -251,7 +255,7 @@
i18n
mat-menu-item
[class.font-weight-bold]="
currentRoute === internalRoutes.portfolio.path
currentRoute() === internalRoutes.portfolio.path
"
[routerLink]="routerLinkPortfolio"
>Portfolio</a
@ -261,7 +265,7 @@
i18n
mat-menu-item
[class.font-weight-bold]="
currentRoute === internalRoutes.accounts.path
currentRoute() === internalRoutes.accounts.path
"
[routerLink]="routerLinkAccounts"
>Accounts</a
@ -270,7 +274,7 @@
i18n
mat-menu-item
[class.font-weight-bold]="
currentRoute === internalRoutes.account.path
currentRoute() === internalRoutes.account.path
"
[routerLink]="routerLinkAccount"
>My Ghostfolio</a
@ -281,7 +285,7 @@
i18n
mat-menu-item
[class.font-weight-bold]="
currentRoute === internalRoutes.adminControl.path
currentRoute() === internalRoutes.adminControl.path
"
[routerLink]="routerLinkAdminControl"
>Admin Control</a
@ -292,22 +296,23 @@
class="d-flex d-sm-none"
i18n
mat-menu-item
[class.font-weight-bold]="currentRoute === routeResources"
[class.font-weight-bold]="currentRoute() === routeResources"
[routerLink]="routerLinkResources"
>Resources</a
>
@if (
hasPermissionForSubscription && user?.subscription?.type === 'Basic'
hasPermissionForSubscription &&
user()?.subscription?.type === 'Basic'
) {
<a
class="d-flex d-sm-none"
mat-menu-item
[class.font-weight-bold]="currentRoute === routePricing"
[class.font-weight-bold]="currentRoute() === routePricing"
[routerLink]="routerLinkPricing"
>
<span class="align-items-center d-flex">
<span i18n>Pricing</span>
@if (currentRoute !== routePricing && hasPromotion) {
@if (currentRoute() !== routePricing && hasPromotion()) {
<span class="badge badge-warning ml-1">%</span>
}
</span>
@ -317,7 +322,7 @@
class="d-flex d-sm-none"
i18n
mat-menu-item
[class.font-weight-bold]="currentRoute === routeAbout"
[class.font-weight-bold]="currentRoute() === routeAbout"
[routerLink]="routerLinkAbout"
>About Ghostfolio</a
>
@ -327,17 +332,17 @@
</li>
</ul>
}
@if (user === null) {
<div class="d-flex h-100 logo-container" [class.filled]="hasTabs">
@if (user() === null) {
<div class="d-flex h-100 logo-container" [class.filled]="hasTabs()">
<a
class="align-items-center h-100 justify-content-start px-2 px-sm-3 rounded-0"
mat-button
[class.w-100]="hasTabs"
[class.w-100]="hasTabs()"
[routerLink]="['/']"
>
<gf-logo
[label]="pageTitle"
[showLabel]="currentRoute !== 'register'"
[label]="pageTitle()"
[showLabel]="currentRoute() !== 'register'"
/>
</a>
</div>
@ -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"
>Features</a
@ -362,8 +367,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</a
@ -375,14 +380,14 @@
class="d-sm-block rounded"
mat-button
[class]="{
'font-weight-bold': currentRoute === routePricing,
'text-decoration-underline': currentRoute === routePricing
'font-weight-bold': currentRoute() === routePricing,
'text-decoration-underline': currentRoute() === routePricing
}"
[routerLink]="routerLinkPricing"
>
<span class="align-items-center d-flex">
<span i18n>Pricing</span>
@if (currentRoute !== routePricing && hasPromotion) {
@if (currentRoute() !== routePricing && hasPromotion()) {
<span class="badge badge-warning ml-1">%</span>
}
</span>
@ -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"
>Markets</a
@ -421,7 +426,7 @@
<ng-container i18n>Sign in</ng-container>
</button>
</li>
@if (currentRoute !== 'register' && hasPermissionToCreateUser) {
@if (currentRoute() !== 'register' && hasPermissionToCreateUser) {
<li class="list-inline-item ml-1">
<a
class="d-none d-sm-block px-3 rounded"

211
apps/client/src/app/components/header/header.component.ts

@ -1,4 +1,7 @@
import { LoginWithAccessTokenDialogParams } from '@ghostfolio/client/components/login-with-access-token-dialog/interfaces/interfaces';
import {
LoginWithAccessTokenDialogParams,
LoginWithAccessTokenDialogResult
} from '@ghostfolio/client/components/login-with-access-token-dialog/interfaces/interfaces';
import { GfLoginWithAccessTokenDialogComponent } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.component';
import { LayoutService } from '@ghostfolio/client/core/layout.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
@ -24,12 +27,12 @@ import {
Component,
CUSTOM_ELEMENTS_SCHEMA,
DestroyRef,
EventEmitter,
HostListener,
Input,
inject,
input,
OnChanges,
Output,
ViewChild
output,
viewChild
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatBadgeModule } from '@angular/material/badge';
@ -71,78 +74,67 @@ import { catchError } from 'rxjs/operators';
styleUrls: ['./header.component.scss']
})
export class GfHeaderComponent implements OnChanges {
@HostListener('window:keydown', ['$event'])
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();
}
}
@Input() currentRoute: string;
@Input() deviceType: string;
@Input() hasPermissionToChangeDateRange: boolean;
@Input() hasPermissionToChangeFilters: boolean;
@Input() hasPromotion: boolean;
@Input() hasTabs: boolean;
@Input() info: InfoItem;
@Input() pageTitle: string;
@Input() user: User;
@Output() signOut = new EventEmitter<void>();
@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<string>();
public readonly deviceType = input.required<string>();
public readonly hasPermissionToChangeDateRange = input.required<boolean>();
public readonly hasPermissionToChangeFilters = input.required<boolean>();
public readonly hasPromotion = input.required<boolean>();
public readonly hasTabs = input.required<boolean>();
public readonly info = input.required<InfoItem | undefined>();
public readonly pageTitle = input.required<string>();
public readonly user = input.required<User | undefined>();
public readonly signOut = output<void>();
protected readonly assistantElement =
viewChild.required<GfAssistantComponent>('assistant');
protected readonly assistentMenuTriggerElement =
viewChild.required<MatMenuTrigger>('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'

4
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;
}

10
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<GfLoginWithAccessTokenDialogComponent>,
public dialogRef: MatDialogRef<
GfLoginWithAccessTokenDialogComponent,
LoginWithAccessTokenDialogResult
>,
private settingsStorageService: SettingsStorageService
) {
addIcons({ eyeOffOutline, eyeOutline });

10
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()

Loading…
Cancel
Save