From 4e6920b6d19b0173f981e797c83185aef4d497d3 Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Mon, 14 Jun 2021 16:06:35 +0200 Subject: [PATCH] Improve usability --- .../app/components/header/header.component.ts | 3 +- ...ogin-with-access-token-dialog.component.ts | 11 ++- .../login-with-access-token-dialog.html | 34 +++++---- .../login-with-access-token-dialog.module.ts | 2 + .../login-with-access-token-dialog.scss | 10 +++ .../pages/account/account-page.component.ts | 72 ++++++++++++++++--- .../src/app/pages/account/account-page.html | 27 ++----- .../app/pages/account/account-page.module.ts | 2 + .../src/app/services/web-authn.service.ts | 8 ++- 9 files changed, 119 insertions(+), 50 deletions(-) diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index f16094edc..6be6706a6 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -89,7 +89,8 @@ export class HeaderComponent implements OnChanges { autoFocus: false, data: { accessToken: '', - hasPermissionToUseSocialLogin: this.hasPermissionForSocialLogin + hasPermissionToUseSocialLogin: this.hasPermissionForSocialLogin, + title: 'Sign in' }, width: '30rem' }); 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 51dd143e3..0de670f92 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 @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; @Component({ selector: 'gf-login-with-access-token-dialog', @@ -8,7 +8,14 @@ import { MAT_DIALOG_DATA } from '@angular/material/dialog'; templateUrl: 'login-with-access-token-dialog.html' }) export class LoginWithAccessTokenDialog { - public constructor(@Inject(MAT_DIALOG_DATA) public data: any) {} + public constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any + ) {} ngOnInit() {} + + public onClose(): void { + this.dialogRef.close(); + } } diff --git a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html index 1e52e7faa..3040d5e08 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html +++ b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1,4 +1,9 @@ -

Sign in

