Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 7e2001e

Browse files
committed
Add password reset back options
1 parent ccfb455 commit 7e2001e

File tree

10 files changed

+240
-62
lines changed

10 files changed

+240
-62
lines changed

res/css/views/auth/_AuthBody.pcss

+38-9
Original file line numberDiff line numberDiff line change
@@ -137,15 +137,50 @@ limitations under the License.
137137
}
138138

139139
/* specialisation for password reset views */
140-
.mx_AuthBody_forgot-password {
140+
.mx_AuthBody.mx_AuthBody_forgot-password {
141141
font-size: $font-14px;
142142
color: $primary-content;
143143
padding: 50px 32px;
144144
min-height: 600px;
145145

146146
h1 {
147-
margin-bottom: $spacing-20;
148-
margin-top: $spacing-24;
147+
margin: $spacing-24 0;
148+
}
149+
150+
.mx_AuthBody_button-container {
151+
display: flex;
152+
justify-content: center;
153+
}
154+
155+
.mx_Login_submit {
156+
font-weight: $font-semi-bold;
157+
margin: 0 0 $spacing-16;
158+
}
159+
160+
.mx_AuthBody_text {
161+
margin-bottom: $spacing-32;
162+
163+
p {
164+
margin: 0 0 8px;
165+
}
166+
}
167+
168+
.mx_AuthBody_sign-in-instead-button {
169+
font-weight: $font-semi-bold;
170+
padding: $spacing-4;
171+
}
172+
173+
.mx_AuthBody_fieldRow {
174+
margin-bottom: $spacing-24;
175+
}
176+
177+
.mx_AccessibleButton.mx_AccessibleButton_hasKind {
178+
background: none;
179+
180+
&:disabled {
181+
cursor: default;
182+
opacity: .4;
183+
}
149184
}
150185
}
151186

@@ -154,12 +189,6 @@ limitations under the License.
154189
color: $secondary-content;
155190
display: flex;
156191
gap: $spacing-8;
157-
margin-bottom: 10px;
158-
margin-top: $spacing-24;
159-
}
160-
161-
.mx_AuthBody_did-not-receive--centered {
162-
justify-content: center;
163192
}
164193

165194
.mx_AuthBody_resend-button {

res/css/views/dialogs/_VerifyEMailDialog.pcss

+10-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ limitations under the License.
2121
.mx_Dialog {
2222
color: $primary-content;
2323
font-size: 14px;
24-
padding: 16px;
24+
padding: $spacing-24 $spacing-24 $spacing-16;
2525
text-align: center;
2626
width: 485px;
2727

@@ -34,5 +34,14 @@ limitations under the License.
3434
color: $secondary-content;
3535
line-height: 20px;
3636
}
37+
38+
.mx_AuthBody_did-not-receive {
39+
justify-content: center;
40+
margin-bottom: 8px;
41+
}
42+
43+
.mx_Dialog_cancelButton {
44+
right: 10px;
45+
}
3746
}
3847
}

src/Modal.tsx

+10-2
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,11 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
347347
<div className="mx_Dialog">
348348
{ this.staticModal.elem }
349349
</div>
350-
<div className="mx_Dialog_background mx_Dialog_staticBackground" onClick={this.onBackgroundClick} />
350+
<div
351+
data-testid="dialog-background"
352+
className="mx_Dialog_background mx_Dialog_staticBackground"
353+
onClick={this.onBackgroundClick}
354+
/>
351355
</div>
352356
);
353357

@@ -368,7 +372,11 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
368372
<div className="mx_Dialog">
369373
{ modal.elem }
370374
</div>
371-
<div className="mx_Dialog_background" onClick={this.onBackgroundClick} />
375+
<div
376+
data-testid="dialog-background"
377+
className="mx_Dialog_background"
378+
onClick={this.onBackgroundClick}
379+
/>
372380
</div>
373381
);
374382

src/PasswordReset.ts

-20
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ import { createClient, IRequestTokenResponse, MatrixClient } from 'matrix-js-sdk
1919

2020
import { _t } from './languageHandler';
2121

