From 0900df463a5e00111a2ca43a5f8af224f4b40ca1 Mon Sep 17 00:00:00 2001 From: Florian THIERRY Date: Wed, 27 Mar 2024 12:15:41 +0100 Subject: [PATCH] Add disconnection and minor improvements on login page. --- frontend/src/app/app.component.scss | 1 + frontend/src/app/app.routes.ts | 14 ++- .../components/header/header.component.html | 13 +- .../components/header/header.component.scss | 25 ++-- .../app/components/header/header.component.ts | 14 ++- .../core/service/authentication.service.ts | 3 +- .../disconnection.component.html | 2 + .../disconnection.component.scss | 6 + .../disconnection/disconnection.component.ts | 21 ++++ .../src/app/pages/home/home.component.html | 2 +- .../src/app/pages/home/home.component.scss | 6 + .../src/app/pages/login/login.component.html | 6 +- .../src/app/pages/login/login.component.scss | 37 ++++++ frontend/src/app/pages/login/login.service.ts | 119 +++++++++--------- 14 files changed, 193 insertions(+), 76 deletions(-) create mode 100644 frontend/src/app/pages/disconnection/disconnection.component.html create mode 100644 frontend/src/app/pages/disconnection/disconnection.component.scss create mode 100644 frontend/src/app/pages/disconnection/disconnection.component.ts diff --git a/frontend/src/app/app.component.scss b/frontend/src/app/app.component.scss index 240b43c..1f9ece3 100644 --- a/frontend/src/app/app.component.scss +++ b/frontend/src/app/app.component.scss @@ -4,5 +4,6 @@ app-header { width: 100%; + margin-bottom: 1em; } } \ No newline at end of file diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index a006cd0..18fa3f8 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -1,6 +1,16 @@ import { Routes } from '@angular/router'; export const routes: Routes = [ - { path: 'login', loadComponent: () => import('./pages/login/login.component').then(module => module.LoginComponent) }, - { path: '**', loadComponent: () => import('./pages/home/home.component').then(module => module.HomeComponent) } + { + path: 'login', + loadComponent: () => import('./pages/login/login.component').then(module => module.LoginComponent) + }, + { + path: 'disconnect', + loadComponent: () => import('./pages/disconnection/disconnection.component').then(module => module.DisconnectionComponent) + }, + { + path: '**', + loadComponent: () => import('./pages/home/home.component').then(module => module.HomeComponent) + } ]; diff --git a/frontend/src/app/components/header/header.component.html b/frontend/src/app/components/header/header.component.html index 8c01a1c..7de179d 100644 --- a/frontend/src/app/components/header/header.component.html +++ b/frontend/src/app/components/header/header.component.html @@ -2,8 +2,10 @@ - logo - Codiki + + logo + Codiki +
@@ -12,5 +14,10 @@
- Login + + Disconnect + + + Login +
\ No newline at end of file diff --git a/frontend/src/app/components/header/header.component.scss b/frontend/src/app/components/header/header.component.scss index 3068014..84db464 100644 --- a/frontend/src/app/components/header/header.component.scss +++ b/frontend/src/app/components/header/header.component.scss @@ -25,14 +25,24 @@ $headerHeight: 3.5em; gap: 1em; padding: 0 1em; - img { - $imageSize: 2em; - width: $imageSize; - height: $imageSize; - } + a { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + color: white; + text-decoration: none; + gap: .5em; - .title { - font-size: 1.5em; + img { + $imageSize: 2em; + width: $imageSize; + height: $imageSize; + } + + .title { + font-size: 1.5em; + } } } @@ -76,6 +86,7 @@ $headerHeight: 3.5em; align-items: center; min-width: 5em; color: white; + margin: 0 .5em; } } } diff --git a/frontend/src/app/components/header/header.component.ts b/frontend/src/app/components/header/header.component.ts index e20662d..3522c24 100644 --- a/frontend/src/app/components/header/header.component.ts +++ b/frontend/src/app/components/header/header.component.ts @@ -1,13 +1,21 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; +import { AuthenticationService } from '../../core/service/authentication.service'; +import { CommonModule } from '@angular/common'; @Component({ selector: 'app-header', standalone: true, - imports: [MatButtonModule, MatIconModule, RouterModule], + imports: [CommonModule, MatButtonModule, MatIconModule, RouterModule], templateUrl: './header.component.html', styleUrl: './header.component.scss', }) -export class HeaderComponent {} +export class HeaderComponent { + private authenticationService = inject(AuthenticationService); + + get isAuthenticated(): boolean { + return this.authenticationService.isAuthenticated(); + } +} diff --git a/frontend/src/app/core/service/authentication.service.ts b/frontend/src/app/core/service/authentication.service.ts index 0aa69b8..117f4d6 100644 --- a/frontend/src/app/core/service/authentication.service.ts +++ b/frontend/src/app/core/service/authentication.service.ts @@ -55,7 +55,8 @@ export class AuthenticationService { const tokenParts = token?.split('.'); if (tokenParts?.length === 3 && tokenParts[1].length) { - const userDetails: UserDetails = JSON.parse(tokenParts[1]); + const decodedTokenPart = atob(tokenParts[1]); + const userDetails: UserDetails = JSON.parse(decodedTokenPart); result = userDetails; } diff --git a/frontend/src/app/pages/disconnection/disconnection.component.html b/frontend/src/app/pages/disconnection/disconnection.component.html new file mode 100644 index 0000000..6d04722 --- /dev/null +++ b/frontend/src/app/pages/disconnection/disconnection.component.html @@ -0,0 +1,2 @@ +

Disconnection...

+ \ No newline at end of file diff --git a/frontend/src/app/pages/disconnection/disconnection.component.scss b/frontend/src/app/pages/disconnection/disconnection.component.scss new file mode 100644 index 0000000..01013a5 --- /dev/null +++ b/frontend/src/app/pages/disconnection/disconnection.component.scss @@ -0,0 +1,6 @@ +:host { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/frontend/src/app/pages/disconnection/disconnection.component.ts b/frontend/src/app/pages/disconnection/disconnection.component.ts new file mode 100644 index 0000000..7ba4d73 --- /dev/null +++ b/frontend/src/app/pages/disconnection/disconnection.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit, inject } from '@angular/core'; +import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; +import { AuthenticationService } from '../../core/service/authentication.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-disconnection', + standalone: true, + imports: [MatProgressSpinnerModule], + templateUrl: './disconnection.component.html', + styleUrl: './disconnection.component.scss' +}) +export class DisconnectionComponent implements OnInit { + private authenticationService = inject(AuthenticationService); + private router = inject(Router); + + ngOnInit(): void { + this.authenticationService.unauthenticate(); + this.router.navigate(['/home']); + } +} diff --git a/frontend/src/app/pages/home/home.component.html b/frontend/src/app/pages/home/home.component.html index 5f2c53f..26e0443 100644 --- a/frontend/src/app/pages/home/home.component.html +++ b/frontend/src/app/pages/home/home.component.html @@ -1 +1 @@ -