+ +
@@ -19,20 +24,23 @@ [(ngModel)]="data.accessToken" > +
+
+
+
Stay signed in
-
-
- - +
+ +
diff --git a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.module.ts b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.module.ts index e2e136c76..e36c96cd0 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.module.ts +++ b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.module.ts @@ -8,6 +8,7 @@ import { MatDialogModule } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; +import { GfDialogHeaderModule } from '../dialog-header/dialog-header.module'; import { LoginWithAccessTokenDialog } from './login-with-access-token-dialog.component'; @NgModule({ @@ -16,6 +17,7 @@ import { LoginWithAccessTokenDialog } from './login-with-access-token-dialog.com imports: [ CommonModule, FormsModule, + GfDialogHeaderModule, MatButtonModule, MatCheckboxModule, MatDialogModule, diff --git a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.scss b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.scss index 39842634f..ea9627fa2 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.scss +++ b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.scss @@ -1,5 +1,15 @@ :host { + display: block; + textarea.mat-input-element.cdk-textarea-autosize { box-sizing: content-box; } + + .mat-checkbox { + ::ng-deep { + label { + margin-bottom: 0; + } + } + } } diff --git a/apps/client/src/app/pages/account/account-page.component.ts b/apps/client/src/app/pages/account/account-page.component.ts index 7d1393bdc..85ca88611 100644 --- a/apps/client/src/app/pages/account/account-page.component.ts +++ b/apps/client/src/app/pages/account/account-page.component.ts @@ -1,5 +1,14 @@ -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; +import { + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + ViewChild +} from '@angular/core'; +import { + MatSlideToggle, + MatSlideToggleChange +} from '@angular/material/slide-toggle'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; @@ -7,8 +16,8 @@ import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config'; import { Access, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { Currency } from '@prisma/client'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { EMPTY, Subject } from 'rxjs'; +import { catchError, takeUntil } from 'rxjs/operators'; @Component({ selector: 'gf-account-page', @@ -16,6 +25,9 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./account-page.scss'] }) export class AccountPageComponent implements OnDestroy, OnInit { + @ViewChild('toggleSignInWithFingerprintEnabledElement') + signInWithFingerprintElement: MatSlideToggle; + public accesses: Access[]; public baseCurrency: Currency; public currencies: Currency[] = []; @@ -30,7 +42,6 @@ export class AccountPageComponent implements OnDestroy, OnInit { */ public constructor( private changeDetectorRef: ChangeDetectorRef, - private dialog: MatDialog, private dataService: DataService, private userService: UserService, public webAuthnService: WebAuthnService @@ -88,21 +99,55 @@ export class AccountPageComponent implements OnDestroy, OnInit { }); } + public onSignInWithFingerprintChange(aEvent: MatSlideToggleChange) { + if (aEvent.checked) { + this.registerDevice(); + } else { + const confirmation = confirm( + 'Do you really want to remove this sign in method?' + ); + + if (confirmation) { + this.deregisterDevice(); + } else { + this.update(); + } + } + } + public ngOnDestroy() { this.unsubscribeSubject.next(); this.unsubscribeSubject.complete(); } - public registerDevice() { + private deregisterDevice() { this.webAuthnService - .register() - .subscribe(() => this.changeDetectorRef.markForCheck()); + .deregister() + .pipe( + catchError(() => { + this.update(); + + return EMPTY; + }) + ) + .subscribe(() => { + this.update(); + }); } - public deregisterDevice() { + private registerDevice() { this.webAuthnService - .deregister() - .subscribe(() => this.changeDetectorRef.markForCheck()); + .register() + .pipe( + catchError(() => { + this.update(); + + return EMPTY; + }) + ) + .subscribe(() => { + this.update(); + }); } private update() { @@ -112,6 +157,11 @@ export class AccountPageComponent implements OnDestroy, OnInit { .subscribe((response) => { this.accesses = response; + if (this.signInWithFingerprintElement) { + this.signInWithFingerprintElement.checked = + this.webAuthnService.isEnabled() ?? false; + } + this.changeDetectorRef.markForCheck(); }); } diff --git a/apps/client/src/app/pages/account/account-page.html b/apps/client/src/app/pages/account/account-page.html index f945fd19b..b8caf2c40 100644 --- a/apps/client/src/app/pages/account/account-page.html +++ b/apps/client/src/app/pages/account/account-page.html @@ -66,29 +66,14 @@ -
-
Authenticator
+
+
Sign in with fingerprint
- - + (change)="onSignInWithFingerprintChange($event)" + >
diff --git a/apps/client/src/app/pages/account/account-page.module.ts b/apps/client/src/app/pages/account/account-page.module.ts index 6e3daf3b8..8e7cddec5 100644 --- a/apps/client/src/app/pages/account/account-page.module.ts +++ b/apps/client/src/app/pages/account/account-page.module.ts @@ -7,6 +7,7 @@ import { MatDialogModule } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { GfPortfolioAccessTableModule } from '@ghostfolio/client/components/access-table/access-table.module'; import { AccountPageRoutingModule } from './account-page-routing.module'; @@ -26,6 +27,7 @@ import { AccountPageComponent } from './account-page.component'; MatFormFieldModule, MatInputModule, MatSelectModule, + MatSlideToggleModule, ReactiveFormsModule ], providers: [] diff --git a/apps/client/src/app/services/web-authn.service.ts b/apps/client/src/app/services/web-authn.service.ts index 619c40489..520202960 100644 --- a/apps/client/src/app/services/web-authn.service.ts +++ b/apps/client/src/app/services/web-authn.service.ts @@ -36,6 +36,10 @@ export class WebAuthnService { {} ) .pipe( + catchError((error) => { + console.warn('Could not register device', error); + return of(null); + }), switchMap((attOps) => { return startAttestation(attOps); }), @@ -59,8 +63,8 @@ export class WebAuthnService { public deregister() { const deviceId = this.getDeviceId(); return this.http.delete(`/api/auth-device/${deviceId}`).pipe( - catchError((e) => { - console.warn(`Could not deregister device ${deviceId}`, e); + catchError((error) => { + console.warn(`Could not deregister device ${deviceId}`, error); return of(null); }), tap(() =>