From bc2e9052585c748056588a3a4ed1d34553b6f8ac Mon Sep 17 00:00:00 2001
From: Thomas <4159106+dtslvr@users.noreply.github.com>
Date: Sat, 22 May 2021 09:18:59 +0200
Subject: [PATCH] Refactor AuthGuard, persist displayMode in user settings

---
 .../src/app/user/update-user-settings.dto.ts  |  7 ++-
 apps/api/src/app/user/user.controller.ts      |  3 +-
 apps/api/src/app/user/user.service.ts         | 16 +++--
 .../api/src/services/configuration.service.ts |  2 +-
 .../components/header/header.component.html   |  6 +-
 apps/client/src/app/core/auth.guard.ts        | 62 +++++++++++++------
 .../src/app/core/http-response.interceptor.ts |  1 -
 .../pages/account/account-page.component.ts   | 24 +++----
 .../src/app/pages/account/account-page.html   | 16 +++--
 apps/client/src/app/pages/zen/zen-page.scss   |  3 +-
 libs/common/src/lib/config.ts                 |  2 +-
 .../lib/interfaces/user-settings.interface.ts |  4 +-
 prisma/schema.prisma                          | 14 +++--
 13 files changed, 96 insertions(+), 64 deletions(-)

diff --git a/apps/api/src/app/user/update-user-settings.dto.ts b/apps/api/src/app/user/update-user-settings.dto.ts
index def1914bd..4c3e978bb 100644
--- a/apps/api/src/app/user/update-user-settings.dto.ts
+++ b/apps/api/src/app/user/update-user-settings.dto.ts
@@ -1,7 +1,10 @@
-import { Currency } from '@prisma/client';
+import { Currency, DisplayMode } from '@prisma/client';
 import { IsString } from 'class-validator';
 
 export class UpdateUserSettingsDto {
   @IsString()
-  currency: Currency;
+  baseCurrency: Currency;
+
+  @IsString()
+  displayMode: DisplayMode;
 }
diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts
index 6b11c064e..974a8195d 100644
--- a/apps/api/src/app/user/user.controller.ts
+++ b/apps/api/src/app/user/user.controller.ts
@@ -93,7 +93,8 @@ export class UserController {
     }
 
     return await this.userService.updateUserSettings({
-      currency: data.currency,
+      currency: data.baseCurrency,
+      displayMode: data.displayMode,
       userId: this.request.user.id
     });
   }
diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts
index f1994fffe..0ed3f2e44 100644
--- a/apps/api/src/app/user/user.service.ts
+++ b/apps/api/src/app/user/user.service.ts
@@ -5,7 +5,7 @@ import { resetHours } from '@ghostfolio/common/helper';
 import { User as IUser, UserWithSettings } from '@ghostfolio/common/interfaces';
 import { getPermissions, permissions } from '@ghostfolio/common/permissions';
 import { Injectable } from '@nestjs/common';
-import { Currency, Prisma, Provider, User } from '@prisma/client';
+import { Currency, DisplayMode, Prisma, Provider, User } from '@prisma/client';
 import { add } from 'date-fns';
 
 const crypto = require('crypto');