home works!

+

Welcome to Codiki application!

diff --git a/frontend/src/app/pages/home/home.component.scss b/frontend/src/app/pages/home/home.component.scss index e69de29..01013a5 100644 --- a/frontend/src/app/pages/home/home.component.scss +++ b/frontend/src/app/pages/home/home.component.scss @@ -0,0 +1,6 @@ +:host { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/frontend/src/app/pages/login/login.component.html b/frontend/src/app/pages/login/login.component.html index d05990b..45e31f5 100644 --- a/frontend/src/app/pages/login/login.component.html +++ b/frontend/src/app/pages/login/login.component.html @@ -4,7 +4,7 @@ - +
- +
+ +
\ No newline at end of file diff --git a/frontend/src/app/pages/login/login.component.scss b/frontend/src/app/pages/login/login.component.scss index e69de29..2592ed7 100644 --- a/frontend/src/app/pages/login/login.component.scss +++ b/frontend/src/app/pages/login/login.component.scss @@ -0,0 +1,37 @@ +:host { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + form { + min-width: 40em; + max-width: 90%; + display: flex; + flex-direction: column; + justify-content: center; + gap: .5em; + + div { + display: flex; + gap: .5em; + + &.actions { + justify-content: end; + + button { + width: 10em; + height: 2em; + } + } + + label { + flex: 1 30%; + } + + input { + flex: 1 70%; + } + } + } +} \ No newline at end of file diff --git a/frontend/src/app/pages/login/login.service.ts b/frontend/src/app/pages/login/login.service.ts index a8c2ec5..84fd89f 100644 --- a/frontend/src/app/pages/login/login.service.ts +++ b/frontend/src/app/pages/login/login.service.ts @@ -1,76 +1,81 @@ -import { Injectable, inject } from "@angular/core"; -import { BehaviorSubject, Observable } from "rxjs"; -import { copy } from "../../core/utils/ObjectUtils"; -import { FormError } from "../../core/model/FormError"; -import { UserRestService } from "../../core/rest-services/user.rest-service"; -import { LoginRequest } from "../../core/rest-services/model/login.model"; -import { AuthenticationService } from "../../core/service/authentication.service"; -import { MatSnackBar } from "@angular/material/snack-bar"; -import { Router } from "@angular/router"; +import { Injectable, inject } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { copy } from '../../core/utils/ObjectUtils'; +import { FormError } from '../../core/model/FormError'; +import { UserRestService } from '../../core/rest-services/user.rest-service'; +import { LoginRequest } from '../../core/rest-services/model/login.model'; +import { AuthenticationService } from '../../core/service/authentication.service'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { Router } from '@angular/router'; export interface LoginState { - request: LoginRequest; - errors: FormError[] + request: LoginRequest; + errors: FormError[]; } const DEFAULT_STATE: LoginState = { - request: { - email: undefined, - password: undefined - }, - errors: [] -} + request: { + email: undefined, + password: undefined, + }, + errors: [], +}; @Injectable() export class LoginService { - private stateSubject = new BehaviorSubject(copy(DEFAULT_STATE)); - private userRestService = inject(UserRestService); - private authenticationService = inject(AuthenticationService); - private snackBar = inject(MatSnackBar); - private router = inject(Router); - - get state$(): Observable { - return this.stateSubject.asObservable(); - } + private stateSubject = new BehaviorSubject(copy(DEFAULT_STATE)); + private userRestService = inject(UserRestService); + private authenticationService = inject(AuthenticationService); + private snackBar = inject(MatSnackBar); + private router = inject(Router); - private get state(): LoginState { - return this.stateSubject.value; - } + get state$(): Observable { + return this.stateSubject.asObservable(); + } - private save(newState: LoginState): void { - this.stateSubject.next(newState); - } + private get state(): LoginState { + return this.stateSubject.value; + } - editEmail(newEmail: string): void { - const state = this.state; + private save(newState: LoginState): void { + this.stateSubject.next(newState); + } - state.request.email = newEmail; + editEmail(newEmail: string): void { + const state = this.state; - this.save(state); - } + state.request.email = newEmail; - editPassword(newPassword: string): void { - const state = this.state; + this.save(state); + } - state.request.password = newPassword; + editPassword(newPassword: string): void { + const state = this.state; - this.save(state); - } + state.request.password = newPassword; - performLogin(): void { - const state = this.state; + this.save(state); + } - // Check state is valid + performLogin(): void { + const state = this.state; - this.userRestService.login(state.request) - .then(response => { - this.authenticationService.authenticate(response.accessToken); - this.snackBar.open('Authentication succeeded!', 'Close', { duration: 5000 }); - this.router.navigate(['/home']); - }) - .catch(error => { - console.error(error) - this.snackBar.open('Authentication failed.', 'Close', { duration: 5000 }); - }); - } -} \ No newline at end of file + // Check state is valid + + this.userRestService + .login(state.request) + .then((response) => { + this.authenticationService.authenticate(response.accessToken); + this.snackBar.open('Authentication succeeded!', 'Close', { + duration: 5000, + }); + this.router.navigate(['/home']); + }) + .catch((error) => { + console.error(error); + this.snackBar.open('Authentication failed.', 'Close', { + duration: 5000, + }); + }); + } +}