From 5069159d50b5b4db804f17d4ec359a9892f5737f Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 30 Jul 2023 23:55:40 -0300 Subject: [PATCH 1/5] Changing Default Project Settings --- .editorconfig | 2 +- package.json | 3 ++- server.js | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index 59d9a3a..0792692 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,7 +4,7 @@ root = true [*] charset = utf-8 indent_style = space -indent_size = 2 +indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true diff --git a/package.json b/package.json index 399aa20..a6fed37 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "frontend-hideyoshi.com", "version": "0.0.0", "scripts": { - "start": "node ./server.js" + "start": "node ./server.js", + "serve": "ng serve" }, "proxy": { "/callback": { diff --git a/server.js b/server.js index 32f2236..b0389f9 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,5 @@ const express = require('express'); -var cors = require('cors'); +const cors = require('cors'); const path = require('path'); const PKG_NAME = "frontend-hideyoshi.com"; @@ -13,4 +13,4 @@ app.get('/*', (req, res) => { res.sendFile(path.join(`${__dirname}/dist/${PKG_NAME}/index.html`)); }); -app.listen(process.env.PORT || 5000); \ No newline at end of file +app.listen(process.env.PORT || 5000); From 0b9b648861f3d985003f72938c5b199e0741cfef Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 1 Aug 2023 01:05:45 -0300 Subject: [PATCH 2/5] Implementation of Better Error Handling on Login --- .../callback/callback.component.ts | 22 ++-- .../error-box/error-box.component.css | 11 ++ .../error-box/error-box.component.html | 3 + .../error-box/error-box.component.spec.ts | 23 ++++ .../error-box/error-box.component.ts | 14 +++ .../header-popup/login/login.component.css | 6 +- .../header-popup/login/login.component.html | 109 +++++++++-------- .../header-popup/login/login.component.ts | 114 +++++++++++++++--- src/app/header/header.module.ts | 9 +- src/app/shared/auth/auth.service.ts | 58 +++++---- .../components/popup/popup.component.css | 4 +- .../components/popup/popup.component.ts | 6 +- src/app/shared/shared.module.ts | 4 +- 13 files changed, 269 insertions(+), 114 deletions(-) create mode 100644 src/app/header/header-popup/error-box/error-box.component.css create mode 100644 src/app/header/header-popup/error-box/error-box.component.html create mode 100644 src/app/header/header-popup/error-box/error-box.component.spec.ts create mode 100644 src/app/header/header-popup/error-box/error-box.component.ts diff --git a/src/app/header/header-popup/callback/callback.component.ts b/src/app/header/header-popup/callback/callback.component.ts index a7f3d46..9a5db0f 100644 --- a/src/app/header/header-popup/callback/callback.component.ts +++ b/src/app/header/header-popup/callback/callback.component.ts @@ -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) - } - } diff --git a/src/app/header/header-popup/error-box/error-box.component.css b/src/app/header/header-popup/error-box/error-box.component.css new file mode 100644 index 0000000..b15d3ad --- /dev/null +++ b/src/app/header/header-popup/error-box/error-box.component.css @@ -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; +} diff --git a/src/app/header/header-popup/error-box/error-box.component.html b/src/app/header/header-popup/error-box/error-box.component.html new file mode 100644 index 0000000..dc5f057 --- /dev/null +++ b/src/app/header/header-popup/error-box/error-box.component.html @@ -0,0 +1,3 @@ +
+ {{errorMessage}} +
diff --git a/src/app/header/header-popup/error-box/error-box.component.spec.ts b/src/app/header/header-popup/error-box/error-box.component.spec.ts new file mode 100644 index 0000000..638a608 --- /dev/null +++ b/src/app/header/header-popup/error-box/error-box.component.spec.ts @@ -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; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ErrorBoxComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ErrorBoxComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/header/header-popup/error-box/error-box.component.ts b/src/app/header/header-popup/error-box/error-box.component.ts new file mode 100644 index 0000000..89ec9ba --- /dev/null +++ b/src/app/header/header-popup/error-box/error-box.component.ts @@ -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() { } + +} diff --git a/src/app/header/header-popup/login/login.component.css b/src/app/header/header-popup/login/login.component.css index df70563..862da27 100644 --- a/src/app/header/header-popup/login/login.component.css +++ b/src/app/header/header-popup/login/login.component.css @@ -113,12 +113,14 @@ transition: .3s; } + + @media (min-width:767px) { .authentication-container { min-width: 630px; } - + .authentication-body { all: unset; justify-content: space-around; @@ -145,4 +147,4 @@ height: 100%; } -} \ No newline at end of file +} diff --git a/src/app/header/header-popup/login/login.component.html b/src/app/header/header-popup/login/login.component.html index e358d43..2c36c3c 100644 --- a/src/app/header/header-popup/login/login.component.html +++ b/src/app/header/header-popup/login/login.component.html @@ -1,57 +1,66 @@ - - {{errorMessage}} - -
-
-
-
-
- - - -
-
- - - -
- +
+
+
+
+
+
+ - -
-
-
-
-
- - + +
+ -
\ No newline at end of file + diff --git a/src/app/header/header-popup/login/login.component.ts b/src/app/header/header-popup/login/login.component.ts index 115c0e7..96d1373 100644 --- a/src/app/header/header-popup/login/login.component.ts +++ b/src/app/header/header-popup/login/login.component.ts @@ -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,18 +18,81 @@ 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 { @Input() - state: boolean = false; + state: boolean = false; @Input() - ignoreClickOutside!: HTMLDivElement[]; + ignoreClickOutside!: HTMLDivElement[]; @Output() - stateChange = new EventEmitter(); + stateChange = new EventEmitter(); popupState = false; @@ -38,6 +102,8 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { errorMessage!: string | null; + isShowErrorMessage = false; + _userIcon = faUser; _passwordIcon = faLock; @@ -47,14 +113,14 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { private changeDetectorRef: ChangeDetectorRef, private matIconRegistry: MatIconRegistry, private domSanitizer: DomSanitizer) { - this.matIconRegistry.addSvgIcon( - "google-logo", - this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG) - ); - this.matIconRegistry.addSvgIcon( - "github-logo", - this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG) - ); + this.matIconRegistry.addSvgIcon( + "google-logo", + this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG) + ); + this.matIconRegistry.addSvgIcon( + "github-logo", + this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG) + ); } ngOnInit(): void { @@ -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; + } + } + } diff --git a/src/app/header/header.module.ts b/src/app/header/header.module.ts index 5eeaa7b..28e1946 100644 --- a/src/app/header/header.module.ts +++ b/src/app/header/header.module.ts @@ -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, diff --git a/src/app/shared/auth/auth.service.ts b/src/app/shared/auth/auth.service.ts index 1d050bb..178d534 100644 --- a/src/app/shared/auth/auth.service.ts +++ b/src/app/shared/auth/auth.service.ts @@ -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'; @@ -25,11 +25,11 @@ export class AuthService { login(userAuthAtempt: User): void { this.validateUser(this.loginUser(userAuthAtempt)); } - + googleLogin() { window.open(this.BACKEND_OAUTH_PATH + '/oauth2/authorization/google', '_self'); } - + githubLogin() { window.open(this.BACKEND_OAUTH_PATH + '/oauth2/authorization/github', '_self'); } @@ -95,9 +95,9 @@ export class AuthService { return this.http.get( this.BACKEND_OAUTH_PATH + '/login/oauth2/code/google', - { + { withCredentials: true, - params: params + params: params }, ).pipe( first() @@ -114,9 +114,9 @@ export class AuthService { return this.http.get( this.BACKEND_OAUTH_PATH + '/login/oauth2/code/github', - { + { withCredentials: true, - params: params + params: params }, ).pipe( first() @@ -133,18 +133,6 @@ export class AuthService { ) } - private validateUser(userAuthAtempt: Observable) { - userAuthAtempt.subscribe({ - next: userAuthentication => { - this.userAuthenticated = userAuthentication; - this.authSubject.next(this.userAuthenticated); - }, - error: err => { - this.authSubject.next(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( 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) { + userAuthAtempt.pipe( + catchError(error => { + if (error.status == 0) { + return of({ + 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(error.error); + }), + first() + ).subscribe({ + next: userAuthentication => { + this.userAuthenticated = userAuthentication; + this.authSubject.next(this.userAuthenticated); + } + }); + } + } diff --git a/src/app/shared/components/popup/popup.component.css b/src/app/shared/components/popup/popup.component.css index b502aa0..9f1c197 100644 --- a/src/app/shared/components/popup/popup.component.css +++ b/src/app/shared/components/popup/popup.component.css @@ -64,7 +64,7 @@ .popup-body { height: calc(100% - 120px); - padding: 0px 60px; + padding: 0px 30px; width: 100%; margin: 0; } @@ -80,4 +80,4 @@ max-width: unset; } -} \ No newline at end of file +} diff --git a/src/app/shared/components/popup/popup.component.ts b/src/app/shared/components/popup/popup.component.ts index 9e8f6da..24502f2 100644 --- a/src/app/shared/components/popup/popup.component.ts +++ b/src/app/shared/components/popup/popup.component.ts @@ -17,7 +17,7 @@ import { Component, ElementRef, EventEmitter, Input, Output, ViewEncapsulation } '* => show', group([ query( - "@*", + "@*", animateChild(), { optional: true } ), @@ -28,7 +28,7 @@ import { Component, ElementRef, EventEmitter, Input, Output, ViewEncapsulation } 'show => hide', group([ query( - "@*", + "@*", animateChild(), { optional: true } ), @@ -51,7 +51,7 @@ export class PopupComponent { constructor() { } - get popupState(): string { + get popupState(): string { return this.state ? 'show' : 'hide'; } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 29f158a..eb36fc2 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -8,10 +8,10 @@ import { PopupComponent } from './components/popup/popup.component'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; @NgModule({ - declarations: [ + declarations: [ ClickedOutsideDirective, SliderItemComponent, - PopupComponent + PopupComponent, ], imports: [ CommonModule, From 3cca992db35f7f5184451fe7b50e280d4d3d8b24 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 1 Aug 2023 01:22:48 -0300 Subject: [PATCH 3/5] Implementation of Better Error Handling on SignUp --- .../error-box/error-box.component.css | 2 +- .../header-popup/login/login.component.html | 4 +- .../header-popup/login/login.component.ts | 11 +- .../header-popup/signup/signup.component.html | 144 ++++++++++-------- .../header-popup/signup/signup.component.ts | 113 ++++++++++++-- 5 files changed, 185 insertions(+), 89 deletions(-) diff --git a/src/app/header/header-popup/error-box/error-box.component.css b/src/app/header/header-popup/error-box/error-box.component.css index b15d3ad..81388fe 100644 --- a/src/app/header/header-popup/error-box/error-box.component.css +++ b/src/app/header/header-popup/error-box/error-box.component.css @@ -1,6 +1,6 @@ .error-box { background-color: #ff00001a; - min-width: 250px; + max-width: 320px; display: flex; border-radius: 5px; padding: 25px; diff --git a/src/app/header/header-popup/login/login.component.html b/src/app/header/header-popup/login/login.component.html index 2c36c3c..9e73db8 100644 --- a/src/app/header/header-popup/login/login.component.html +++ b/src/app/header/header-popup/login/login.component.html @@ -1,8 +1,8 @@ - -
(); - popupState = false; - loginForm!: FormGroup; authSubject!: Subscription; @@ -137,7 +135,6 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { } ngAfterViewInit(): void { - this.popupState = this.state; this.changeDetectorRef.detectChanges(); } @@ -175,7 +172,7 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { } private closePopup() { - this.popupState = false; + this.state = false; this.loginForm.reset(); } diff --git a/src/app/header/header-popup/signup/signup.component.html b/src/app/header/header-popup/signup/signup.component.html index 3f85c98..af12a22 100644 --- a/src/app/header/header-popup/signup/signup.component.html +++ b/src/app/header/header-popup/signup/signup.component.html @@ -1,73 +1,85 @@ - {{errorMessage}} -
-
-
-
-
- - - -
-
- - - -
-
- - - -
-
- - - -
- +
+
+
+
+
+ -
-
-
-
+
-
\ No newline at end of file + diff --git a/src/app/header/header-popup/signup/signup.component.ts b/src/app/header/header-popup/signup/signup.component.ts index d62c76c..838307c 100644 --- a/src/app/header/header-popup/signup/signup.component.ts +++ b/src/app/header/header-popup/signup/signup.component.ts @@ -9,6 +9,7 @@ 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,18 +18,81 @@ const GITHUB_LOGO_SVG = "assets/img/providers/github.svg"; @Component({ selector: 'app-signup', templateUrl: './signup.component.html', - styleUrls: ['./signup.component.css'] + styleUrls: ['./signup.component.css'], + animations: [ + trigger('resizeContainerForErrorMessage', [ + state('hide', + style({ + height: '100px', + width: '320px', + }) + ), + transition( + 'show => hide', + group([ + query( + "@*", + animateChild(), + { optional: true } + ), + animate('1s ease') + ]) + ) + ]), + trigger('showErrorMessage', [ + state('show', + style({ + opacity: 1, + height: '100px', + width: '320px', + }) + ), + 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 SignupComponent implements OnInit { @Input() - state: boolean = false; + state: boolean = false; @Input() - ignoreClickOutside!: HTMLDivElement[]; + ignoreClickOutside!: HTMLDivElement[]; @Output() - stateChange = new EventEmitter(); + stateChange = new EventEmitter(); signupForm!: FormGroup; @@ -36,6 +100,8 @@ export class SignupComponent implements OnInit { errorMessage!: string | null; + isShowErrorMessage = false; + _fullnameIcon = faFingerprint; _emailIcon = faEnvelope; @@ -47,15 +113,15 @@ export class SignupComponent implements OnInit { constructor(private authService: AuthService, private matIconRegistry: MatIconRegistry, private domSanitizer: DomSanitizer) { - this.matIconRegistry.addSvgIcon( - "google-logo", - this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG) - ); - this.matIconRegistry.addSvgIcon( - "github-logo", - this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG) - ); - } + this.matIconRegistry.addSvgIcon( + "google-logo", + this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG) + ); + this.matIconRegistry.addSvgIcon( + "github-logo", + this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG) + ); + } ngOnInit(): void { this.signupForm = new FormGroup({ @@ -110,5 +176,26 @@ export class SignupComponent implements OnInit { this.signupForm.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; + } + } + } From c60215d7d297a1ec4d5a534b4a6853c38ec47480 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 1 Aug 2023 20:05:04 -0300 Subject: [PATCH 4/5] Better Animations in the Auth Popus --- .../header/header-popup/login/login.component.css | 12 ++++++++---- .../header-popup/signup/signup.component.css | 14 +++++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/app/header/header-popup/login/login.component.css b/src/app/header/header-popup/login/login.component.css index 862da27..428fbf7 100644 --- a/src/app/header/header-popup/login/login.component.css +++ b/src/app/header/header-popup/login/login.component.css @@ -78,13 +78,14 @@ background-color: #f44336; border-radius: 15px; } -.input-div:hover:after { +.input-div:hover:after, +.input-div:has(input.form-control:focus):after { width: 100%; transition: .4s; overflow: hidden; } -.input-div > .form-control, .input-div > .form-control:focus { +.input-div > .form-control, .input-div > .form-control:hover{ border: none; border-color: inherit; -webkit-box-shadow: none; @@ -103,12 +104,15 @@ -webkit-box-shadow: 0 0 0px 1000px #ffffff inset; } +.input-div:has(input.form-control:focus) > .form-control::placeholder, .input-div:hover > .form-control::placeholder, -.input-div:hover > .input-div-icon { +.input-div:hover > .input-div-icon, +.input-div:has(input.form-control:focus) > .input-div-icon { color: #D8291C; transition: .3s; } -.input-div:hover > .form-control::placeholder { +.input-div:hover > .form-control::placeholder, +.input-div:has(input.form-control:focus) > .form-control::placeholder { font-weight: 500; transition: .3s; } diff --git a/src/app/header/header-popup/signup/signup.component.css b/src/app/header/header-popup/signup/signup.component.css index 22f2864..a109cf3 100644 --- a/src/app/header/header-popup/signup/signup.component.css +++ b/src/app/header/header-popup/signup/signup.component.css @@ -81,7 +81,8 @@ background-color: #f44336; border-radius: 15px; } -.input-div:hover:after { +.input-div:hover:after, +.input-div:has(input.form-control:focus):after { width: 100%; transition: .4s; overflow: hidden; @@ -106,12 +107,15 @@ -webkit-box-shadow: 0 0 0px 1000px #ffffff inset; } +.input-div:has(input.form-control:focus) > .form-control::placeholder, .input-div:hover > .form-control::placeholder, -.input-div:hover > .input-div-icon { +.input-div:hover > .input-div-icon, +.input-div:has(input.form-control:focus) > .input-div-icon { color: #D8291C; transition: .3s; } -.input-div:hover > .form-control::placeholder { +.input-div:hover > .form-control::placeholder, +.input-div:focus > .form-control::placeholder{ font-weight: 500; transition: .3s; } @@ -121,7 +125,7 @@ .authentication-container { min-width: 630px; } - + .auth-body { all: unset; justify-content: space-around; @@ -148,4 +152,4 @@ height: 100%; } -} \ No newline at end of file +} From fea0b063b64dd1bcdb1bf9d2ec7d790d495ea96d Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 2 Aug 2023 00:08:58 -0300 Subject: [PATCH 5/5] Better Validations --- .../header/header-popup/login/login.component.html | 1 + src/app/header/header-popup/login/login.component.ts | 6 ++++-- .../header/header-popup/signup/signup.component.html | 1 + .../header/header-popup/signup/signup.component.ts | 11 +++++++---- src/app/shared/validators/validate-email.validator.ts | 11 +++++++++++ .../shared/validators/validate-not-empty.validator.ts | 9 +++++++++ .../shared/validators/validate-password.validator.ts | 11 +++++++++++ 7 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 src/app/shared/validators/validate-email.validator.ts create mode 100644 src/app/shared/validators/validate-not-empty.validator.ts create mode 100644 src/app/shared/validators/validate-password.validator.ts diff --git a/src/app/header/header-popup/login/login.component.html b/src/app/header/header-popup/login/login.component.html index 9e73db8..b954c83 100644 --- a/src/app/header/header-popup/login/login.component.html +++ b/src/app/header/header-popup/login/login.component.html @@ -34,6 +34,7 @@ placeholder="Password"> diff --git a/src/app/header/header-popup/login/login.component.ts b/src/app/header/header-popup/login/login.component.ts index c6d10f2..8b08965 100644 --- a/src/app/header/header-popup/login/login.component.ts +++ b/src/app/header/header-popup/login/login.component.ts @@ -10,6 +10,8 @@ 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"; +import {ValidatePasswordValidator} from "../../../shared/validators/validate-password.validator"; +import {ValidateNotEmptyValidator} from "../../../shared/validators/validate-not-empty.validator"; const GOOGLE_LOGO_SVG = "assets/img/providers/google.svg"; @@ -123,8 +125,8 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { ngOnInit(): void { this.loginForm = new FormGroup({ - 'username': new FormControl(null, [Validators.required]), - 'password': new FormControl(null, [Validators.required]) + 'username': new FormControl(null, [Validators.required, ValidateNotEmptyValidator]), + 'password': new FormControl(null, [Validators.required, ValidatePasswordValidator]) }); this.errorMessage = null; this.authSubject = this.authService.authSubject.subscribe( diff --git a/src/app/header/header-popup/signup/signup.component.html b/src/app/header/header-popup/signup/signup.component.html index af12a22..947eba8 100644 --- a/src/app/header/header-popup/signup/signup.component.html +++ b/src/app/header/header-popup/signup/signup.component.html @@ -53,6 +53,7 @@ placeholder="Password"> diff --git a/src/app/header/header-popup/signup/signup.component.ts b/src/app/header/header-popup/signup/signup.component.ts index 838307c..3b7bf25 100644 --- a/src/app/header/header-popup/signup/signup.component.ts +++ b/src/app/header/header-popup/signup/signup.component.ts @@ -10,6 +10,9 @@ 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"; +import {ValidateEmailValidator} from "../../../shared/validators/validate-email.validator"; +import {ValidatePasswordValidator} from "../../../shared/validators/validate-password.validator"; +import {ValidateNotEmptyValidator} from "../../../shared/validators/validate-not-empty.validator"; const GOOGLE_LOGO_SVG = "assets/img/providers/google.svg"; @@ -125,12 +128,12 @@ export class SignupComponent implements OnInit { ngOnInit(): void { this.signupForm = new FormGroup({ - 'fullname': new FormControl(null, [Validators.required]), + 'fullname': new FormControl(null, [Validators.required, ValidateNotEmptyValidator]), // Create a Email Validator - 'email': new FormControl(null, [Validators.required]), - 'username': new FormControl(null, [Validators.required]), + 'email': new FormControl(null, [Validators.required, ValidateEmailValidator]), + 'username': new FormControl(null, [Validators.required, ValidateNotEmptyValidator]), // Create a Password Validator - 'password': new FormControl(null, [Validators.required]) + 'password': new FormControl(null, [Validators.required, ValidatePasswordValidator]) }); this.errorMessage = null; this.authSubject = this.authService.authSubject.subscribe( diff --git a/src/app/shared/validators/validate-email.validator.ts b/src/app/shared/validators/validate-email.validator.ts new file mode 100644 index 0000000..f30c4e3 --- /dev/null +++ b/src/app/shared/validators/validate-email.validator.ts @@ -0,0 +1,11 @@ +import {AbstractControl} from "@angular/forms"; + +export function ValidateEmailValidator(control: AbstractControl) { + const email = control.value; + const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g; + const emailValid = emailRegex.test(email); + if (!emailValid) { + return { invalidEmail: true }; + } + return null; +} diff --git a/src/app/shared/validators/validate-not-empty.validator.ts b/src/app/shared/validators/validate-not-empty.validator.ts new file mode 100644 index 0000000..be790a1 --- /dev/null +++ b/src/app/shared/validators/validate-not-empty.validator.ts @@ -0,0 +1,9 @@ +import {AbstractControl} from "@angular/forms"; + +export function ValidateNotEmptyValidator(control: AbstractControl) { + const value = control.value; + if (!value || value.length === 0) { + return { invalidNotEmpty: true }; + } + return null; +} diff --git a/src/app/shared/validators/validate-password.validator.ts b/src/app/shared/validators/validate-password.validator.ts new file mode 100644 index 0000000..71817d0 --- /dev/null +++ b/src/app/shared/validators/validate-password.validator.ts @@ -0,0 +1,11 @@ +import {AbstractControl} from "@angular/forms"; + +export function ValidatePasswordValidator(control: AbstractControl) { + var password = control.value; + var passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/g; + var passwordValid = passwordRegex.test(password); + if (!passwordValid) { + return { invalidPassword: true }; + } + return null; +}