@@ -52,8 +52,9 @@ export class UserService {
       accounts: Account,
       permissions: currentPermissions,
       settings: {
-        baseCurrency: Settings?.currency || UserService.DEFAULT_CURRENCY,
-        locale
+        locale,
+        baseCurrency: Settings?.currency ?? UserService.DEFAULT_CURRENCY,
+        displayMode: Settings.displayMode ?? DisplayMode.DEFAULT
       },
       subscription: {
         expiresAt: resetHours(add(new Date(), { days: 7 })),
@@ -79,6 +80,7 @@ export class UserService {
       // Set default settings if needed
       user.Settings = {
         currency: UserService.DEFAULT_CURRENCY,
+        displayMode: DisplayMode.DEFAULT,
         updatedAt: new Date(),
         userId: user?.id
       };
@@ -187,14 +189,17 @@ export class UserService {
 
   public async updateUserSettings({
     currency,
+    displayMode,
     userId
   }: {
-    currency: Currency;
+    currency?: Currency;
+    displayMode?: DisplayMode;
     userId: string;
   }) {
     await this.prisma.settings.upsert({
       create: {
         currency,
+        displayMode,
         User: {
           connect: {
             id: userId
@@ -202,7 +207,8 @@ export class UserService {
         }
       },
       update: {
-        currency
+        currency,
+        displayMode
       },
       where: {
         userId: userId
diff --git a/apps/api/src/services/configuration.service.ts b/apps/api/src/services/configuration.service.ts
index a52bf85cd..0ba57989c 100644
--- a/apps/api/src/services/configuration.service.ts
+++ b/apps/api/src/services/configuration.service.ts
@@ -1,8 +1,8 @@
 import { Injectable } from '@nestjs/common';
+import { DataSource } from '@prisma/client';
 import { bool, cleanEnv, json, num, port, str } from 'envalid';
 
 import { Environment } from './interfaces/environment.interface';
-import { DataSource } from '.prisma/client';
 
 @Injectable()
 export class ConfigurationService {
diff --git a/apps/client/src/app/components/header/header.component.html b/apps/client/src/app/components/header/header.component.html
index a7b6800aa..598ca13df 100644
--- a/apps/client/src/app/components/header/header.component.html
+++ b/apps/client/src/app/components/header/header.component.html
@@ -8,11 +8,14 @@
       class="d-none d-sm-block"
       i18n
       mat-flat-button
-      [color]="currentRoute === 'home' ? 'primary' : null"
+      [color]="
+        currentRoute === 'home' || currentRoute === 'zen' ? 'primary' : null
+      "
       [routerLink]="['/']"
       >Overview</a
     >
     <a
+      *ngIf="user?.settings?.displayMode === 'DEFAULT'"
       class="d-none d-sm-block mx-1"
       i18n
       mat-flat-button
@@ -21,6 +24,7 @@
       >Analysis</a
     >
     <a
+      *ngIf="user?.settings?.displayMode === 'DEFAULT'"
       class="d-none d-sm-block mx-1"
       i18n
       mat-flat-button
diff --git a/apps/client/src/app/core/auth.guard.ts b/apps/client/src/app/core/auth.guard.ts
index 5df7a3638..00dc9553e 100644
--- a/apps/client/src/app/core/auth.guard.ts
+++ b/apps/client/src/app/core/auth.guard.ts
@@ -5,16 +5,19 @@ import {
   Router,
   RouterStateSnapshot
 } from '@angular/router';
+import { DisplayMode } from '@prisma/client';
+import { EMPTY } from 'rxjs';
+import { catchError } from 'rxjs/operators';
 
+import { DataService } from '../services/data.service';
 import { SettingsStorageService } from '../services/settings-storage.service';
-import { TokenStorageService } from '../services/token-storage.service';
 
 @Injectable({ providedIn: 'root' })
 export class AuthGuard implements CanActivate {
   constructor(
+    private dataService: DataService,
     private router: Router,
-    private settingsStorageService: SettingsStorageService,
-    private tokenStorageService: TokenStorageService
+    private settingsStorageService: SettingsStorageService
   ) {}
 
   canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
@@ -25,23 +28,46 @@ export class AuthGuard implements CanActivate {
       );
     }
 
-    const isLoggedIn = !!this.tokenStorageService.getToken();
+    return new Promise<boolean>((resolve) => {
+      this.dataService
+        .fetchUser()
+        .pipe(
+          catchError(() => {
+            if (state.url !== '/start') {
+              this.router.navigate(['/start']);
+              resolve(false);
+              return EMPTY;
+            }
 
-    if (isLoggedIn) {
-      if (state.url === '/start') {
-        this.router.navigate(['/home']);
-        return false;
-      }
+            resolve(true);
+            return EMPTY;
+          })
+        )
+        .subscribe((user) => {
+          if (
+            state.url === '/home' &&
+            user.settings.displayMode === DisplayMode.ZEN
+          ) {
+            this.router.navigate(['/zen']);
+            resolve(false);
+          } else if (state.url === '/start') {
+            if (user.settings.displayMode === DisplayMode.ZEN) {
+              this.router.navigate(['/zen']);
+            } else {
+              this.router.navigate(['/home']);
+            }
 
-      return true;
-    }
-
-    // Not logged in
-    if (state.url !== '/start') {
-      this.router.navigate(['/start']);
-      return false;
-    }
+            resolve(false);
+          } else if (
+            state.url === '/zen' &&
+            user.settings.displayMode === DisplayMode.DEFAULT
+          ) {
+            this.router.navigate(['/home']);
+            resolve(false);
+          }
 
-    return true;
+          resolve(true);
+        });
+    });
   }
 }
diff --git a/apps/client/src/app/core/http-response.interceptor.ts b/apps/client/src/app/core/http-response.interceptor.ts
index 72de866d2..3f49e7891 100644
--- a/apps/client/src/app/core/http-response.interceptor.ts
+++ b/apps/client/src/app/core/http-response.interceptor.ts
@@ -79,7 +79,6 @@ export class HttpResponseInterceptor implements HttpInterceptor {
           }
         } else if (error.status === StatusCodes.UNAUTHORIZED) {
           this.tokenStorageService.signOut();
-          this.router.navigate(['start']);
         }
 
         return throwError('');
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 a710ca2a5..4d1b428e3 100644
--- a/apps/client/src/app/pages/account/account-page.component.ts
+++ b/apps/client/src/app/pages/account/account-page.component.ts
@@ -51,8 +51,6 @@ export class AccountPageComponent implements OnDestroy, OnInit {
         this.dataService.fetchUser().subscribe((user) => {
           this.user = user;
 
-          this.user.settings.mode = 'ZEN';
-
           this.hasPermissionToUpdateUserSettings = hasPermission(
             this.user.permissions,
             permissions.updateUserSettings
@@ -70,9 +68,14 @@ export class AccountPageComponent implements OnDestroy, OnInit {
     this.update();
   }
 
-  public onChangeBaseCurrency({ value: currency }: { value: Currency }) {
+  public onChangeUserSettings(aKey: string, aValue: string) {
+    const settings = { ...this.user.settings, [aKey]: aValue };
+
     this.dataService
-      .putUserSettings({ currency })
+      .putUserSettings({
+        baseCurrency: settings?.baseCurrency,
+        displayMode: settings?.displayMode
+      })
       .pipe(takeUntil(this.unsubscribeSubject))
       .subscribe(() => {
         this.dataService.fetchUser().subscribe((user) => {
@@ -83,19 +86,6 @@ export class AccountPageComponent implements OnDestroy, OnInit {
       });
   }
 
-  public onChangeMode({ value: mode }: { value: Currency }) {
-    /*this.dataService
-      .putUserSettings({ currency })
-      .pipe(takeUntil(this.unsubscribeSubject))
-      .subscribe(() => {
-        this.dataService.fetchUser().subscribe((user) => {
-          this.user = user;
-
-          this.cd.markForCheck();
-        });
-      });*/
-  }
-
   public ngOnDestroy() {
     this.unsubscribeSubject.next();
     this.unsubscribeSubject.complete();
diff --git a/apps/client/src/app/pages/account/account-page.html b/apps/client/src/app/pages/account/account-page.html
index e3b6f830a..ff2904782 100644
--- a/apps/client/src/app/pages/account/account-page.html
+++ b/apps/client/src/app/pages/account/account-page.html
@@ -30,14 +30,14 @@
           <div class="d-flex mt-4 py-1">
             <div class="pt-4 w-50" i18n>Settings</div>
             <div class="w-50">
-              <form #changeBaseCurrencyForm="ngForm">
-                <mat-form-field appearance="outline" class="w-100">
+              <form #changeUserSettingsForm="ngForm">
+                <mat-form-field appearance="outline" class="mb-3 w-100">
                   <mat-label i18n>Base Currency</mat-label>
                   <mat-select
                     name="baseCurrency"
                     [disabled]="!hasPermissionToUpdateUserSettings"
                     [value]="user.settings.baseCurrency"
-                    (selectionChange)="onChangeBaseCurrency($event)"
+                    (selectionChange)="onChangeUserSettings('baseCurrency', $event.value)"
                   >
                     <mat-option
                       *ngFor="let currency of currencies"
@@ -46,15 +46,13 @@
                     >
                   </mat-select>
                 </mat-form-field>
-              </form>
-              <form #changeModeForm="ngForm">
                 <mat-form-field appearance="outline" class="w-100">
-                  <mat-label i18n>Mode</mat-label>
+                  <mat-label i18n>Display Mode</mat-label>
                   <mat-select
-                    name="baseCurrency"
+                    name="displayMode"
                     [disabled]="!hasPermissionToUpdateUserSettings"
-                    [value]="user.settings.mode"
-                    (selectionChange)="onChangeMode($event)"
+                    [value]="user.settings.displayMode"
+                    (selectionChange)="onChangeUserSettings('displayMode', $event.value)"
                   >
                     <mat-option value="DEFAULT">Default</mat-option>
                     <mat-option value="ZEN">Zen</mat-option>
diff --git a/apps/client/src/app/pages/zen/zen-page.scss b/apps/client/src/app/pages/zen/zen-page.scss
index d3be6211f..a0921ff14 100644
--- a/apps/client/src/app/pages/zen/zen-page.scss
+++ b/apps/client/src/app/pages/zen/zen-page.scss
@@ -4,8 +4,7 @@
 
   .chart-container {
     aspect-ratio: 16 / 9;
-    cursor: pointer;
-    margin-top: -1rem;
+    margin-top: 3rem;
     max-height: 50vh;
 
     // Fallback for aspect-ratio (using padding hack)
diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts
index 3c724127c..66191843d 100644
--- a/libs/common/src/lib/config.ts
+++ b/libs/common/src/lib/config.ts
@@ -1,4 +1,4 @@
-import { Currency } from '.prisma/client';
+import { Currency } from '@prisma/client';
 
 export const baseCurrency = Currency.CHF;
 
diff --git a/libs/common/src/lib/interfaces/user-settings.interface.ts b/libs/common/src/lib/interfaces/user-settings.interface.ts
index e025e0832..78a6ad01c 100644
--- a/libs/common/src/lib/interfaces/user-settings.interface.ts
+++ b/libs/common/src/lib/interfaces/user-settings.interface.ts
@@ -1,7 +1,7 @@
-import { Currency } from '@prisma/client';
+import { Currency, DisplayMode } from '@prisma/client';
 
 export interface UserSettings {
   baseCurrency: Currency;
+  displayMode: DisplayMode;
   locale: string;
-  mode: 'DEFAULT' | 'ZEN';
 }
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index eb7a084c9..7742bdd69 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -92,10 +92,11 @@ model Property {
 }
 
 model Settings {
-  currency  Currency
-  updatedAt DateTime @updatedAt
-  User      User     @relation(fields: [userId], references: [id])
-  userId    String   @id
+  currency    Currency?
+  displayMode DisplayMode?
+  updatedAt   DateTime     @updatedAt
+  User        User         @relation(fields: [userId], references: [id])
+  userId      String       @id
 }
 
 model User {
@@ -133,6 +134,11 @@ enum DataSource {
   YAHOO
 }
 
+enum DisplayMode {
+  DEFAULT
+  ZEN
+}
+
 enum Provider {
   ANONYMOUS
   GOOGLE