Merge pull request #2578 from jryans/auth-reset-password
Style reset password to match design
This commit is contained in:
commit
30395f064d
3 changed files with 191 additions and 106 deletions
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2017, 2018 New Vector Ltd
|
Copyright 2017, 2018, 2019 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -25,6 +25,18 @@ import SdkConfig from "../../../SdkConfig";
|
||||||
|
|
||||||
import PasswordReset from "../../../PasswordReset";
|
import PasswordReset from "../../../PasswordReset";
|
||||||
|
|
||||||
|
// Phases
|
||||||
|
// Show controls to configure server details
|
||||||
|
const PHASE_SERVER_DETAILS = 0;
|
||||||
|
// Show the forgot password inputs
|
||||||
|
const PHASE_FORGOT = 1;
|
||||||
|
// Email is in the process of being sent
|
||||||
|
const PHASE_SENDING_EMAIL = 2;
|
||||||
|
// Email has been sent
|
||||||
|
const PHASE_EMAIL_SENT = 3;
|
||||||
|
// User has clicked the link in email and completed reset
|
||||||
|
const PHASE_DONE = 4;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'ForgotPassword',
|
displayName: 'ForgotPassword',
|
||||||
|
|
||||||
|
@ -47,28 +59,29 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl,
|
enteredHsUrl: this.props.customHsUrl || this.props.defaultHsUrl,
|
||||||
enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl,
|
enteredIsUrl: this.props.customIsUrl || this.props.defaultIsUrl,
|
||||||
progress: null,
|
phase: PHASE_FORGOT,
|
||||||
password: null,
|
email: "",
|
||||||
password2: null,
|
password: "",
|
||||||
|
password2: "",
|
||||||
errorText: null,
|
errorText: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
submitPasswordReset: function(hsUrl, identityUrl, email, password) {
|
submitPasswordReset: function(hsUrl, identityUrl, email, password) {
|
||||||
this.setState({
|
this.setState({
|
||||||
progress: "sending_email",
|
phase: PHASE_SENDING_EMAIL,
|
||||||
});
|
});
|
||||||
this.reset = new PasswordReset(hsUrl, identityUrl);
|
this.reset = new PasswordReset(hsUrl, identityUrl);
|
||||||
this.reset.resetPassword(email, password).done(() => {
|
this.reset.resetPassword(email, password).done(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
progress: "sent_email",
|
phase: PHASE_EMAIL_SENT,
|
||||||
});
|
});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
|
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
|
||||||
this.setState({
|
this.setState({
|
||||||
progress: null,
|
phase: PHASE_FORGOT,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -80,7 +93,7 @@ module.exports = React.createClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.reset.checkEmailLinkClicked().done((res) => {
|
this.reset.checkEmailLinkClicked().done((res) => {
|
||||||
this.setState({ progress: "complete" });
|
this.setState({ phase: PHASE_DONE });
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
this.showErrorDialog(err.message);
|
this.showErrorDialog(err.message);
|
||||||
});
|
});
|
||||||
|
@ -126,7 +139,7 @@ module.exports = React.createClass({
|
||||||
onFinished: (confirmed) => {
|
onFinished: (confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
this.submitPasswordReset(
|
this.submitPasswordReset(
|
||||||
this.state.enteredHomeserverUrl, this.state.enteredIdentityServerUrl,
|
this.state.enteredHsUrl, this.state.enteredIsUrl,
|
||||||
this.state.email, this.state.password,
|
this.state.email, this.state.password,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -153,14 +166,29 @@ module.exports = React.createClass({
|
||||||
onServerConfigChange: function(config) {
|
onServerConfigChange: function(config) {
|
||||||
const newState = {};
|
const newState = {};
|
||||||
if (config.hsUrl !== undefined) {
|
if (config.hsUrl !== undefined) {
|
||||||
newState.enteredHomeserverUrl = config.hsUrl;
|
newState.enteredHsUrl = config.hsUrl;
|
||||||
}
|
}
|
||||||
if (config.isUrl !== undefined) {
|
if (config.isUrl !== undefined) {
|
||||||
newState.enteredIdentityServerUrl = config.isUrl;
|
newState.enteredIsUrl = config.isUrl;
|
||||||
}
|
}
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onServerDetailsNextPhaseClick(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.setState({
|
||||||
|
phase: PHASE_FORGOT,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onEditServerDetailsClick(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.setState({
|
||||||
|
phase: PHASE_SERVER_DETAILS,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
onLoginClick: function(ev) {
|
onLoginClick: function(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
@ -175,94 +203,151 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
renderServerDetails() {
|
||||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
|
||||||
const AuthHeader = sdk.getComponent("auth.AuthHeader");
|
|
||||||
const AuthBody = sdk.getComponent("auth.AuthBody");
|
|
||||||
const ServerConfig = sdk.getComponent("auth.ServerConfig");
|
const ServerConfig = sdk.getComponent("auth.ServerConfig");
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||||
|
|
||||||
let resetPasswordJsx;
|
if (SdkConfig.get()['disable_custom_urls']) {
|
||||||
|
return null;
|
||||||
if (this.state.progress === "sending_email") {
|
|
||||||
resetPasswordJsx = <Spinner />;
|
|
||||||
} else if (this.state.progress === "sent_email") {
|
|
||||||
resetPasswordJsx = (
|
|
||||||
<div>
|
|
||||||
{ _t("An email has been sent to %(emailAddress)s. Once you've followed the link it contains, " +
|
|
||||||
"click below.", { emailAddress: this.state.email }) }
|
|
||||||
<br />
|
|
||||||
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
|
||||||
value={_t('I have verified my email address')} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (this.state.progress === "complete") {
|
|
||||||
resetPasswordJsx = (
|
|
||||||
<div>
|
|
||||||
<p>{ _t('Your password has been reset') }.</p>
|
|
||||||
<p>{ _t('You have been logged out of all devices and will no longer receive push notifications. ' +
|
|
||||||
'To re-enable notifications, sign in again on each device') }.</p>
|
|
||||||
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
|
|
||||||
value={_t('Return to login screen')} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let serverConfigSection;
|
|
||||||
if (!SdkConfig.get()['disable_custom_urls']) {
|
|
||||||
serverConfigSection = (
|
|
||||||
<ServerConfig ref="serverConfig"
|
|
||||||
defaultHsUrl={this.props.defaultHsUrl}
|
|
||||||
defaultIsUrl={this.props.defaultIsUrl}
|
|
||||||
customHsUrl={this.props.customHsUrl}
|
|
||||||
customIsUrl={this.props.customIsUrl}
|
|
||||||
onServerConfigChange={this.onServerConfigChange}
|
|
||||||
delayTimeMs={0} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<ServerConfig
|
||||||
|
defaultHsUrl={this.props.defaultHsUrl}
|
||||||
|
defaultIsUrl={this.props.defaultIsUrl}
|
||||||
|
customHsUrl={this.state.enteredHsUrl}
|
||||||
|
customIsUrl={this.state.enteredIsUrl}
|
||||||
|
onServerConfigChange={this.onServerConfigChange}
|
||||||
|
delayTimeMs={0} />
|
||||||
|
<AccessibleButton className="mx_Login_submit"
|
||||||
|
onClick={this.onServerDetailsNextPhaseClick}
|
||||||
|
>
|
||||||
|
{_t("Next")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderForgot() {
|
||||||
let errorText = null;
|
let errorText = null;
|
||||||
const err = this.state.errorText || this.props.defaultServerDiscoveryError;
|
const err = this.state.errorText || this.props.defaultServerDiscoveryError;
|
||||||
if (err) {
|
if (err) {
|
||||||
errorText = <div className="mx_Login_error">{ err }</div>;
|
errorText = <div className="mx_Login_error">{ err }</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPasswordJsx = (
|
let yourMatrixAccountText = _t('Your account');
|
||||||
<div>
|
try {
|
||||||
<p>
|
const parsedHsUrl = new URL(this.state.enteredHsUrl);
|
||||||
{ _t('To reset your password, enter the email address linked to your account') }:
|
yourMatrixAccountText = _t('Your account on %(serverName)s', {
|
||||||
</p>
|
serverName: parsedHsUrl.hostname,
|
||||||
<div>
|
});
|
||||||
|
} catch (e) {
|
||||||
|
errorText = <div className="mx_Login_error">{_t(
|
||||||
|
"The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please " +
|
||||||
|
"enter a valid URL including the protocol prefix.",
|
||||||
|
{
|
||||||
|
hsUrl: this.state.enteredHsUrl,
|
||||||
|
})}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If custom URLs are allowed, wire up the server details edit link.
|
||||||
|
let editLink = null;
|
||||||
|
if (!SdkConfig.get()['disable_custom_urls']) {
|
||||||
|
editLink = <a className="mx_AuthBody_editServerDetails"
|
||||||
|
href="#" onClick={this.onEditServerDetailsClick}
|
||||||
|
>
|
||||||
|
{_t('Change')}
|
||||||
|
</a>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<h3>
|
||||||
|
{yourMatrixAccountText}
|
||||||
|
{editLink}
|
||||||
|
</h3>
|
||||||
|
{errorText}
|
||||||
<form onSubmit={this.onSubmitForm}>
|
<form onSubmit={this.onSubmitForm}>
|
||||||
<input className="mx_Login_field" ref="user" type="text"
|
<div className="mx_AuthBody_fieldRow">
|
||||||
|
<input className="mx_Login_field" type="text"
|
||||||
name="reset_email" // define a name so browser's password autofill gets less confused
|
name="reset_email" // define a name so browser's password autofill gets less confused
|
||||||
value={this.state.email}
|
value={this.state.email}
|
||||||
onChange={this.onInputChanged.bind(this, "email")}
|
onChange={this.onInputChanged.bind(this, "email")}
|
||||||
placeholder={_t('Email address')} autoFocus />
|
placeholder={_t('Email')} autoFocus />
|
||||||
<br />
|
</div>
|
||||||
<input className="mx_Login_field" ref="pass" type="password"
|
<div className="mx_AuthBody_fieldRow">
|
||||||
|
<input className="mx_Login_field" type="password"
|
||||||
name="reset_password"
|
name="reset_password"
|
||||||
value={this.state.password}
|
value={this.state.password}
|
||||||
onChange={this.onInputChanged.bind(this, "password")}
|
onChange={this.onInputChanged.bind(this, "password")}
|
||||||
placeholder={_t('New password')} />
|
placeholder={_t('Password')} />
|
||||||
<br />
|
<input className="mx_Login_field" type="password"
|
||||||
<input className="mx_Login_field" ref="pass" type="password"
|
|
||||||
name="reset_password_confirm"
|
name="reset_password_confirm"
|
||||||
value={this.state.password2}
|
value={this.state.password2}
|
||||||
onChange={this.onInputChanged.bind(this, "password2")}
|
onChange={this.onInputChanged.bind(this, "password2")}
|
||||||
placeholder={_t('Confirm your new password')} />
|
placeholder={_t('Confirm')} />
|
||||||
<br />
|
</div>
|
||||||
|
<span>{_t(
|
||||||
|
'A verification email will be sent to your inbox to confirm ' +
|
||||||
|
'setting your new password.',
|
||||||
|
)}</span>
|
||||||
<input className="mx_Login_submit" type="submit" value={_t('Send Reset Email')} />
|
<input className="mx_Login_submit" type="submit" value={_t('Send Reset Email')} />
|
||||||
</form>
|
</form>
|
||||||
{ serverConfigSection }
|
|
||||||
{ errorText }
|
|
||||||
<a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#">
|
<a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#">
|
||||||
{_t('Sign in instead')}
|
{_t('Sign in instead')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>;
|
||||||
</div>
|
},
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
renderSendingEmail() {
|
||||||
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
return <Spinner />;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderEmailSent() {
|
||||||
|
return <div>
|
||||||
|
{_t("An email has been sent to %(emailAddress)s. Once you've followed the " +
|
||||||
|
"link it contains, click below.", { emailAddress: this.state.email })}
|
||||||
|
<br />
|
||||||
|
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
||||||
|
value={_t('I have verified my email address')} />
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderDone() {
|
||||||
|
return <div>
|
||||||
|
<p>{_t("Your password has been reset.")}</p>
|
||||||
|
<p>{_t(
|
||||||
|
"You have been logged out of all devices and will no longer receive " +
|
||||||
|
"push notifications. To re-enable notifications, sign in again on each " +
|
||||||
|
"device.",
|
||||||
|
)}</p>
|
||||||
|
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
|
||||||
|
value={_t('Return to login screen')} />
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||||
|
const AuthHeader = sdk.getComponent("auth.AuthHeader");
|
||||||
|
const AuthBody = sdk.getComponent("auth.AuthBody");
|
||||||
|
|
||||||
|
let resetPasswordJsx;
|
||||||
|
switch (this.state.phase) {
|
||||||
|
case PHASE_SERVER_DETAILS:
|
||||||
|
resetPasswordJsx = this.renderServerDetails();
|
||||||
|
break;
|
||||||
|
case PHASE_FORGOT:
|
||||||
|
resetPasswordJsx = this.renderForgot();
|
||||||
|
break;
|
||||||
|
case PHASE_SENDING_EMAIL:
|
||||||
|
resetPasswordJsx = this.renderSendingEmail();
|
||||||
|
break;
|
||||||
|
case PHASE_EMAIL_SENT:
|
||||||
|
resetPasswordJsx = this.renderEmailSent();
|
||||||
|
break;
|
||||||
|
case PHASE_DONE:
|
||||||
|
resetPasswordJsx = this.renderDone();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthPage>
|
<AuthPage>
|
||||||
|
|
|
@ -31,10 +31,10 @@ import { AutoDiscovery } from "matrix-js-sdk";
|
||||||
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
|
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
|
||||||
|
|
||||||
// Phases
|
// Phases
|
||||||
// Show the appropriate login flow(s) for the server
|
|
||||||
const PHASE_LOGIN = 0;
|
|
||||||
// Show controls to configure server details
|
// Show controls to configure server details
|
||||||
const PHASE_SERVER_DETAILS = 1;
|
const PHASE_SERVER_DETAILS = 0;
|
||||||
|
// Show the appropriate login flow(s) for the server
|
||||||
|
const PHASE_LOGIN = 1;
|
||||||
|
|
||||||
// Enable phases for login
|
// Enable phases for login
|
||||||
const PHASES_ENABLED = true;
|
const PHASES_ENABLED = true;
|
||||||
|
@ -514,7 +514,6 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PHASES_ENABLED && this.state.phase !== PHASE_SERVER_DETAILS) {
|
if (PHASES_ENABLED && this.state.phase !== PHASE_SERVER_DETAILS) {
|
||||||
// TODO: ...
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1335,16 +1335,17 @@
|
||||||
"A new password must be entered.": "A new password must be entered.",
|
"A new password must be entered.": "A new password must be entered.",
|
||||||
"New passwords must match each other.": "New passwords must match each other.",
|
"New passwords must match each other.": "New passwords must match each other.",
|
||||||
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
|
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
|
||||||
"An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.",
|
"Your account": "Your account",
|
||||||
"I have verified my email address": "I have verified my email address",
|
"Your account on %(serverName)s": "Your account on %(serverName)s",
|
||||||
"Your password has been reset": "Your password has been reset",
|
"The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please enter a valid URL including the protocol prefix.": "The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please enter a valid URL including the protocol prefix.",
|
||||||
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device",
|
"A verification email will be sent to your inbox to confirm setting your new password.": "A verification email will be sent to your inbox to confirm setting your new password.",
|
||||||
"Return to login screen": "Return to login screen",
|
|
||||||
"To reset your password, enter the email address linked to your account": "To reset your password, enter the email address linked to your account",
|
|
||||||
"New password": "New password",
|
|
||||||
"Confirm your new password": "Confirm your new password",
|
|
||||||
"Send Reset Email": "Send Reset Email",
|
"Send Reset Email": "Send Reset Email",
|
||||||
"Sign in instead": "Sign in instead",
|
"Sign in instead": "Sign in instead",
|
||||||
|
"An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.",
|
||||||
|
"I have verified my email address": "I have verified my email address",
|
||||||
|
"Your password has been reset.": "Your password has been reset.",
|
||||||
|
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.",
|
||||||
|
"Return to login screen": "Return to login screen",
|
||||||
"Set a new password": "Set a new password",
|
"Set a new password": "Set a new password",
|
||||||
"Invalid homeserver discovery response": "Invalid homeserver discovery response",
|
"Invalid homeserver discovery response": "Invalid homeserver discovery response",
|
||||||
"Invalid identity server discovery response": "Invalid identity server discovery response",
|
"Invalid identity server discovery response": "Invalid identity server discovery response",
|
||||||
|
|
Loading…
Reference in a new issue