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); 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..81388fe --- /dev/null +++ b/src/app/header/header-popup/error-box/error-box.component.css @@ -0,0 +1,11 @@ +.error-box { + background-color: #ff00001a; + max-width: 320px; + 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..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,22 +104,27 @@ -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; } + + @media (min-width:767px) { .authentication-container { min-width: 630px; } - + .authentication-body { all: unset; justify-content: space-around; @@ -145,4 +151,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..b954c83 100644 --- a/src/app/header/header-popup/login/login.component.html +++ b/src/app/header/header-popup/login/login.component.html @@ -1,57 +1,67 @@ - - {{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..8b08965 100644 --- a/src/app/header/header-popup/login/login.component.ts +++ b/src/app/header/header-popup/login/login.component.ts @@ -3,12 +3,15 @@ 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} 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"; +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"; @@ -17,20 +20,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: '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 LoginComponent implements OnInit, AfterViewInit, OnDestroy { @Input() - state: boolean = false; + state: boolean = false; @Input() - ignoreClickOutside!: HTMLDivElement[]; + ignoreClickOutside!: HTMLDivElement[]; @Output() - stateChange = new EventEmitter(); - - popupState = false; + stateChange = new EventEmitter(); loginForm!: FormGroup; @@ -38,6 +102,8 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { errorMessage!: string | null; + isShowErrorMessage = false; + _userIcon = faUser; _passwordIcon = faLock; @@ -47,20 +113,20 @@ 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 { 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( @@ -71,7 +137,6 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { } ngAfterViewInit(): void { - this.popupState = this.state; this.changeDetectorRef.detectChanges(); } @@ -89,7 +154,6 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { password: this.loginForm.controls['password'].value } this.authService.login(user); - } onGoogleLogin() { @@ -110,8 +174,29 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { } private closePopup() { - this.popupState = false; + this.state = false; 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-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 +} diff --git a/src/app/header/header-popup/signup/signup.component.html b/src/app/header/header-popup/signup/signup.component.html index 3f85c98..947eba8 100644 --- a/src/app/header/header-popup/signup/signup.component.html +++ b/src/app/header/header-popup/signup/signup.component.html @@ -1,73 +1,86 @@ - {{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..3b7bf25 100644 --- a/src/app/header/header-popup/signup/signup.component.ts +++ b/src/app/header/header-popup/signup/signup.component.ts @@ -9,6 +9,10 @@ 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"; +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"; @@ -17,18 +21,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 +103,8 @@ export class SignupComponent implements OnInit { errorMessage!: string | null; + isShowErrorMessage = false; + _fullnameIcon = faFingerprint; _emailIcon = faEnvelope; @@ -47,24 +116,24 @@ 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({ - '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( @@ -110,5 +179,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; + } + } + } 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, 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; +}