Skip to content

Commit f09c3c4

Browse files
authored
Merge pull request #2715 from ORCID/aromanovv/PD-3786-update-reset-password-ui
Aromanovv/pd 3786 update reset password UI
2 parents a59bd66 + dac67f8 commit f09c3c4

23 files changed

Lines changed: 591 additions & 558 deletions
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { TestBed } from '@angular/core/testing'
2+
import {
3+
Router,
4+
ActivatedRouteSnapshot,
5+
RouterStateSnapshot,
6+
UrlTree,
7+
} from '@angular/router'
8+
import { ResetPasswordGuard } from './reset-password.guard'
9+
import { RouterTestingModule } from '@angular/router/testing'
10+
const ApplicationRoutes = {
11+
signin: 'signin',
12+
}
13+
14+
describe('ResetPasswordGuard', () => {
15+
let guard: ResetPasswordGuard
16+
let router: Router
17+
const createMockRouteSnapshot = (key: string | null) => {
18+
return {
19+
params: {
20+
key: key,
21+
},
22+
} as unknown as ActivatedRouteSnapshot
23+
}
24+
25+
const mockStateSnapshot = {} as RouterStateSnapshot
26+
27+
beforeEach(() => {
28+
TestBed.configureTestingModule({
29+
imports: [RouterTestingModule],
30+
providers: [ResetPasswordGuard],
31+
})
32+
guard = TestBed.inject(ResetPasswordGuard)
33+
router = TestBed.inject(Router)
34+
})
35+
36+
it('should be created', () => {
37+
expect(guard).toBeTruthy()
38+
})
39+
40+
it('should return true if the "key" query parameter exists', () => {
41+
const mockRoute = createMockRouteSnapshot('some-token-value')
42+
const result = guard.canActivate(mockRoute, mockStateSnapshot)
43+
expect(result).toBeTruthy()
44+
})
45+
46+
it('should return a UrlTree to signin if the "key" query parameter does not exist', () => {
47+
const mockRoute = createMockRouteSnapshot(null)
48+
const routerSpy = spyOn(router, 'createUrlTree').and.callThrough()
49+
50+
const result = guard.canActivate(mockRoute, mockStateSnapshot)
51+
expect(routerSpy).toHaveBeenCalledWith([ApplicationRoutes.signin])
52+
expect(result instanceof UrlTree).toBeTrue()
53+
})
54+
})
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Injectable } from '@angular/core'
2+
import {
3+
ActivatedRouteSnapshot,
4+
Router,
5+
RouterStateSnapshot,
6+
UrlTree,
7+
} from '@angular/router'
8+
import { Observable } from 'rxjs'
9+
10+
import { ApplicationRoutes } from '../constants'
11+
12+
@Injectable({
13+
providedIn: 'root',
14+
})
15+
export class ResetPasswordGuard {
16+
constructor(private _router: Router) {}
17+
canActivate(
18+
next: ActivatedRouteSnapshot,
19+
state: RouterStateSnapshot
20+
):
21+
| Observable<boolean | UrlTree>
22+
| Promise<boolean | UrlTree>
23+
| boolean
24+
| UrlTree {
25+
const key = next.params['key']
26+
return !!key || this._router.createUrlTree([ApplicationRoutes.signin])
27+
}
28+
}
Lines changed: 171 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,174 @@
1-
<main id="main">
2-
<div class="container">
3-
<div class="row horizontal-center" [formGroup]="recoveryForm">
4-
<mat-card appearance="outlined" class="orcid-wizard col l6 m6 s4">
5-
<mat-card-header>
6-
<mat-card-title i18n="@@ngOrcid.passwordAndOrcid"
7-
>Password and ORCID iD Recovery
8-
</mat-card-title>
9-
</mat-card-header>
10-
<mat-progress-bar
11-
*ngIf="loading"
12-
mode="indeterminate"
13-
></mat-progress-bar>
14-
<mat-card-content>
15-
<div [hidden]="submitted">
16-
<p
17-
class="row mat-caption"
18-
i18n="@@ngOrcid.didYouForget"
19-
id="didYouForget"
20-
>
21-
Did you forget your password or ORCID iD?
22-
</p>
23-
<mat-chip-listbox
24-
aria-labelledby="didYouForget"
25-
formControlName="type"
26-
required
27-
>
28-
<mat-chip-option
29-
value="resetPassword"
30-
class="orcid-outline-chip"
31-
#passwordChip
32-
(click)="passwordChip.select()"
33-
i18n="@@check_password_modal.password"
34-
>Password</mat-chip-option
35-
>
36-
<mat-chip-option
37-
value="remindOrcidId"
38-
#emailChip
39-
(click)="emailChip.select()"
40-
class="orcid-outline-chip"
41-
i18n="@@common.orcid_id"
42-
>ORCID iD</mat-chip-option
43-
>
44-
</mat-chip-listbox>
45-
<mat-error
46-
*ngIf="
47-
typeFormControl.hasError('required') && typeFormControl.touched
48-
"
49-
[@transitionMessages]="_subscriptAnimationState"
50-
class="chipsError mat-caption"
51-
i18n="@@ngOrcid.pleaseChooseRecovery"
52-
>Please choose a recovery option</mat-error
53-
>
54-
55-
<mat-form-field appearance="fill">
56-
<mat-label i18n="@@oauth_sign_up.labelemail">Email</mat-label>
57-
<input matInput formControlName="email" required appTrim />
58-
59-
<mat-error
60-
i18n="@@Email.resendClaim.invalidEmail"
61-
*ngIf="
62-
emailFormControl.hasError('required') ||
63-
emailFormControl.hasError('email') ||
64-
emailFormControl.hasError('pattern')
65-
"
66-
>Please enter a valid email address</mat-error
67-
>
68-
69-
<mat-error
70-
*ngFor="
71-
let backendError of emailFormControl.errors
72-
? emailFormControl.errors['backendErrors']
73-
: []
74-
"
75-
>{{ backendError }}</mat-error
76-
>
77-
</mat-form-field>
78-
79-
<button
80-
mat-raised-button
81-
(click)="onSubmit()"
82-
color="primary"
83-
type="submit"
84-
[disabled]="loading"
85-
i18n="@@ngOrcid.recoverAccountDetails"
86-
id="cy-recover-acc-details"
87-
>
88-
RECOVER ACCOUNT DETAILS
89-
</button>
90-
<mat-error *ngIf="this.serverError">{{
91-
this.serverError
92-
}}</mat-error>
93-
</div>
94-
<div class="success-container" *ngIf="submitted" role="alert">
95-
<p>
96-
<ng-container
97-
i18n="@@orcid.frontend.reset.password.email_success_1"
98-
>We have sent a message to</ng-container
99-
>
100-
<strong>{{ emailFormControl.value }}</strong>
101-
<ng-container
102-
i18n="@@orcid.frontend.reset.password.email_success_2"
103-
>from reset&#64;notify.orcid.org.</ng-container
104-
>
105-
<ng-container i18n="@@ngOrcid.youCanNow"
106-
>You can now</ng-container
107-
>
108-
<a
109-
i18n="@@ngOrcid.returnToLoginPage"
110-
(click)="navigateTo('/login')"
111-
>return to the login page.</a
112-
>
113-
</p>
114-
<p>
115-
<ng-container
116-
i18n="@@orcid.frontend.reset.password.email_success_3"
117-
>If you do not receive a message within 10 minutes, please check
118-
your spam folder. If you still do not see a message,
119-
then</ng-container
120-
>
1+
<div
2+
[formGroup]="recoveryForm"
3+
class="grid justify-items-center text-center max-w-[452px] mt-8 sm:p-16! p-8! border rounded-[10px] ml-auto mr-auto"
4+
>
5+
<div class="icon">
6+
<img src="assets/vectors/orcid.logo.icon.svg" role="presentation" />
7+
</div>
8+
<h1
9+
class="orc-font-heading-small font-normal mb-8"
10+
i18n="@@recovery.passwordAndIdRecovery"
11+
>
12+
Password and iD recovery
13+
</h1>
14+
@if (!submitted) {
15+
<div class="text-left mb-8">
16+
<app-alert-message
17+
type="notice"
18+
role="dialog"
19+
aria-live="polite"
20+
aria-labelledby="dialogTitle"
21+
aria-describedby="dialogDescription"
22+
class="mb-6"
23+
>
24+
<div content class="content grid gap-2">
25+
<b i18n="@@recovery.lostAccessToYourEmailAddresses">
26+
Lost access to your email addresses?
27+
</b>
28+
<div>
29+
<span i18n="@@recovery.youCanAlwaysSignIn"
30+
>You can always sign in to ORCID with your</span
31+
>&nbsp;<b i18n="@@recovery.16DigitOrcidId">16-digit ORCID iD</b
32+
>&nbsp;<span i18n="@@register.and">and</span>&nbsp;<b
33+
i18n="@@recovery.accountPassword"
34+
>account password</b
35+
>&nbsp;<span i18n="@@recovery.instead">instead.</span>
36+
</div>
37+
<a
38+
href="#"
39+
tabindex="0"
40+
class="underline"
41+
[routerLink]="['/signin']"
42+
i18n="@@recovery.trySigningIn"
43+
[attr.aria-label]="ariaLabelLostAccess + ' ' + ariaLabelTrySigningIn"
44+
>Try signing in with your iD and password now</a
45+
>
46+
</div>
47+
</app-alert-message>
48+
<h2
49+
class="orc-font-body mb-4 mt-0 leading-6"
50+
i18n="@@recovery.whatHaveYouForgotten"
51+
>
52+
What have you forgotten?
53+
</h2>
54+
<mat-radio-group
55+
formControlName="recoveryType"
56+
color="primary"
57+
[attr.aria-label]="ariaLabelIveForgotten"
58+
>
59+
<mat-radio-button value="password" class="block mb-4 text-sm/normal!">
60+
<span
61+
class="orc-font-body-small ml-2"
62+
i18n="@@recovery.myOrcidAccountPassword"
63+
>my ORCID account password</span
64+
>
65+
</mat-radio-button>
12166

122-
<a
123-
i18n="@@orcid.frontend.reset.password.email_success_4"
124-
(click)="navigateTo('https://support.orcid.org/')"
125-
>contact us</a
126-
>.
127-
</p>
128-
</div>
129-
</mat-card-content>
130-
</mat-card>
67+
<mat-radio-button value="orcidId" class="block">
68+
<span
69+
class="orc-font-body-small ml-2"
70+
i18n="@@recovery.my16DigitOrcidId"
71+
>
72+
my 16-digit ORCID iD
73+
</span>
74+
</mat-radio-button>
75+
</mat-radio-group>
76+
<h2
77+
class="orc-font-body mt-6 mb-1 leading-6"
78+
i18n="@@recovery.whereShouldWeSend"
79+
>
80+
Where should we send the recovery email?
81+
</h2>
82+
<div
83+
class="leading-5.25 orc-font-body-small mb-4"
84+
i18n="@@recovery.pleaseUseAnEmail"
85+
>
86+
Please use an email address associated with your ORCID account.
87+
</div>
88+
<mat-label
89+
class="orc-font-small-print leading-4.5 font-bold!"
90+
id="email-input-label"
91+
i18n="@@register.Email"
92+
[ngClass]="{
93+
error:
94+
recoveryForm.get('email').touched && recoveryForm.get('email').invalid
95+
}"
96+
>Email
97+
</mat-label>
98+
<mat-form-field
99+
appearance="outline"
100+
[hideRequiredMarker]="true"
101+
class="no-hint passwordField my-2 w-full"
102+
>
103+
<input
104+
id="email"
105+
aria-labelledby="email-input-label"
106+
formControlName="email"
107+
matInput
108+
class="h-5.25 p-2 orc-font-body-small"
109+
[ngClass]="{
110+
error:
111+
recoveryForm.get('email').touched &&
112+
recoveryForm.get('email').dirty &&
113+
recoveryForm.get('email').invalid
114+
}"
115+
/>
116+
</mat-form-field>
117+
@if (!recoveryForm.get('email').invalid ||
118+
!(recoveryForm.get('email').touched )) {
119+
<mat-hint class="orc-font-small-print" i18n="@@recovery.forExampleEmail"
120+
>For example: joe&#64;institution.edu
121+
</mat-hint>
122+
} @if (recoveryForm.get('email').hasError('required') &&
123+
(recoveryForm.get('email').touched)) {
124+
<mat-error class="orc-font-small-print password-error ml-0">
125+
<ng-container i18n="@@register.primaryEmailRequired"
126+
>An email is required</ng-container
127+
>
128+
</mat-error>
129+
} @if (recoveryForm.get('email').hasError('email') &&
130+
(recoveryForm.get('email').touched )) {
131+
<mat-error
132+
i18n="@@side-bar.invalidEmailErrorMessage"
133+
class="orc-font-small-print ml-0"
134+
>
135+
Please enter a valid email address, for example joe&#64;institution.edu
136+
</mat-error>
137+
}
138+
</div>
139+
<hr class="mb-8!" />
140+
<button
141+
mat-raised-button
142+
(click)="onSubmit()"
143+
class="row text-base! mat-elevation-z0 h-10"
144+
type="submit"
145+
i18n="@@recovery.sendRecoveryEmail"
146+
[disabled]="loading"
147+
color="primary"
148+
[ngClass]="{ 'button-loading': loading }"
149+
id="cy-recover-acc-details"
150+
>
151+
Send recovery email
152+
</button>
153+
} @if (submitted) {
154+
<div class="text-left orc-font-body-small">
155+
<span i18n="@@recovery.weHaveSent">We have sent a recovery email to</span
156+
>&nbsp;
157+
<b>{{ recoveryForm.get('email').value }}</b>
158+
<br />
159+
<br />
160+
<div i18n="@@recovery.ifYouDoNotReceive">
161+
If you do not receive the recovery email within 10 minutes please check
162+
your spam folder.
131163
</div>
164+
<br />
165+
<a
166+
href="#"
167+
[routerLink]="['/signin']"
168+
class="underline"
169+
i18n="@@recovery.backToSignIn"
170+
>Back to sign in</a
171+
>
132172
</div>
133-
</main>
173+
}
174+
</div>

0 commit comments

Comments
 (0)