diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 52a961c6c2cfcf8ae3d4aa8822cf753cdd1ae978..cc9f16a2572084418e8084ac0e8436e89d5fe78e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -5,6 +5,7 @@ import { HomeComponent } from './home/home.component'; import { LegalNoticeComponent } from './legal-notice/legal-notice.component'; import { StructureDetailsComponent } from './structure-list/components/structure-details/structure-details.component'; import { StructureListComponent } from './structure-list/structure-list.component'; +import { UserVerificationComponent } from './user-verification/user-verification.component'; const routes: Routes = [ { path: 'print', outlet: 'print', children: [{ path: 'structure', component: StructureDetailsComponent }] }, @@ -36,6 +37,10 @@ const routes: Routes = [ path: 'about', component: AboutComponent, }, + { + path: 'users/verify/:id', + component: UserVerificationComponent, + }, { path: '**', redirectTo: 'home', diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e951cd68bad1da63920dfe5dc9a205f29dae98db..02649bb0928321b50390bc3aee7092d6fcdc9ef5 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -22,6 +22,7 @@ import { ModalFilterComponent } from './structure-list/components/modal-filter/m import { LegalNoticeComponent } from './legal-notice/legal-notice.component'; import { AboutComponent } from './about/about.component'; import { MenuPhoneComponent } from './menu-phone/menu-phone.component'; +import { UserVerificationComponent } from './user-verification/user-verification.component'; @NgModule({ declarations: [ @@ -38,6 +39,7 @@ import { MenuPhoneComponent } from './menu-phone/menu-phone.component'; LegalNoticeComponent, AboutComponent, MenuPhoneComponent, + UserVerificationComponent, ], imports: [ BrowserModule, diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index 3e9740a8e9c152ce124d430a5e4e279c937e8064..f933d061ed0f48e2f2dee5818a41825b88a6139a 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -13,12 +13,16 @@ <!-- <a routerLink="/resources" [routerLinkActive]="'active'" i18n>Ressources</a> <a routerLink="/projects" [routerLinkActive]="'active'" i18n>Projets</a> --> <a routerLink="/about" [routerLinkActive]="'active'" i18n>Qui sommes-nous ?</a> - <!--<a routerLink="/login" [routerLinkActive]="'active'" i18n><span class="clickable ico-mglass purple"></span></a> - <a routerLink="/home" [routerLinkActive]="'active'" i18n - ><span class="ico-profile" fxLayout="column" fxLayoutAlign="center center"> + <!-- <a routerLink="/login" [routerLinkActive]="'active'" i18n><span class="clickable ico-mglass purple"></span></a> --> + <button *ngIf="!isLoggedIn" (click)="isPopUpOpen = !isPopUpOpen"> + <span class="ico-profile" fxLayout="column" fxLayoutAlign="center center"> <span class="head"></span> - <span class="body"></span> </span - ></a> --> + <span class="body"></span> + </span> + </button> + <button *ngIf="isLoggedIn" (click)="logout()">Logout</button> </div> </div> <app-menu-phone *ngIf="showMenu" (closeEvent)="closeMenu($event)"></app-menu-phone> +<app-signup-modal *ngIf="displaySignUp" [openned]="isPopUpOpen" (closed)="closeSignUpModal($event)"></app-signup-modal> +<app-signin-modal *ngIf="!displaySignUp" [openned]="isPopUpOpen" (closed)="closeSignInModal()"></app-signin-modal> diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts index b645bfde04ca991c8c78fe03cd11123e4b27f1f0..cceb2dcf1e1f88b8b13e5a77ede032bff0ebd31f 100644 --- a/src/app/header/header.component.ts +++ b/src/app/header/header.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { AuthService } from '../services/auth.service'; @Component({ selector: 'app-header', @@ -7,8 +8,10 @@ import { Component, OnInit } from '@angular/core'; }) export class HeaderComponent implements OnInit { public showMenu = false; + public isPopUpOpen = false; + public displaySignUp = true; - constructor() {} + constructor(private authService: AuthService) {} ngOnInit(): void {} public openMenu(): void { @@ -17,4 +20,25 @@ export class HeaderComponent implements OnInit { public closeMenu(): void { this.showMenu = false; } + + public get isLoggedIn(): boolean { + return this.authService.isLoggedIn(); + } + + public logout(): void { + this.authService.logout(); + } + + public closeSignInModal(): void { + this.isPopUpOpen = false; + this.displaySignUp = true; + } + + public closeSignUpModal(value: boolean): void { + if (!value) { + this.displaySignUp = false; + } else { + this.isPopUpOpen = false; + } + } } diff --git a/src/app/models/user-auth.model.ts b/src/app/models/user-auth.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..0c3ddbf83cffa09a4cd9eaa18e714166d55fbc0e --- /dev/null +++ b/src/app/models/user-auth.model.ts @@ -0,0 +1,5 @@ +export class UserAuth { + username: string; + accessToken: string; + expiresAt: string; +} diff --git a/src/app/models/user.model.ts b/src/app/models/user.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..3269aeea7176afe91ee0e1eab134afc46aae0762 --- /dev/null +++ b/src/app/models/user.model.ts @@ -0,0 +1,8 @@ +export class User { + _id: string; + email: string; + password: string; + emailVerified: boolean; + role: number; + validationToken: string; +} diff --git a/src/app/services/auth.service.spec.ts b/src/app/services/auth.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1251cacf9dda3b3bd9dc5222583fafa110ffe8f --- /dev/null +++ b/src/app/services/auth.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AuthService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..9834bd0077a68ea597950c43c3f40ba3dc93d3b2 --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,66 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { DateTime } from 'luxon'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { UserAuth } from '../models/user-auth.model'; +import { User } from '../models/user.model'; +@Injectable({ + providedIn: 'root', +}) +export class AuthService { + private userSubject: BehaviorSubject<UserAuth>; + public user: Observable<UserAuth>; + + constructor(private http: HttpClient) { + this.userSubject = new BehaviorSubject<UserAuth>(JSON.parse(localStorage.getItem('user'))); + this.user = this.userSubject.asObservable(); + } + + public get userValue(): UserAuth { + return this.userSubject.value; + } + + public get token(): string { + return this.userSubject.value.accessToken; + } + + public logout(): void { + localStorage.removeItem('user'); + this.userSubject.next(null); + } + + public isLoggedIn(): boolean { + if (this.userValue) { + return new DateTime.local().setZone('Europe/Paris') < this.getExpiration(); + } + return false; + } + + private getExpiration(): DateTime { + return DateTime.fromISO(this.userValue.expiresAt, { zone: 'Europe/Paris' }); + } + + public register(user: User): Observable<any> { + return this.http.post('api/users', user); + } + + public login(email: string, password: string): Observable<any> { + return this.http + .post<UserAuth>('api/auth/login', { email, password }) + .pipe( + map((user) => { + // store user details and jwt token in local storage to keep user logged in between page refreshes + localStorage.setItem('user', JSON.stringify(user)); + this.userSubject.next(user); + return user; + }) + ); + } + + public verifyUser(userId: string, token: string): Observable<any> { + return this.http.post(`api/users/verify/${userId}`, null, { + params: { token }, + }); + } +} diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts index c16e176b8a231207116e9055974735f25a43259e..4c62240127832104c3ac9dee13a1364a9386f9f3 100644 --- a/src/app/shared/components/index.ts +++ b/src/app/shared/components/index.ts @@ -1,10 +1,26 @@ import { ButtonComponent } from './button/button.component'; import { LogoCardComponent } from './logo-card/logo-card.component'; import { ModalComponent } from './modal/modal.component'; +import { SignUpModalComponent } from './signup-modal/signup-modal.component'; +import { SignInModalComponent } from './signin-modal/signin-modal.component'; import { SvgIconComponent } from './svg-icon/svg-icon.component'; // tslint:disable-next-line: max-line-length -export { LogoCardComponent, SvgIconComponent, ModalComponent, ButtonComponent }; +export { + LogoCardComponent, + SvgIconComponent, + ModalComponent, + ButtonComponent, + SignUpModalComponent, + SignInModalComponent, +}; // tslint:disable-next-line:variable-name -export const SharedComponents = [LogoCardComponent, SvgIconComponent, ModalComponent, ButtonComponent]; +export const SharedComponents = [ + LogoCardComponent, + SvgIconComponent, + ModalComponent, + SignUpModalComponent, + SignInModalComponent, + ButtonComponent, +]; diff --git a/src/app/shared/components/signin-modal/signin-modal.component.html b/src/app/shared/components/signin-modal/signin-modal.component.html new file mode 100644 index 0000000000000000000000000000000000000000..4d0b4c3de3b555027d9551d422bd871fd372abda --- /dev/null +++ b/src/app/shared/components/signin-modal/signin-modal.component.html @@ -0,0 +1,70 @@ +<div class="cModal" [ngClass]="openned ? 'oModal' : ''"> + <div (clickOutside)="closeModal()" fxLayout="column" fxLayoutAlign="center center"> + <div class="ico-close-wrapper"> + <div (click)="closeModal()" class="ico-close-details"></div> + </div> + <h4>Inscription</h4> + <div *ngIf="!success"> + <form [formGroup]="form" (ngSubmit)="onSubmit()"> + <div class="form-group"> + <label for="email">Email</label> + <input + type="email" + autocomplete="on" + formControlName="email" + class="form-control" + [ngClass]="{ 'is-invalid': submitted && f.email.errors }" + /> + <div *ngIf="submitted && f.email.errors" class="invalid-feedback"> + <div *ngIf="f.email.errors.required">Email is required</div> + </div> + </div> + <div class="form-group"> + <label for="password">Mot de passe</label> + <input + type="password" + autocomplete="on" + formControlName="password" + class="form-control" + [ngClass]="{ 'is-invalid': submitted && f.password.errors }" + /> + <div *ngIf="submitted && f.password.errors" class="invalid-feedback"> + <div *ngIf="f.password.errors.required">Le mot de passe est obligatoire</div> + <div *ngIf="f.password.errors.pattern"> + Le mot de passe doit avoir au minimun 8 caractères, une majuscule, une minuscule, un chiffre et un + caractère spécial. + </div> + </div> + </div> + <div class="form-group"> + <label for="confirmPassword">Confirmation du mot de passe</label> + <input + type="password" + autocomplete="on" + formControlName="confirmPassword" + class="form-control" + [ngClass]="{ 'is-invalid': submitted && f.confirmPassword.errors }" + /> + <div *ngIf="submitted && f.confirmPassword.errors" class="invalid-feedback"> + <div *ngIf="f.confirmPassword.errors.required">La confirmation du mot de passe est obligatoire</div> + <div *ngIf="f.confirmPassword.errors.mustMatch">Les mot de passe ne sont pas les mêmes</div> + </div> + </div> + + <div *ngIf="userAlreadyExist">Cette addresse mail est déjà utilisée</div> + <div class="form-group"> + <button [disabled]="loading" class="btn btn-primary"> + <span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span> + Register + </button> + <a routerLink="../login" class="btn btn-link">Cancel</a> + </div> + </form> + </div> + <div *ngIf="success"> + <p> + Un mail de confirmation vous a été envoyé. Merci de valider votre addresse mail avant de pouvoir vous connecter. + </p> + </div> + </div> +</div> diff --git a/src/app/shared/components/signin-modal/signin-modal.component.scss b/src/app/shared/components/signin-modal/signin-modal.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..b65234fb3d7035bd0a406e3d617d3d3a3bd107e9 --- /dev/null +++ b/src/app/shared/components/signin-modal/signin-modal.component.scss @@ -0,0 +1,40 @@ +@import '../../../../assets/scss/typography'; +@import '../../../../assets/scss/color'; +@import '../../../../assets/scss/buttons'; +@import '../../../../assets/scss/z-index'; +@import '../../../../assets/scss/hyperlink'; +.cModal { + position: fixed; + z-index: $modal-add-structure-z-index; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: $modal-background; + display: none; +} +.cModal > div { + max-width: 450px; + position: relative; + top: 40%; + left: 50%; + transform: translate(-50%, -50%); + background: $white; + text-align: center; + border-radius: 4px; + @include cn-bold-16; + p { + padding: 30px 48px 0 40px; + } +} +.oModal { + display: block; +} +.ico-close-wrapper { + position: absolute; + top: -10px; + right: -10px; +} +.ico-close-details { + opacity: 1; +} diff --git a/src/app/shared/components/signin-modal/signin-modal.component.spec.ts b/src/app/shared/components/signin-modal/signin-modal.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d7bcc9ccbd538ed5f68655312242b1093cdac0ae --- /dev/null +++ b/src/app/shared/components/signin-modal/signin-modal.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SignInModalComponent } from './signin-modal.component'; + +describe('SignInModalComponent', () => { + let component: SignInModalComponent; + let fixture: ComponentFixture<SignInModalComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [SignInModalComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SignInModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/signin-modal/signin-modal.component.ts b/src/app/shared/components/signin-modal/signin-modal.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..253ec9136e9f410ff1a85d862d04430024d42ae8 --- /dev/null +++ b/src/app/shared/components/signin-modal/signin-modal.component.ts @@ -0,0 +1,71 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms'; +import { first } from 'rxjs/operators'; +import { AuthService } from '../../../services/auth.service'; +import { MustMatch } from '../../validator/form'; + +@Component({ + selector: 'app-signin-modal', + templateUrl: './signin-modal.component.html', + styleUrls: ['./signin-modal.component.scss'], +}) +export class SignInModalComponent implements OnInit { + public form: FormGroup; + public loading = false; + public submitted = false; + public success = false; + public userAlreadyExist = false; + + constructor(private formBuilder: FormBuilder, private authService: AuthService) {} + + @Input() public openned: boolean; + @Output() closed = new EventEmitter(); + + ngOnInit(): void { + this.form = this.formBuilder.group( + { + email: ['', Validators.required], + password: [ + '', + [Validators.required, Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/)], + ], + confirmPassword: [''], + }, + { validator: MustMatch('password', 'confirmPassword') } + ); + } + + // getter for form fields + get f(): { [key: string]: AbstractControl } { + return this.form.controls; + } + + public closeModal(): void { + this.closed.emit(); + } + + public onSubmit(): void { + this.submitted = true; + + // stop here if form is invalid + if (this.form.invalid) { + return; + } + + this.loading = true; + this.authService + .register(this.form.value) + .pipe(first()) + .subscribe( + () => { + this.success = true; + }, + (error) => { + this.loading = false; + if (error.error.statusCode === 400) { + this.userAlreadyExist = true; + } + } + ); + } +} diff --git a/src/app/shared/components/signup-modal/signup-modal.component.html b/src/app/shared/components/signup-modal/signup-modal.component.html new file mode 100644 index 0000000000000000000000000000000000000000..e12cffd1bb4d9ddf98e5be88099be372786c06c3 --- /dev/null +++ b/src/app/shared/components/signup-modal/signup-modal.component.html @@ -0,0 +1,46 @@ +<div class="cModal" [ngClass]="openned ? 'oModal' : ''"> + <div (clickOutside)="closeModal()" fxLayout="column" fxLayoutAlign="center center"> + <div class="ico-close-wrapper"> + <div (click)="closeModal()" class="ico-close-details"></div> + </div> + <h4 class="card-header">Connexion</h4> + <div class="card-body"> + <form [formGroup]="loginForm" (ngSubmit)="onSubmit()"> + <div class="form-group"> + <label for="username">Identifiant</label> + <input + type="text" + formControlName="username" + autocomplete="on" + class="form-control" + [ngClass]="{ 'is-invalid': submitted && f.username.errors }" + /> + <div *ngIf="submitted && f.username.errors" class="invalid-feedback"> + <div *ngIf="f.username.errors.required">Identifiant requis</div> + </div> + </div> + <div class="form-group"> + <label for="password">Mot de passe</label> + <input + type="password" + formControlName="password" + autocomplete="on" + class="form-control" + [ngClass]="{ 'is-invalid': submitted && f.password.errors }" + /> + <div *ngIf="submitted && f.password.errors"> + <div *ngIf="f.password.errors.required">Mot de passe requis</div> + </div> + </div> + <div *ngIf="authFailed">Identifiant ou mot de passe invalide</div> + <div> + <button [disabled]="loading"> + <span *ngIf="loading"></span> + Login + </button> + <button (click)="sendSwitchToSignIn()">Inscription</button> + </div> + </form> + </div> + </div> +</div> diff --git a/src/app/shared/components/signup-modal/signup-modal.component.scss b/src/app/shared/components/signup-modal/signup-modal.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..b65234fb3d7035bd0a406e3d617d3d3a3bd107e9 --- /dev/null +++ b/src/app/shared/components/signup-modal/signup-modal.component.scss @@ -0,0 +1,40 @@ +@import '../../../../assets/scss/typography'; +@import '../../../../assets/scss/color'; +@import '../../../../assets/scss/buttons'; +@import '../../../../assets/scss/z-index'; +@import '../../../../assets/scss/hyperlink'; +.cModal { + position: fixed; + z-index: $modal-add-structure-z-index; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: $modal-background; + display: none; +} +.cModal > div { + max-width: 450px; + position: relative; + top: 40%; + left: 50%; + transform: translate(-50%, -50%); + background: $white; + text-align: center; + border-radius: 4px; + @include cn-bold-16; + p { + padding: 30px 48px 0 40px; + } +} +.oModal { + display: block; +} +.ico-close-wrapper { + position: absolute; + top: -10px; + right: -10px; +} +.ico-close-details { + opacity: 1; +} diff --git a/src/app/shared/components/signup-modal/signup-modal.component.spec.ts b/src/app/shared/components/signup-modal/signup-modal.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..dfc6badbb2d213984c67aab883c17f7f85c44a5e --- /dev/null +++ b/src/app/shared/components/signup-modal/signup-modal.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SignUpModalComponent } from './signup-modal.component'; + +describe('SignUpModalComponent', () => { + let component: SignUpModalComponent; + let fixture: ComponentFixture<SignUpModalComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [SignUpModalComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SignUpModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/signup-modal/signup-modal.component.ts b/src/app/shared/components/signup-modal/signup-modal.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..1ef87d4d1f9ba8924e4642ec8a60bd5b3a834f56 --- /dev/null +++ b/src/app/shared/components/signup-modal/signup-modal.component.ts @@ -0,0 +1,74 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { first } from 'rxjs/operators'; +import { AuthService } from '../../../services/auth.service'; + +@Component({ + selector: 'app-signup-modal', + templateUrl: './signup-modal.component.html', + styleUrls: ['./signup-modal.component.scss'], +}) +export class SignUpModalComponent implements OnInit { + public loginForm: FormGroup; + public loading = false; + public submitted = false; + public authFailed = false; + public returnUrl: string; + + constructor( + private formBuilder: FormBuilder, + private route: ActivatedRoute, + private router: Router, + private authService: AuthService + ) {} + + @Input() public openned: boolean; + @Output() closed = new EventEmitter<boolean>(); + + ngOnInit(): void { + this.loginForm = this.formBuilder.group({ + username: ['', Validators.required], + password: ['', Validators.required], + }); + // get return url from route parameters or default to '/' + this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; + } + + // getter for form fields + get f(): { [key: string]: AbstractControl } { + return this.loginForm.controls; + } + + public closeModal(): void { + this.closed.emit(true); + } + + public sendSwitchToSignIn(): void { + this.closed.emit(false); + } + + public onSubmit(): void { + this.submitted = true; + + // stop here if form is invalid + if (this.loginForm.invalid) { + return; + } + + this.loading = true; + this.authService + .login(this.f.username.value, this.f.password.value) + .pipe(first()) + .subscribe( + () => { + this.router.navigate([this.returnUrl]); + this.closeModal(); + }, + () => { + this.loading = false; + this.authFailed = true; + } + ); + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 4e6a02828e3c2ed7197aab10de2e29437bc177f3..3acb5e40b1e85264b0188e786f40b0c3bd29e411 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -1,13 +1,14 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { FlexLayoutModule } from '@angular/flex-layout'; import { SharedComponents } from './components'; import { SharedPipes } from './pipes'; import { SharedDirectives } from './directives'; -import { RouterModule } from '@angular/router'; -import { FlexLayoutModule } from '@angular/flex-layout'; import { SvgIconComponent } from './components/svg-icon/svg-icon.component'; @NgModule({ - imports: [CommonModule, RouterModule, FlexLayoutModule], + imports: [CommonModule, RouterModule, FlexLayoutModule, ReactiveFormsModule], declarations: [...SharedPipes, ...SharedComponents, ...SharedDirectives, SvgIconComponent], exports: [...SharedPipes, ...SharedComponents, ...SharedDirectives], }) diff --git a/src/app/shared/validator/form.ts b/src/app/shared/validator/form.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a1cc906def4e3e5fe38aef3e0dd61155f9a8b54 --- /dev/null +++ b/src/app/shared/validator/form.ts @@ -0,0 +1,16 @@ +import { FormGroup } from '@angular/forms'; + +// custom validator to check that two fields match +export function MustMatch(controlName: string, matchingControlName: string): any { + return (formGroup: FormGroup) => { + const control = formGroup.controls[controlName]; + const matchingControl = formGroup.controls[matchingControlName]; + + // set error on matchingControl if validation fails + if (control.value !== matchingControl.value) { + matchingControl.setErrors({ mustMatch: true }); + } else { + matchingControl.setErrors(null); + } + }; +} diff --git a/src/app/user-verification/user-verification.component.html b/src/app/user-verification/user-verification.component.html new file mode 100644 index 0000000000000000000000000000000000000000..acb513c81be1f0418d4ccdec20f41e2644e76a97 --- /dev/null +++ b/src/app/user-verification/user-verification.component.html @@ -0,0 +1,13 @@ +<div fxLayout="column" class="content-container"> + <h1 style="display: none">Vérification du mail utilisateur</h1> + <div class="section-container" fxLayout="colum" fxLayoutAlign="center center"> + <p *ngIf="!verificationSuccess && !verificationIssue">Votre email est en cours de vérification ...</p> + <p *ngIf="verificationSuccess"> + Vous avez correctement validé l'email associé a votre compte. Vous pouvez dès maintenant vous connecter au site. + </p> + <p *ngIf="verificationIssue"> + Une erreur est survenue lors de la validation de votre email... Veuillez envoyer un mail au support. + </p> + <div></div> + </div> +</div> diff --git a/src/app/user-verification/user-verification.component.scss b/src/app/user-verification/user-verification.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/user-verification/user-verification.component.spec.ts b/src/app/user-verification/user-verification.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..241a7b6ad2873d8422d1b0d5563d9e0d77a0a3ec --- /dev/null +++ b/src/app/user-verification/user-verification.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserVerificationComponent } from './user-verification.component'; + +describe('UserVerificationComponent', () => { + let component: UserVerificationComponent; + let fixture: ComponentFixture<UserVerificationComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ UserVerificationComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UserVerificationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/user-verification/user-verification.component.ts b/src/app/user-verification/user-verification.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..e5c0e4e7903c5777b94d1d6e536f8af442850414 --- /dev/null +++ b/src/app/user-verification/user-verification.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { AuthService } from '../services/auth.service'; + +@Component({ + selector: 'app-user-verification', + templateUrl: './user-verification.component.html', + styleUrls: ['./user-verification.component.scss'], +}) +export class UserVerificationComponent implements OnInit { + public userId: string; + public token: string; + public verificationSuccess = false; + public verificationIssue = false; + + constructor(private activatedRoute: ActivatedRoute, private authService: AuthService) { + this.activatedRoute.queryParams.subscribe((params) => { + this.token = params['token']; + }); + } + + ngOnInit(): void { + this.userId = this.activatedRoute.snapshot.paramMap.get('id'); + this.sendVerification(); + } + + private sendVerification(): void { + this.authService.verifyUser(this.userId, this.token).subscribe( + () => { + this.verificationSuccess = true; + }, + () => { + this.verificationIssue = true; + } + ); + } +}