From 3db0df04df7dd3988b04c1b0eb5cf87b1c0842fb Mon Sep 17 00:00:00 2001 From: Hugo SUBTIL <ext.sopra.husubtil@grandlyon.com> Date: Fri, 11 Dec 2020 18:46:03 +0100 Subject: [PATCH] feat(profile): add change password --- src/app/config/http-interceptor.ts | 4 ++ src/app/profile/profile.component.html | 54 ++++++++++++++++++++- src/app/profile/profile.component.ts | 53 +++++++++++++++++++- src/app/profile/services/profile.service.ts | 8 ++- src/app/services/auth.service.ts | 5 +- src/app/services/geojson.service.ts | 7 +-- src/app/shared/shared.module.ts | 10 +++- 7 files changed, 133 insertions(+), 8 deletions(-) diff --git a/src/app/config/http-interceptor.ts b/src/app/config/http-interceptor.ts index caa70fa29..d4911dbb9 100644 --- a/src/app/config/http-interceptor.ts +++ b/src/app/config/http-interceptor.ts @@ -17,6 +17,10 @@ export class CustomHttpInterceptor implements HttpInterceptor { // HttpHeader object immutable - copy values const headerSettings: { [name: string]: string | string[] } = {}; + if (request.headers.get('skip')) { + return next.handle(request); + } + for (const key of request.headers.keys()) { headerSettings[key] = request.headers.getAll(key); } diff --git a/src/app/profile/profile.component.html b/src/app/profile/profile.component.html index ac9939990..1a7ca9883 100644 --- a/src/app/profile/profile.component.html +++ b/src/app/profile/profile.component.html @@ -1,9 +1,61 @@ <div fxLayout="column" class="content-container"> - <div class="section-container" fxLayout="colum" fxLayoutAlign="center center"> + <div class="section-container" fxLayout="column" fxLayoutAlign="center center"> <h1>Profil</h1> <div *ngIf="userProfile" fxLayout="column" fxLayoutAlign="center" fxLayoutGap="10px"> <p>Id: {{ userProfile._id }}</p> <p>Email: {{ userProfile.email }}</p> + <button (click)="toogleChangePassword()">Changer de mot de passe</button> + + <form *ngIf="changePassword" fxLayout="column" fxLayoutGap="10px" [formGroup]="form" (ngSubmit)="onSubmit()"> + <div class="form-group"> + <label for="oldPassword">Ancien mot de passe</label> + <input + type="password" + autocomplete="on" + formControlName="oldPassword" + class="form-control" + [ngClass]="{ 'is-invalid': submitted && f.oldPassword.errors }" + /> + <div *ngIf="submitted && f.oldPassword.errors" class="invalid-feedback"> + <div *ngIf="f.oldPassword.errors.required">L'Ancien mot de passe est obligatoire</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 class="form-group"> + <button type="submit" [disabled]="loading" class="btn btn-primary">Appliquer</button> + </div> + </form> </div> </div> </div> diff --git a/src/app/profile/profile.component.ts b/src/app/profile/profile.component.ts index 04a353c04..e22fff776 100644 --- a/src/app/profile/profile.component.ts +++ b/src/app/profile/profile.component.ts @@ -1,5 +1,7 @@ import { Component, OnInit } from '@angular/core'; +import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { User } from '../models/user.model'; +import { MustMatch } from '../shared/validator/form'; import { ProfileService } from './services/profile.service'; @Component({ @@ -8,13 +10,62 @@ import { ProfileService } from './services/profile.service'; styleUrls: ['./profile.component.scss'], }) export class ProfileComponent implements OnInit { + public form: FormGroup; public userProfile: User; + public submitted = false; + public changePassword = false; + public loading = false; - constructor(private profileService: ProfileService) {} + constructor(private formBuilder: FormBuilder, private profileService: ProfileService) {} ngOnInit(): void { this.profileService.getProfile().subscribe((profile) => { this.userProfile = profile; }); + this.initForm(); + } + + public initForm(): void { + this.form = this.formBuilder.group( + { + oldPassword: [ + '', + [Validators.required, Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/)], + ], + 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 toogleChangePassword(): void { + this.changePassword = !this.changePassword; + } + + public onSubmit(): void { + this.submitted = true; + // stop here if form is invalid + if (this.form.invalid) { + return; + } + this.loading = true; + + this.profileService.changePassword(this.form.value.password, this.form.value.oldPassword).subscribe( + () => { + this.toogleChangePassword(); + }, + (error) => { + this.loading = false; + } + ); } } diff --git a/src/app/profile/services/profile.service.ts b/src/app/profile/services/profile.service.ts index 5e43c8784..0e1a65cf2 100644 --- a/src/app/profile/services/profile.service.ts +++ b/src/app/profile/services/profile.service.ts @@ -7,9 +7,15 @@ import { User } from '../../models/user.model'; providedIn: 'root', }) export class ProfileService { + private readonly baseUrl = 'api/users'; + constructor(private http: HttpClient) {} public getProfile(): Observable<User> { - return this.http.get<User>('api/users/profile'); + return this.http.get<User>(`${this.baseUrl}/profile`); + } + + public changePassword(newPassword: string, oldPassword: string): Observable<User> { + return this.http.post<any>(`${this.baseUrl}/change-password`, { newPassword, oldPassword }); } } diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index 9834bd007..4f40f2c3d 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -22,7 +22,10 @@ export class AuthService { } public get token(): string { - return this.userSubject.value.accessToken; + if (this.userSubject.value) { + return this.userSubject.value.accessToken; + } + return null; } public logout(): void { diff --git a/src/app/services/geojson.service.ts b/src/app/services/geojson.service.ts index b48ce0da4..ac3bfe1e2 100644 --- a/src/app/services/geojson.service.ts +++ b/src/app/services/geojson.service.ts @@ -17,7 +17,7 @@ export class GeojsonService { */ public getAddressByCoord(longitude: number, latitude: number): Observable<any> { return this.http - .get('/reverse/' + '?lon=' + longitude + '&lat=' + latitude) + .get('/reverse/' + '?lon=' + longitude + '&lat=' + latitude, { headers: { skip: 'true' } }) .pipe(map((data: { features: any[] }) => new GeoJson(data.features[0]))); } @@ -37,7 +37,8 @@ export class GeojsonService { return this.http .get( '/wfs/grandlyon' + - '?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=ter_territoire.maison_de_la_metropole&outputFormat=application/json; subtype=geojson&SRSNAME=EPSG:4171&startIndex=0' + '?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=ter_territoire.maison_de_la_metropole&outputFormat=application/json; subtype=geojson&SRSNAME=EPSG:4171&startIndex=0', + { headers: { skip: 'true' } } ) .pipe(map((data: { features: any[] }) => _.map(data.features, this.parseToGeoJson))); } @@ -48,7 +49,7 @@ export class GeojsonService { */ public getCoord(numero: string, address: string, zipcode: string): Observable<GeoJson> { return this.http - .get('/geocoding/photon/api' + '?q=' + numero + ' ' + address + ' ' + zipcode) + .get('/geocoding/photon/api' + '?q=' + numero + ' ' + address + ' ' + zipcode, { headers: { skip: 'true' } }) .pipe(map((data: { features: any[]; type: string }) => new GeoJson(data.features[0]))); } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 3acb5e40b..8bc1231da 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -10,6 +10,14 @@ import { SvgIconComponent } from './components/svg-icon/svg-icon.component'; @NgModule({ imports: [CommonModule, RouterModule, FlexLayoutModule, ReactiveFormsModule], declarations: [...SharedPipes, ...SharedComponents, ...SharedDirectives, SvgIconComponent], - exports: [...SharedPipes, ...SharedComponents, ...SharedDirectives], + exports: [ + ...SharedPipes, + ...SharedComponents, + ...SharedDirectives, + CommonModule, + RouterModule, + FlexLayoutModule, + ReactiveFormsModule, + ], }) export class SharedModule {} -- GitLab