22-
const CHECK_EMAIL_VERIFIED_POLL_INTERVAL = 2000;
23-
2422
/**
2523
* Allows a user to reset their password on a homeserver.
2624
*
@@ -108,24 +106,6 @@ export default class PasswordReset {
108106
await this.checkEmailLinkClicked();
109107
}
110108

111-
public async retrySetNewPassword(password: string): Promise<void> {
112-
this.password = password;
113-
return new Promise((resolve) => {
114-
this.tryCheckEmailLinkClicked(resolve);
115-
});
116-
}
117-
118-
private tryCheckEmailLinkClicked(resolve: Function): void {
119-
this.checkEmailLinkClicked()
120-
.then(() => resolve())
121-
.catch(() => {
122-
window.setTimeout(
123-
() => this.tryCheckEmailLinkClicked(resolve),
124-
CHECK_EMAIL_VERIFIED_POLL_INTERVAL,
125-
);
126-
});
127-
}
128-
129109
/**
130110
* Checks if the email link has been clicked by attempting to change the password
131111
* for the mxid linked to the email.

src/components/structures/auth/ForgotPassword.tsx

+35-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ limitations under the License.
1919
import React, { ReactNode } from 'react';
2020
import { logger } from 'matrix-js-sdk/src/logger';
2121
import { createClient } from "matrix-js-sdk/src/matrix";
22+
import { sleep } from 'matrix-js-sdk/src/utils';
2223

2324
import { _t, _td } from '../../../languageHandler';
2425
import Modal from "../../../Modal";
@@ -43,6 +44,8 @@ import Spinner from '../../views/elements/Spinner';
4344
import { formatSeconds } from '../../../DateUtils';
4445
import AutoDiscoveryUtils from '../../../utils/AutoDiscoveryUtils';
4546

47+
const emailCheckInterval = 2000;
48+
4649
enum Phase {
4750
// Show email input
4851
EnterEmail = 1,
@@ -83,6 +86,10 @@ interface State {
8386
}
8487

8588
export default class ForgotPassword extends React.Component<Props, State> {
89+
public static defaultProps: Partial<Props> = {
90+
onLoginClick: () => {},
91+
};
92+
8693
private reset: PasswordReset;
8794
private fieldPassword: Field | null = null;
8895
private fieldPasswordConfirm: Field | null = null;
@@ -277,22 +284,43 @@ export default class ForgotPassword extends React.Component<Props, State> {
277284
{
278285
email: this.state.email,
279286
errorText: this.state.errorText,
287+
onCloseClick: () => {
288+
modal.close();
289+
this.setState({ phase: Phase.PasswordInput });
290+
},
291+
onReEnterEmailClick: () => {
292+
modal.close();
293+
this.setState({ phase: Phase.EnterEmail });
294+
},
280295
onResendClick: this.sendVerificationMail,
281296
},
282297
"mx_VerifyEMailDialog",
283298
false,
284299
false,
285300
{
286-
// this modal cannot be dismissed except reset is done or forced
287301
onBeforeClose: async (reason?: string) => {
288-
return this.state.phase === Phase.Done || reason === "force";
302+
if (reason === "backgroundClick") {
303+
// Modal dismissed by clicking the background.
304+
// Go one phase back.
305+
this.setState({ phase: Phase.PasswordInput });
306+
}
307+
308+
return true;
289309
},
290310
},
291311
);
292312

293-
await this.reset.retrySetNewPassword(this.state.password);
294-
this.phase = Phase.Done;
295-
modal.close();
313+
// Don't retry if the phase changed. For example when going back to email input.
314+
while (this.state.phase === Phase.ResettingPassword) {
315+
try {
316+
await this.reset.setNewPassword(this.state.password);
317+
this.setState({ phase: Phase.Done });
318+
modal.close();
319+
} catch (e) {
320+
// Email not confirmed, yet. Retry after a while.
321+
await sleep(emailCheckInterval);
322+
}
323+
}
296324
}
297325

298326
private onSubmitForm = async (ev: React.FormEvent): Promise<void> => {
@@ -339,6 +367,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
339367
homeserver={this.props.serverConfig.hsName}
340368
loading={this.state.phase === Phase.SendingEmail}
341369
onInputChanged={this.onInputChanged}
370+
onLoginClick={this.props.onLoginClick!} // set by default props
342371
onSubmitForm={this.onSubmitForm}
343372
/>;
344373
}
@@ -374,6 +403,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
374403
return <CheckEmail
375404
email={this.state.email}
376405
errorText={this.state.errorText}
406+
onReEnterEmailClick={() => this.setState({ phase: Phase.EnterEmail })}
377407
onResendClick={this.sendVerificationMail}
378408
onSubmitForm={this.onSubmitForm}
379409
/>;

src/components/structures/auth/forgot-password/CheckEmail.tsx

+28-14
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { ErrorMessage } from "../../ErrorMessage";
2727
interface CheckEmailProps {
2828
email: string;
2929
errorText: string | ReactNode | null;
30+
onReEnterEmailClick: () => void;
3031
onResendClick: () => Promise<boolean>;
3132
onSubmitForm: (ev: React.FormEvent) => void;
3233
}
@@ -37,6 +38,7 @@ interface CheckEmailProps {
3738
export const CheckEmail: React.FC<CheckEmailProps> = ({
3839
email,
3940
errorText,
41+
onReEnterEmailClick,
4042
onSubmitForm,
4143
onResendClick,
4244
}) => {
@@ -50,13 +52,32 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
5052
return <>
5153
<EMailPromptIcon className="mx_AuthBody_emailPromptIcon--shifted" />
5254
<h1>{ _t("Check your email to continue") }</h1>
53-
<p>
54-
{ _t(
55-
"Follow the instructions sent to <b>%(email)s</b>",
56-
{ email: email },
57-
{ b: t => <b>{ t }</b> },
58-
) }
59-
</p>
55+
<div className="mx_AuthBody_text">
56+
<p>
57+
{ _t(
58+
"Follow the instructions sent to <b>%(email)s</b>",
59+
{ email: email },
60+
{ b: t => <b>{ t }</b> },
61+
) }
62+
</p>
63+
<div className="mx_AuthBody_did-not-receive">
64+
<span className="mx_VerifyEMailDialog_text-light">{ _t("Wrong email address?") }</span>
65+
<AccessibleButton
66+
className="mx_AuthBody_resend-button"
67+
kind="link"
68+
onClick={onReEnterEmailClick}
69+
>
70+
{ _t("Re-enter email address") }
71+
</AccessibleButton>
72+
</div>
73+
</div>
74+
{ errorText && <ErrorMessage message={errorText} /> }
75+
<input
76+
onClick={onSubmitForm}
77+
type="button"
78+
className="mx_Login_submit"
79+
value={_t("Next")}
80+
/>
6081
<div className="mx_AuthBody_did-not-receive">
6182
<span className="mx_VerifyEMailDialog_text-light">{ _t("Did not receive it?") }</span>
6283
<AccessibleButton
@@ -73,12 +94,5 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
7394
/>
7495
</AccessibleButton>
7596
</div>
76-
{ errorText && <ErrorMessage message={errorText} /> }
77-
<input
78-
onClick={onSubmitForm}
79-
type="button"
80-
className="mx_Login_submit"
81-
value={_t("Next")}
82-
/>
8397
</>;
8498
};

src/components/structures/auth/forgot-password/EnterEmail.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ import EmailField from "../../../views/auth/EmailField";
2222
import { ErrorMessage } from "../../ErrorMessage";
2323
import Spinner from "../../../views/elements/Spinner";
2424
import Field from "../../../views/elements/Field";
25+
import AccessibleButton from "../../../views/elements/AccessibleButton";
2526

2627
interface EnterEmailProps {
2728
email: string;
2829
errorText: string | ReactNode | null;
2930
homeserver: string;
3031
loading: boolean;
3132
onInputChanged: (stateKey: string, ev: React.FormEvent<HTMLInputElement>) => void;
33+
onLoginClick: () => void;
3234
onSubmitForm: (ev: React.FormEvent) => void;
3335
}
3436

@@ -41,6 +43,7 @@ export const EnterEmail: React.FC<EnterEmailProps> = ({
4143
homeserver,
4244
loading,
4345
onInputChanged,
46+
onLoginClick,
4447
onSubmitForm,
4548
}) => {
4649
const submitButtonChild = loading
@@ -92,6 +95,15 @@ export const EnterEmail: React.FC<EnterEmailProps> = ({
9295
>
9396
{ submitButtonChild }
9497
</button>
98+
<div className="mx_AuthBody_button-container">
99+
<AccessibleButton
100+
className="mx_AuthBody_sign-in-instead-button"
101+
element="button"
102+
kind="link"
103+
onClick={onLoginClick}>
104+
{ _t("Sign in instead") }
105+
</AccessibleButton>
106+
</div>
95107
</fieldset>
96108
</form>
97109
</>;

0 commit comments

Comments
 (0)