Add disconnection and minor improvements on login page.

This commit is contained in:
Florian THIERRY
2024-03-27 12:15:41 +01:00
parent 13c2cc8118
commit 0900df463a
14 changed files with 193 additions and 76 deletions

View File

@@ -4,5 +4,6 @@
app-header { app-header {
width: 100%; width: 100%;
margin-bottom: 1em;
} }
} }

View File

@@ -1,6 +1,16 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
export const routes: Routes = [ 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)
}
]; ];

View File

@@ -2,8 +2,10 @@
<button type="button"> <button type="button">
<mat-icon>menu</mat-icon> <mat-icon>menu</mat-icon>
</button> </button>
<a [routerLink]="['/home']">
<img src="assets/images/codiki.png" alt="logo"/> <img src="assets/images/codiki.png" alt="logo"/>
<span class="title">Codiki</span> <span class="title">Codiki</span>
</a>
</div> </div>
<div> <div>
<input name="search-query" placeholder="Search something..." /> <input name="search-query" placeholder="Search something..." />
@@ -12,5 +14,10 @@
</button> </button>
</div> </div>
<div> <div>
<ng-container *ngIf="isAuthenticated; else anonymousRightMenu">
<a [routerLink]="['/disconnect']">Disconnect</a>
</ng-container>
<ng-template #anonymousRightMenu>
<a [routerLink]="['/login']">Login</a> <a [routerLink]="['/login']">Login</a>
</ng-template>
</div> </div>

View File

@@ -25,6 +25,15 @@ $headerHeight: 3.5em;
gap: 1em; gap: 1em;
padding: 0 1em; padding: 0 1em;
a {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
color: white;
text-decoration: none;
gap: .5em;
img { img {
$imageSize: 2em; $imageSize: 2em;
width: $imageSize; width: $imageSize;
@@ -35,6 +44,7 @@ $headerHeight: 3.5em;
font-size: 1.5em; font-size: 1.5em;
} }
} }
}
&:nth-child(2) { &:nth-child(2) {
flex: 1; flex: 1;
@@ -76,6 +86,7 @@ $headerHeight: 3.5em;
align-items: center; align-items: center;
min-width: 5em; min-width: 5em;
color: white; color: white;
margin: 0 .5em;
} }
} }
} }

View File

@@ -1,13 +1,21 @@
import { Component } from '@angular/core'; import { Component, inject } from '@angular/core';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { AuthenticationService } from '../../core/service/authentication.service';
import { CommonModule } from '@angular/common';
@Component({ @Component({
selector: 'app-header', selector: 'app-header',
standalone: true, standalone: true,
imports: [MatButtonModule, MatIconModule, RouterModule], imports: [CommonModule, MatButtonModule, MatIconModule, RouterModule],
templateUrl: './header.component.html', templateUrl: './header.component.html',
styleUrl: './header.component.scss', styleUrl: './header.component.scss',
}) })
export class HeaderComponent {} export class HeaderComponent {
private authenticationService = inject(AuthenticationService);
get isAuthenticated(): boolean {
return this.authenticationService.isAuthenticated();
}
}

View File

@@ -55,7 +55,8 @@ export class AuthenticationService {
const tokenParts = token?.split('.'); const tokenParts = token?.split('.');
if (tokenParts?.length === 3 && tokenParts[1].length) { 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; result = userDetails;
} }

View File

@@ -0,0 +1,2 @@
<h1>Disconnection...</h1>
<mat-spinner></mat-spinner>

View File

@@ -0,0 +1,6 @@
:host {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

View File

@@ -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']);
}
}

View File

@@ -1 +1 @@
<p>home works!</p> <h1>Welcome to Codiki application!</h1>

View File

@@ -0,0 +1,6 @@
:host {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

View File

@@ -4,7 +4,7 @@
<label for="email"> <label for="email">
Email address Email address
</label> </label>
<input type="email" formControlName="email" required /> <input type="email" formControlName="email" autocomplete="email" required />
</div> </div>
<div> <div>
<label for="password"> <label for="password">
@@ -12,5 +12,7 @@
</label> </label>
<input type="password" formControlName="password" required /> <input type="password" formControlName="password" required />
</div> </div>
<div class="actions">
<button type="submit">Send</button> <button type="submit">Send</button>
</div>
</form> </form>

View File

@@ -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%;
}
}
}
}

View File

@@ -1,25 +1,25 @@
import { Injectable, inject } from "@angular/core"; import { Injectable, inject } from '@angular/core';
import { BehaviorSubject, Observable } from "rxjs"; import { BehaviorSubject, Observable } from 'rxjs';
import { copy } from "../../core/utils/ObjectUtils"; import { copy } from '../../core/utils/ObjectUtils';
import { FormError } from "../../core/model/FormError"; import { FormError } from '../../core/model/FormError';
import { UserRestService } from "../../core/rest-services/user.rest-service"; import { UserRestService } from '../../core/rest-services/user.rest-service';
import { LoginRequest } from "../../core/rest-services/model/login.model"; import { LoginRequest } from '../../core/rest-services/model/login.model';
import { AuthenticationService } from "../../core/service/authentication.service"; import { AuthenticationService } from '../../core/service/authentication.service';
import { MatSnackBar } from "@angular/material/snack-bar"; import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from "@angular/router"; import { Router } from '@angular/router';
export interface LoginState { export interface LoginState {
request: LoginRequest; request: LoginRequest;
errors: FormError[] errors: FormError[];
} }
const DEFAULT_STATE: LoginState = { const DEFAULT_STATE: LoginState = {
request: { request: {
email: undefined, email: undefined,
password: undefined password: undefined,
}, },
errors: [] errors: [],
} };
@Injectable() @Injectable()
export class LoginService { export class LoginService {
@@ -62,15 +62,20 @@ export class LoginService {
// Check state is valid // Check state is valid
this.userRestService.login(state.request) this.userRestService
.then(response => { .login(state.request)
.then((response) => {
this.authenticationService.authenticate(response.accessToken); this.authenticationService.authenticate(response.accessToken);
this.snackBar.open('Authentication succeeded!', 'Close', { duration: 5000 }); this.snackBar.open('Authentication succeeded!', 'Close', {
duration: 5000,
});
this.router.navigate(['/home']); this.router.navigate(['/home']);
}) })
.catch(error => { .catch((error) => {
console.error(error) console.error(error);
this.snackBar.open('Authentication failed.', 'Close', { duration: 5000 }); this.snackBar.open('Authentication failed.', 'Close', {
duration: 5000,
});
}); });
} }
} }