Implementation of Better Error Handling on Login

This commit is contained in:
2023-08-01 01:05:45 -03:00
parent 5069159d50
commit 0b9b648861
13 changed files with 269 additions and 114 deletions

View File

@@ -19,10 +19,16 @@ export class CallbackComponent implements OnInit {
let auth: 'google' | 'github' = p['auth'];
if (auth === 'google') {
this.loginGoogle(p);
} else if (auth === 'github') {
this.loginGithub(p);
switch (auth) {
case "github":
this.authService.loginGithubUser(p)
break;
case "google":
this.authService.loginGoogleUser(p)
break;
default:
console.log(`Unimplemented auth: ${auth}`)
break;
}
this.router.navigate(['/home'])
@@ -30,12 +36,4 @@ export class CallbackComponent implements OnInit {
})
}
private loginGoogle(p: any) {
this.authService.loginGoogleUser(p)
}
private loginGithub(p: any) {
this.authService.loginGithubUser(p)
}
}

View File

@@ -0,0 +1,11 @@
.error-box {
background-color: #ff00001a;
min-width: 250px;
display: flex;
border-radius: 5px;
padding: 25px;
margin: 0 auto;
color: #ff0000;
font-size: 14px;
font-weight: 500;
}

View File

@@ -0,0 +1,3 @@
<div class="error-box" *ngIf="errorMessage">
{{errorMessage}}
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ErrorBoxComponent } from './error-box.component';
describe('ErrorBoxComponent', () => {
let component: ErrorBoxComponent;
let fixture: ComponentFixture<ErrorBoxComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ErrorBoxComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(ErrorBoxComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,14 @@
import {Component, Input} from '@angular/core';
@Component({
selector: 'app-error-box',
templateUrl: './error-box.component.html',
styleUrls: ['./error-box.component.css']
})
export class ErrorBoxComponent {
@Input()
errorMessage: string|null = "Error, please try again later."
constructor() { }
}

View File

@@ -113,6 +113,8 @@
transition: .3s;
}
@media (min-width:767px) {
.authentication-container {

View File

@@ -2,9 +2,16 @@
(stateChange)="onStateChange($event)"
[ignoreClickOutside]="ignoreClickOutside">
{{errorMessage}}
<div class="container overflow-hidden"
[@resizeContainerForErrorMessage]="hideErrorMessage()">
<div class="container authentication-container">
<app-error-box [errorMessage]="errorMessage"
[@showErrorMessage]="showErrorMessage()">
</app-error-box>
<div class="container authentication-container"
[@hideAuthContainer]="hideErrorMessage()"
(@hideAuthContainer.done)="hideAuthContainer($event)">
<div class="row">
<div class="col-lg-6 authentication-body">
<form [formGroup]="loginForm" (ngSubmit)="onLogin()">
@@ -54,4 +61,6 @@
</div>
</div>
</div>
</app-popup>

View File

@@ -3,12 +3,13 @@ import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
import { faLock, faUser } from '@fortawesome/free-solid-svg-icons';
import { Subscription } from 'rxjs';
import {Subscription, timeout} from 'rxjs';
import { AuthService } from 'src/app/shared/auth/auth.service';
import { HttpError } from 'src/app/shared/model/httpError/httpError.model';
import HttpErrorChecker from 'src/app/shared/model/httpError/httpErrorChecker';
import UserChecker from 'src/app/shared/model/user/user.checker';
import { User } from 'src/app/shared/model/user/user.model';
import {animate, animateChild, group, query, state, style, transition, trigger} from "@angular/animations";
const GOOGLE_LOGO_SVG = "assets/img/providers/google.svg";
@@ -17,7 +18,70 @@ const GITHUB_LOGO_SVG = "assets/img/providers/github.svg";
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
styleUrls: ['./login.component.css'],
animations: [
trigger('resizeContainerForErrorMessage', [
state('hide',
style({
height: '100px',
width: '370px',
})
),
transition(
'show => hide',
group([
query(
"@*",
animateChild(),
{ optional: true }
),
animate('1s ease')
])
)
]),
trigger('showErrorMessage', [
state('show',
style({
opacity: 1,
height: '100px',
width: '340px',
})
),
state('hide',
style({
opacity: 0,
height: '0px',
width: '0px',
})
),
transition(
'* => show',
animate(
'500ms ease-in'
)
),
]),
trigger('hideAuthContainer', [
state('hide',
style({
opacity: 0,
})
),
transition(
'show => hide',
group([
query(
"@*",
animateChild(),
{ optional: true }
),
animate(
'250ms ease-out'
)
])
)
]),
]
})
export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
@@ -38,6 +102,8 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
errorMessage!: string | null;
isShowErrorMessage = false;
_userIcon = faUser;
_passwordIcon = faLock;
@@ -89,7 +155,6 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
password: this.loginForm.controls['password'].value
}
this.authService.login(user);
}
onGoogleLogin() {
@@ -114,4 +179,25 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
this.loginForm.reset();
}
public showErrorMessage(): string {
if (this.isShowErrorMessage) {
return "show";
}
return "hide";
}
public hideErrorMessage(): string {
if (!!this.errorMessage) {
return "hide";
}
return "show";
}
hideAuthContainer(event: any) {
if (event.toState === "hide") {
event.element.style.display = "none";
this.isShowErrorMessage = true;
}
}
}

