-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1059 from ORCID/passwordResetStartComponent
Password reset start component
- Loading branch information
Showing
9 changed files
with
277 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,18 @@ | ||
import { Routes } from '@angular/router' | ||
import { LoginComponent } from './login/login.component' | ||
import { PasswordResetInitComponent } from './password/password-reset-init.component' | ||
|
||
export const routes: Routes = [ | ||
{ | ||
path: 'login', | ||
component: LoginComponent, | ||
}, | ||
{ | ||
path: 'reset/request', | ||
component: PasswordResetInitComponent, | ||
data: { | ||
authorities: [], | ||
pageTitle: 'global.menu.account.password.string', | ||
}, | ||
}, | ||
] |
46 changes: 46 additions & 0 deletions
46
ui/src/app/account/password/password-reset-init.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<div class="container m-3"> | ||
<div class="row justify-content-center"> | ||
<div class="col-md-8"> | ||
<h1 jhiTranslate="reset.request.title.string">Reset your password</h1> | ||
|
||
<div class="alert alert-danger" jhiTranslate="reset.request.messages.notfound.string" *ngIf="errorEmailNotExists"> | ||
<strong>Email address isn't registered!</strong> Please check and try again. | ||
</div> | ||
|
||
<div class="alert alert-warning" *ngIf="!success"> | ||
<p jhiTranslate="reset.request.messages.info.string">Enter the email address you used to register.</p> | ||
</div> | ||
|
||
<div class="alert alert-success" *ngIf="success === 'OK'"> | ||
<p jhiTranslate="reset.request.messages.success.string">Check your emails for details on how to reset your password.</p> | ||
</div> | ||
|
||
<form *ngIf="!success" name="form" role="form" (ngSubmit)="requestReset()" [formGroup]="resetRequestForm"> | ||
<div class="form-group"> | ||
<label class="form-control-label" for="email" jhiTranslate="global.form.email.label.string">Email</label> | ||
<input type="email" class="form-control" id="email" name="email" placeholder="{{'global.form.email.placeholder.string'}}" | ||
formControlName="email"> | ||
<div *ngIf="resetRequestForm.get('email')?.invalid && (resetRequestForm.get('email')?.dirty || resetRequestForm.get('email')?.touched)"> | ||
<small class="form-text text-danger" | ||
*ngIf="resetRequestForm.get('email')?.errors!['required']" jhiTranslate="global.messages.validate.email.required.string"> | ||
Your email is required. | ||
</small> | ||
<small class="form-text text-danger" | ||
*ngIf="resetRequestForm.get('email')?.errors!['email']" jhiTranslate="global.messages.validate.email.invalid.string"> | ||
Your email is invalid. | ||
</small> | ||
<small class="form-text text-danger" | ||
*ngIf="resetRequestForm.get('email')?.errors!['minlength']" jhiTranslate="global.messages.validate.email.minlength.string"> | ||
Your email is required to be at least 5 characters. | ||
</small> | ||
<small class="form-text text-danger" id="maxlengthError" | ||
*ngIf="resetRequestForm.get('email')?.errors!['maxlength']" jhiTranslate="global.messages.validate.email.maxlength.string"> | ||
Your email cannot be longer than 100 characters. | ||
</small> | ||
</div> | ||
</div> | ||
<button type="submit" id="reset" [disabled]="resetRequestForm.invalid" class="btn btn-primary" jhiTranslate="reset.request.form.button.string">Reset</button> | ||
</form> | ||
</div> | ||
</div> | ||
</div> |
146 changes: 146 additions & 0 deletions
146
ui/src/app/account/password/password-reset-init.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import { ComponentFixture, TestBed, inject } from '@angular/core/testing' | ||
import { of, throwError } from 'rxjs' | ||
|
||
import { PasswordResetInitService } from '../service/password-reset-init.service' | ||
import { PasswordResetInitComponent } from './password-reset-init.component' | ||
import { EMAIL_NOT_FOUND_TYPE } from 'src/app/app.constants' | ||
import { HttpClientTestingModule } from '@angular/common/http/testing' | ||
import { By } from '@angular/platform-browser' | ||
|
||
describe('Component Tests', () => { | ||
describe('PasswordResetInitComponent', () => { | ||
let fixture: ComponentFixture<PasswordResetInitComponent> | ||
let comp: PasswordResetInitComponent | ||
beforeEach(() => { | ||
fixture = TestBed.configureTestingModule({ | ||
imports: [HttpClientTestingModule], | ||
declarations: [PasswordResetInitComponent], | ||
}).createComponent(PasswordResetInitComponent) | ||
comp = fixture.componentInstance | ||
}) | ||
|
||
it('should define its initial state', () => { | ||
expect(comp.success).toBeUndefined() | ||
expect(comp.error).toBeUndefined() | ||
expect(comp.errorEmailNotExists).toBeUndefined() | ||
}) | ||
|
||
it('notifies of success upon successful requestReset', inject( | ||
[PasswordResetInitService], | ||
(service: PasswordResetInitService) => { | ||
spyOn(service, 'initPasswordReset').and.returnValue(of({})) | ||
comp.resetRequestForm.patchValue({ | ||
email: '[email protected]', | ||
}) | ||
|
||
comp.requestReset() | ||
const emailControl = comp.resetRequestForm.get('email')! | ||
emailControl.setValue('[email protected]') | ||
fixture.detectChanges() | ||
expect(comp.success).toEqual('OK') | ||
expect(comp.error).toBeUndefined() | ||
expect(comp.errorEmailNotExists).toBeUndefined() | ||
fixture.whenStable().then(() => { | ||
expect(true).toBeFalsy() | ||
const button = fixture.debugElement.query(By.css('#reset')) | ||
expect(button.nativeElement.disabled).toBeFalsy() | ||
}) | ||
} | ||
)) | ||
|
||
it('notifies of unknown email upon email address not registered/400', inject( | ||
[PasswordResetInitService], | ||
(service: PasswordResetInitService) => { | ||
spyOn(service, 'initPasswordReset').and.returnValue( | ||
throwError({ | ||
status: 400, | ||
error: { type: EMAIL_NOT_FOUND_TYPE }, | ||
}) | ||
) | ||
comp.resetRequestForm.patchValue({ | ||
email: '[email protected]', | ||
}) | ||
comp.requestReset() | ||
|
||
expect(service.initPasswordReset).toHaveBeenCalledWith('[email protected]') | ||
expect(comp.success).toBeUndefined() | ||
expect(comp.error).toBeUndefined() | ||
expect(comp.errorEmailNotExists).toEqual('ERROR') | ||
} | ||
)) | ||
|
||
it('notifies of error upon error response', inject( | ||
[PasswordResetInitService], | ||
(service: PasswordResetInitService) => { | ||
spyOn(service, 'initPasswordReset').and.returnValue( | ||
throwError({ | ||
status: 503, | ||
data: 'something else', | ||
}) | ||
) | ||
comp.resetRequestForm.patchValue({ | ||
email: '[email protected]', | ||
}) | ||
comp.requestReset() | ||
|
||
expect(service.initPasswordReset).toHaveBeenCalledWith('[email protected]') | ||
expect(comp.success).toBeUndefined() | ||
expect(comp.errorEmailNotExists).toBeUndefined() | ||
expect(comp.error).toEqual('ERROR') | ||
} | ||
)) | ||
|
||
it('should disable the submit button for invalid email address', () => { | ||
const emailControl = comp.resetRequestForm.get('email')! | ||
emailControl.markAsTouched() | ||
emailControl.setValue('invalid-email') | ||
fixture.detectChanges() | ||
const errorMessage = fixture.debugElement.query(By.css('small')) | ||
expect(errorMessage).toBeTruthy() | ||
const errorText = errorMessage.nativeElement.textContent.trim() | ||
expect(errorText).toBe('Your email is invalid.') | ||
const button = fixture.debugElement.query(By.css('#reset')) | ||
expect(button.nativeElement.disabled).toBeTruthy() | ||
}) | ||
|
||
it('should disable the submit button for empty email address field', () => { | ||
const emailControl = comp.resetRequestForm.get('email')! | ||
emailControl.markAsTouched() | ||
fixture.detectChanges() | ||
const errorMessage = fixture.debugElement.query(By.css('small')) | ||
expect(errorMessage).toBeTruthy() | ||
const errorText = errorMessage.nativeElement.textContent.trim() | ||
expect(errorText).toBe('Your email is required.') | ||
const button = fixture.debugElement.query(By.css('#reset')) | ||
expect(button.nativeElement.disabled).toBeTruthy() | ||
}) | ||
|
||
it('should disable the submit button for short email address', () => { | ||
const emailControl = comp.resetRequestForm.get('email')! | ||
emailControl.setValue('i@a') | ||
emailControl.markAsTouched() | ||
fixture.detectChanges() | ||
const errorMessage = fixture.debugElement.query(By.css('small')) | ||
expect(errorMessage).toBeTruthy() | ||
const errorText = errorMessage.nativeElement.textContent.trim() | ||
expect(errorText).toBe('Your email is required to be at least 5 characters.') | ||
const button = fixture.debugElement.query(By.css('#reset')) | ||
expect(button.nativeElement.disabled).toBeTruthy() | ||
}) | ||
|
||
it('should disable the submit button for long email address', () => { | ||
const emailControl = comp.resetRequestForm.get('email')! | ||
emailControl.setValue( | ||
'abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde@mail.com' | ||
) | ||
emailControl.markAsTouched() | ||
fixture.detectChanges() | ||
const errorMessage = fixture.debugElement.query(By.css('#maxlengthError')) | ||
expect(errorMessage).toBeTruthy() | ||
const errorText = errorMessage.nativeElement.textContent.trim() | ||
expect(errorText).toBe('Your email cannot be longer than 100 characters.') | ||
const button = fixture.debugElement.query(By.css('#reset')) | ||
expect(button.nativeElement.disabled).toBeTruthy() | ||
}) | ||
}) | ||
}) |
49 changes: 49 additions & 0 deletions
49
ui/src/app/account/password/password-reset-init.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { Component, AfterViewInit, Renderer2 } from '@angular/core' | ||
import { FormBuilder, FormGroup, Validators } from '@angular/forms' | ||
|
||
import { PasswordResetInitService } from '../service/password-reset-init.service' | ||
import { EMAIL_NOT_FOUND_TYPE } from 'src/app/app.constants' | ||
|
||
@Component({ | ||
selector: 'app-password-reset-init', | ||
templateUrl: './password-reset-init.component.html', | ||
}) | ||
export class PasswordResetInitComponent implements AfterViewInit { | ||
error: string | undefined | ||
errorEmailNotExists: string | undefined | ||
success: string | undefined | ||
resetRequestForm = this.fb.group({ | ||
email: ['', [Validators.required, Validators.minLength(5), Validators.maxLength(100), Validators.email]], | ||
}) | ||
|
||
constructor( | ||
private passwordResetInitService: PasswordResetInitService, | ||
private renderer: Renderer2, | ||
private fb: FormBuilder | ||
) {} | ||
|
||
ngAfterViewInit() { | ||
setTimeout(() => this.renderer.selectRootElement('#email').focus()) | ||
} | ||
|
||
requestReset() { | ||
this.error = undefined | ||
this.errorEmailNotExists = undefined | ||
|
||
if (this.resetRequestForm.get(['email'])) { | ||
this.passwordResetInitService.initPasswordReset(this.resetRequestForm.get(['email'])!.value).subscribe({ | ||
next: () => { | ||
this.success = 'OK' | ||
}, | ||
error: (response) => { | ||
this.success = undefined | ||
if (response.status === 400 && response.error.type === EMAIL_NOT_FOUND_TYPE) { | ||
this.errorEmailNotExists = 'ERROR' | ||
} else { | ||
this.error = 'ERROR' | ||
} | ||
}, | ||
}) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Injectable } from '@angular/core' | ||
import { HttpClient } from '@angular/common/http' | ||
import { Observable } from 'rxjs' | ||
|
||
@Injectable({ providedIn: 'root' }) | ||
export class PasswordResetInitService { | ||
constructor(private http: HttpClient) {} | ||
|
||
initPasswordReset(mail: string): Observable<any> { | ||
return this.http.post('/services/userservice/api/account/reset-password/init', mail) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
:host { | ||
margin-top: auto; | ||
} | ||
|
||
|
||
a { | ||
color: rgba(0,0,0,0.87); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters