diff --git a/src/app/config/http-interceptor.ts b/src/app/config/http-interceptor.ts index caa70fa29ad5a660413cb43def77a1989285c655..d4911dbb91d27b0ae93f214a1c4d8d52a9d4b2a2 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 ac9939990b5e965f301f3472db7dd3f3068b51ce..1a7ca9883fc528448dad988b6b2b1e23ae907cbc 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 04a353c04221759d2ee7e083420304aea462014a..e22fff776015432069060bdb2ae221e83fc0fd78 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 5e43c87840e4ee4081a7df112b62ccd64b3910ac..0e1a65cf28b119a03a4b19debaee3a6543faedbd 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 9834bd0077a68ea597950c43c3f40ba3dc93d3b2..4f40f2c3de4bac22ba01f1723f73e3f9d68ce71b 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 b48ce0da4b6dd0a1d46bc8557607893691bb2003..ac3bfe1e2cedf2c0375deda7deadfde9112ab341 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 3acb5e40b1e85264b0188e786f40b0c3bd29e411..8bc1231da353edb6f58b1ad39d9421b7b2d6fd8f 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 {}