View File

@@ -1,5 +1,5 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {CommonModule, NgOptimizedImage} from '@angular/common';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { HeaderComponent } from './header.component';
@@ -15,6 +15,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { SignupComponent } from './header-popup/signup/signup.component';
import { CallbackComponent } from './header-popup/callback/callback.component';
import {MatIconModule} from '@angular/material/icon';
import { ErrorBoxComponent } from './header-popup/error-box/error-box.component';
@@ -27,7 +28,8 @@ import {MatIconModule} from '@angular/material/icon';
HeaderDropdownComponent,
LoginComponent,
SignupComponent,
CallbackComponent
CallbackComponent,
ErrorBoxComponent,
],
imports: [
CommonModule,
@@ -37,7 +39,8 @@ import {MatIconModule} from '@angular/material/icon';
FormsModule,
ReactiveFormsModule,
MatIconModule,
SharedModule
SharedModule,
NgOptimizedImage
], exports: [
HeaderComponent,
HeaderSliderComponent,

View File

@@ -1,8 +1,8 @@
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { first, firstValueFrom, Observable, Subject, take } from 'rxjs';
import {first, firstValueFrom, Observable, of, Subject, take} from 'rxjs';
import { catchError } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { param } from 'ts-interface-checker';
import { HttpError } from '../model/httpError/httpError.model';
import { Token } from '../model/token/token.model';
import { User } from '../model/user/user.model';
@@ -133,18 +133,6 @@ export class AuthService {
)
}
private validateUser(userAuthAtempt: Observable<User>) {
userAuthAtempt.subscribe({
next: userAuthentication => {
this.userAuthenticated = <User>userAuthentication;
this.authSubject.next(this.userAuthenticated);
},
error: err => {
this.authSubject.next(<HttpError>err.error);
}
});
}
private refreshAccessToken() {
return firstValueFrom(this.http.post(
this.BACKEND_PATH + "/user/login/refresh",
@@ -157,19 +145,37 @@ export class AuthService {
return this.http.get<User>(
this.BACKEND_PATH + '/session/validate',
{ withCredentials: true }
).pipe(
first()
);
}
private destroySessions() {
return this.http.post(
return this.http.delete(
this.BACKEND_PATH + '/session/destroy',
{},
{ withCredentials: true }
).pipe(
take(1)
);
}
private validateUser(userAuthAtempt: Observable<User>) {
userAuthAtempt.pipe(
catchError(error => {
if (error.status == 0) {
return of(<HttpError>{
title: "Service Unavailable",
status: 500,
details: "Service Unavailable, please try again later.",
developerMessage: "Service Unavailable, please try again later.",
timestamp: new Date().toISOString()
});
}
return of(<HttpError>error.error);
}),
first()
).subscribe({
next: userAuthentication => {
this.userAuthenticated = <User>userAuthentication;
this.authSubject.next(this.userAuthenticated);
}
});
}
}

View File

@@ -64,7 +64,7 @@
.popup-body {
height: calc(100% - 120px);
padding: 0px 60px;
padding: 0px 30px;
width: 100%;
margin: 0;
}

View File

@@ -11,7 +11,7 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
declarations: [
ClickedOutsideDirective,
SliderItemComponent,
PopupComponent
PopupComponent,
],
imports: [
CommonModule,