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

Commit 99659d2

Browse files
committed
Add password reset back options
1 parent ccfb455 commit 99659d2

File tree

9 files changed

+208
-59
lines changed

9 files changed

+208
-59
lines changed

res/css/views/auth/_AuthBody.pcss

Lines changed: 38 additions & 9 deletions
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

Lines changed: 10 additions & 1 deletion
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/PasswordReset.ts

Lines changed: 0 additions & 20 deletions
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

Lines changed: 31 additions & 5 deletions
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,
@@ -277,22 +280,43 @@ export default class ForgotPassword extends React.Component<Props, State> {
277280
{
278281
email: this.state.email,
279282
errorText: this.state.errorText,
283+
onCloseClick: () => {
284+
modal.close();
285+
this.setState({ phase: Phase.PasswordInput });
286+
},
287+
onReEnterEmailClick: () => {
288+
modal.close();
289+
this.setState({ phase: Phase.EnterEmail });
290+
},
280291
onResendClick: this.sendVerificationMail,
281292
},
282293
"mx_VerifyEMailDialog",
283294
false,
284295
false,
285296
{
286-
// this modal cannot be dismissed except reset is done or forced
287297
onBeforeClose: async (reason?: string) => {
288-
return this.state.phase === Phase.Done || reason === "force";
298+
if (reason === "backgroundClick") {
299+
// Modal dismissed by clicking the background.
300+
// Go one phase back.
301+
this.setState({ phase: Phase.PasswordInput });
302+
}
303+
304+
return true;
289305
},
290306
},
291307
);
292308

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

298322
private onSubmitForm = async (ev: React.FormEvent): Promise<void> => {
@@ -339,6 +363,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
339363
homeserver={this.props.serverConfig.hsName}
340364
loading={this.state.phase === Phase.SendingEmail}
341365
onInputChanged={this.onInputChanged}
366+
onLoginClick={this.props.onLoginClick}
342367
onSubmitForm={this.onSubmitForm}
343368
/>;
344369
}
@@ -374,6 +399,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
374399
return <CheckEmail
375400
email={this.state.email}
376401
errorText={this.state.errorText}
402+
onReEnterEmailClick={() => this.setState({ phase: Phase.EnterEmail })}
377403
onResendClick={this.sendVerificationMail}
378404
onSubmitForm={this.onSubmitForm}
379405
/>;

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

Lines changed: 28 additions & 14 deletions
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

Lines changed: 12 additions & 0 deletions
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
</>;

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,16 @@ import { ErrorMessage } from "../../ErrorMessage";
2727
interface Props {
2828
email: string;
2929
errorText: string | null;
30+
onCloseClick: () => void;
31+
onReEnterEmailClick: () => void;
3032
onResendClick: () => Promise<boolean>;
3133
}
3234

3335
export const VerifyEmailModal: React.FC<Props> = ({
3436
email,
3537
errorText,
38+
onCloseClick,
39+
onReEnterEmailClick,
3640
onResendClick,
3741
}) => {
3842
const { toggle: toggleTooltipVisible, value: tooltipVisible } = useTimeoutToggle(false, 2500);
@@ -57,7 +61,8 @@ export const VerifyEmailModal: React.FC<Props> = ({
5761
},
5862
) }
5963
</p>
60-
<div className="mx_AuthBody_did-not-receive mx_AuthBody_did-not-receive--centered">
64+
65+
<div className="mx_AuthBody_did-not-receive">
6166
<span className="mx_VerifyEMailDialog_text-light">{ _t("Did not receive it?") }</span>
6267
<AccessibleButton
6368
className="mx_AuthBody_resend-button"
@@ -74,5 +79,22 @@ export const VerifyEmailModal: React.FC<Props> = ({
7479
</AccessibleButton>
7580
{ errorText && <ErrorMessage message={errorText} /> }
7681
</div>
82+
83+
<div className="mx_AuthBody_did-not-receive">
84+
<span className="mx_VerifyEMailDialog_text-light">{ _t("Wrong email address?") }</span>
85+
<AccessibleButton
86+
className="mx_AuthBody_resend-button"
87+
kind="link"
88+
onClick={onReEnterEmailClick}
89+
>
90+
{ _t("Re-enter email address") }
91+
</AccessibleButton>
92+
</div>
93+
94+
<AccessibleButton
95+
onClick={onCloseClick}
96+
className="mx_Dialog_cancelButton"
97+
aria-label={_t("Close dialog")}
98+
/>
7799
</>;
78100
};

src/i18n/strings/en_EN.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3496,13 +3496,16 @@
34963496
"Clear personal data": "Clear personal data",
34973497
"Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.",
34983498
"Follow the instructions sent to <b>%(email)s</b>": "Follow the instructions sent to <b>%(email)s</b>",
3499+
"Wrong email address?": "Wrong email address?",
3500+
"Re-enter email address": "Re-enter email address",
34993501
"Did not receive it?": "Did not receive it?",
35003502
"Verification link email resent!": "Verification link email resent!",
35013503
"Send email": "Send email",
35023504
"Enter your email to reset password": "Enter your email to reset password",
35033505
"<b>%(homeserver)s</b> will send you a verification link to let you reset your password.": "<b>%(homeserver)s</b> will send you a verification link to let you reset your password.",
35043506
"The email address linked to your account must be entered.": "The email address linked to your account must be entered.",
35053507
"The email address doesn't appear to be valid.": "The email address doesn't appear to be valid.",
3508+
"Sign in instead": "Sign in instead",
35063509
"Verify your email to continue": "Verify your email to continue",
35073510
"We need to know it’s you before resetting your password.\n Click the link in the email we just sent to <b>%(email)s</b>": "We need to know it’s you before resetting your password.\n Click the link in the email we just sent to <b>%(email)s</b>",
35083511
"Commands": "Commands",

0 commit comments

Comments
 (0)