From af8c3edba6d7cdd3bb0acb1f741f7eeaf27020a6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Mar 2017 14:56:26 +0000 Subject: [PATCH 1/9] Support adding phone numbers in UserSettings --- src/AddThreepid.js | 52 +++++++- src/components/structures/UserSettings.js | 146 +++++++++++++++++++--- 2 files changed, 181 insertions(+), 17 deletions(-) diff --git a/src/AddThreepid.js b/src/AddThreepid.js index d6a1d58aa0..44d709371b 100644 --- a/src/AddThreepid.js +++ b/src/AddThreepid.js @@ -1,5 +1,6 @@ /* Copyright 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -51,11 +52,35 @@ class AddThreepid { }); } + /** + * Attempt to add a msisdn threepid. This will trigger a side-effect of + * sending a test message to the provided phone number. + * @param {string} emailAddress The email address to add + * @param {boolean} bind If True, bind this phone number to this mxid on the Identity Server + * @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken(). + */ + addMsisdn(phoneCountry, phoneNumber, bind) { + this.bind = bind; + return MatrixClientPeg.get().requestAdd3pidMsisdnToken( + phoneCountry, phoneNumber, this.clientSecret, 1, + ).then((res) => { + this.sessionId = res.sid; + return res; + }, function(err) { + if (err.errcode == 'M_THREEPID_IN_USE') { + err.message = "This phone number is already in use"; + } else if (err.httpStatus) { + err.message = err.message + ` (Status ${err.httpStatus})`; + } + throw err; + }); + } + /** * Checks if the email link has been clicked by attempting to add the threepid - * @return {Promise} Resolves if the password was reset. Rejects with an object + * @return {Promise} Resolves if the email address was added. Rejects with an object * with a "message" property which contains a human-readable message detailing why - * the reset failed, e.g. "There is no mapped matrix user ID for the given email address". + * the request failed. */ checkEmailLinkClicked() { var identityServerDomain = MatrixClientPeg.get().idBaseUrl.split("://")[1]; @@ -73,6 +98,29 @@ class AddThreepid { throw err; }); } + + /** + * Takes a phone number verification code as entered by the user and validates + * it with the ID server, then if successful, adds the phone number. + * @return {Promise} Resolves if the email address was added. Rejects with an object + * with a "message" property which contains a human-readable message detailing why + * the request failed. + */ + haveMsisdnToken(token) { + return MatrixClientPeg.get().submitMsisdnToken( + this.sessionId, this.clientSecret, token, + ).then((result) => { + if (result.errcode) { + throw result; + } + const identityServerDomain = MatrixClientPeg.get().idBaseUrl.split("://")[1]; + return MatrixClientPeg.get().addThreePid({ + sid: this.sessionId, + client_secret: this.clientSecret, + id_server: identityServerDomain + }, this.bind); + }); + } } module.exports = AddThreepid; diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index febdccd9c3..ed8a271241 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -132,13 +132,17 @@ module.exports = React.createClass({ threePids: [], phase: "UserSettings.LOADING", // LOADING, DISPLAY email_add_pending: false, + msisdn_add_pending: false, vectorVersion: null, rejectingInvites: false, + phoneCountry: null, + phoneNumber: "", }; }, componentWillMount: function() { this._unmounted = false; + this._addThreepid = null; if (PlatformPeg.get()) { q().then(() => { @@ -214,6 +218,14 @@ module.exports = React.createClass({ }); }, + _onPhoneCountryChange: function(phoneCountry) { + this.setState({ phoneCountry: phoneCountry }); + }, + + _onPhoneNumberChange: function(ev) { + this.setState({ phoneNumber: ev.target.value }); + }, + onAction: function(payload) { if (payload.action === "notifier_enabled") { this.forceUpdate(); @@ -315,12 +327,26 @@ module.exports = React.createClass({ UserSettingsStore.setEnableNotifications(event.target.checked); }, - onAddThreepidClicked: function(value, shouldSubmit) { + _onAddEmailEditFinished: function(value, shouldSubmit) { if (!shouldSubmit) return; + this._addEmail(); + }, + + _onAddMsisdnEditFinished: function(value, shouldSubmit) { + if (!shouldSubmit) return; + this._addMsisdn(); + }, + + _onAddMsisdnSubmit: function(ev) { + ev.preventDefault(); + this._addMsisdn(); + }, + + _addEmail: function() { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - var email_address = this.refs.add_threepid_input.value; + var email_address = this.refs.add_email_input.value; if (!Email.looksValid(email_address)) { Modal.createDialog(ErrorDialog, { title: "Invalid Email Address", @@ -328,10 +354,10 @@ module.exports = React.createClass({ }); return; } - this.add_threepid = new AddThreepid(); + this._addThreepid = new AddThreepid(); // we always bind emails when registering, so let's do the // same here. - this.add_threepid.addEmailAddress(email_address, true).done(() => { + this._addThreepid.addEmailAddress(email_address, true).done(() => { Modal.createDialog(QuestionDialog, { title: "Verification Pending", description: "Please check your email and click on the link it contains. Once this is done, click continue.", @@ -346,10 +372,69 @@ module.exports = React.createClass({ description: "Unable to add email address" }); }); - ReactDOM.findDOMNode(this.refs.add_threepid_input).blur(); + ReactDOM.findDOMNode(this.refs.add_email_input).blur(); this.setState({email_add_pending: true}); }, + _addMsisdn: function() { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + + this._addThreepid = new AddThreepid(); + // we always phone numbers when registering, so let's do the + // same here. + this._addThreepid.addMsisdn(this.state.phoneCountry, this.state.phoneNumber, true).then((resp) => { + this._promptForMsisdnVerificationCode(resp.msisdn); + }).catch((err) => { + console.error("Unable to add phone number: " + err); + let msg = err.message; + Modal.createDialog(ErrorDialog, { + title: "Error", + description: msg, + }); + }).finally(() => { + this.setState({msisdn_add_pending: false}); + }).done();; + ReactDOM.findDOMNode(this.refs.add_msisdn_input).blur(); + this.setState({msisdn_add_pending: true}); + }, + + _promptForMsisdnVerificationCode(msisdn, err) { + const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); + let msgElements = [ +
A text message has been sent to +{msisdn}. + Please enter the verification code it contains
+ ]; + if (err) { + let msg = err.error; + if (err.errcode == 'M_THREEPID_AUTH_FAILED') { + msg = "Incorrect verification code"; + } + msgElements.push(
{msg}
); + } + Modal.createDialog(TextInputDialog, { + title: "Enter Code", + description:
{msgElements}
, + button: "Submit", + onFinished: (should_verify, token) => { + if (!should_verify) { + this._addThreepid = null; + return; + } + this.setState({msisdn_add_pending: true}); + this._addThreepid.haveMsisdnToken(token).then(() => { + this._addThreepid = null; + this.setState({phoneNumber: ''}); + return this._refreshFromServer(); + }).catch((err) => { + this._promptForMsisdnVerificationCode(msisdn, err); + }).finally(() => { + this.setState({msisdn_add_pending: false}); + }).done(); + } + }); + }, + onRemoveThreepidClicked: function(threepid) { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); Modal.createDialog(QuestionDialog, { @@ -385,8 +470,8 @@ module.exports = React.createClass({ }, verifyEmailAddress: function() { - this.add_threepid.checkEmailLinkClicked().done(() => { - this.add_threepid = undefined; + this._addThreepid.checkEmailLinkClicked().done(() => { + this._addThreepid = null; this.setState({ phase: "UserSettings.LOADING", }); @@ -795,30 +880,61 @@ module.exports = React.createClass({ ); }); - var addThreepidSection; + let addEmailSection; + let addMsisdnSection; if (this.state.email_add_pending) { - addThreepidSection = ; + addEmailSection = ; } else if (!MatrixClientPeg.get().isGuest()) { - addThreepidSection = ( -
+ addEmailSection = ( +
+ onValueChanged={ this._onAddEmailEditFinished } />
- Add + Add
); } - threepidsSection.push(addThreepidSection); + if (this.state.msisdn_add_pending) { + addMsisdnSection = ; + } else if (!MatrixClientPeg.get().isGuest()) { + const CountryDropdown = sdk.getComponent('views.login.CountryDropdown'); + addMsisdnSection = ( +
+
+
+
+
+ + + +
+
+ Add +
+
+ ); + } + threepidsSection.push(addEmailSection); + threepidsSection.push(addMsisdnSection); var accountJsx; From 6a37fc432544c3680a03a9f08834103e13268341 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Mar 2017 12:00:16 +0000 Subject: [PATCH 2/9] Comment typos --- src/AddThreepid.js | 5 +++-- src/components/structures/UserSettings.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/AddThreepid.js b/src/AddThreepid.js index 44d709371b..c89de4f5fa 100644 --- a/src/AddThreepid.js +++ b/src/AddThreepid.js @@ -55,7 +55,8 @@ class AddThreepid { /** * Attempt to add a msisdn threepid. This will trigger a side-effect of * sending a test message to the provided phone number. - * @param {string} emailAddress The email address to add + * @param {string} phoneCountry The ISO 2 letter code of the country to resolve phoneNumber in + * @param {string} phoneNumber The national or international formatted phone number to add * @param {boolean} bind If True, bind this phone number to this mxid on the Identity Server * @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken(). */ @@ -102,7 +103,7 @@ class AddThreepid { /** * Takes a phone number verification code as entered by the user and validates * it with the ID server, then if successful, adds the phone number. - * @return {Promise} Resolves if the email address was added. Rejects with an object + * @return {Promise} Resolves if the phone number was added. Rejects with an object * with a "message" property which contains a human-readable message detailing why * the request failed. */ diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 6626d2c400..b50c3318ce 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -387,7 +387,7 @@ module.exports = React.createClass({ const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); this._addThreepid = new AddThreepid(); - // we always phone numbers when registering, so let's do the + // we always bind phone numbers when registering, so let's do the // same here. this._addThreepid.addMsisdn(this.state.phoneCountry, this.state.phoneNumber, true).then((resp) => { this._promptForMsisdnVerificationCode(resp.msisdn); From 4cd24d15d453b749ebcc967e30d15207fd10ebc3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Mar 2017 15:18:27 +0000 Subject: [PATCH 3/9] Factor out AddPhoneNumber to a separate component --- src/component-index.js | 2 + src/components/structures/UserSettings.js | 114 +----------- .../views/settings/AddPhoneNumber.js | 170 ++++++++++++++++++ 3 files changed, 177 insertions(+), 109 deletions(-) create mode 100644 src/components/views/settings/AddPhoneNumber.js diff --git a/src/component-index.js b/src/component-index.js index c83c0dbb11..d6873c6dfd 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -229,6 +229,8 @@ import views$rooms$TopUnreadMessagesBar from './components/views/rooms/TopUnread views$rooms$TopUnreadMessagesBar && (module.exports.components['views.rooms.TopUnreadMessagesBar'] = views$rooms$TopUnreadMessagesBar); import views$rooms$UserTile from './components/views/rooms/UserTile'; views$rooms$UserTile && (module.exports.components['views.rooms.UserTile'] = views$rooms$UserTile); +import views$settings$AddPhoneNumber from './components/views/settings/AddPhoneNumber'; +views$settings$AddPhoneNumber && (module.exports.components['views.settings.AddPhoneNumber'] = views$settings$AddPhoneNumber); import views$settings$ChangeAvatar from './components/views/settings/ChangeAvatar'; views$settings$ChangeAvatar && (module.exports.components['views.settings.ChangeAvatar'] = views$settings$ChangeAvatar); import views$settings$ChangeDisplayName from './components/views/settings/ChangeDisplayName'; diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index b50c3318ce..5633bd0bc7 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -135,8 +136,6 @@ module.exports = React.createClass({ msisdn_add_pending: false, vectorVersion: null, rejectingInvites: false, - phoneCountry: null, - phoneNumber: "", }; }, @@ -218,14 +217,6 @@ module.exports = React.createClass({ }); }, - _onPhoneCountryChange: function(phoneCountry) { - this.setState({ phoneCountry: phoneCountry }); - }, - - _onPhoneNumberChange: function(ev) { - this.setState({ phoneNumber: ev.target.value }); - }, - onAction: function(payload) { if (payload.action === "notifier_enabled") { this.forceUpdate(); @@ -338,16 +329,6 @@ module.exports = React.createClass({ this._addEmail(); }, - _onAddMsisdnEditFinished: function(value, shouldSubmit) { - if (!shouldSubmit) return; - this._addMsisdn(); - }, - - _onAddMsisdnSubmit: function(ev) { - ev.preventDefault(); - this._addMsisdn(); - }, - _addEmail: function() { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); @@ -382,65 +363,6 @@ module.exports = React.createClass({ this.setState({email_add_pending: true}); }, - _addMsisdn: function() { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - - this._addThreepid = new AddThreepid(); - // we always bind phone numbers when registering, so let's do the - // same here. - this._addThreepid.addMsisdn(this.state.phoneCountry, this.state.phoneNumber, true).then((resp) => { - this._promptForMsisdnVerificationCode(resp.msisdn); - }).catch((err) => { - console.error("Unable to add phone number: " + err); - let msg = err.message; - Modal.createDialog(ErrorDialog, { - title: "Error", - description: msg, - }); - }).finally(() => { - this.setState({msisdn_add_pending: false}); - }).done();; - ReactDOM.findDOMNode(this.refs.add_msisdn_input).blur(); - this.setState({msisdn_add_pending: true}); - }, - - _promptForMsisdnVerificationCode(msisdn, err) { - const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); - let msgElements = [ -
A text message has been sent to +{msisdn}. - Please enter the verification code it contains
- ]; - if (err) { - let msg = err.error; - if (err.errcode == 'M_THREEPID_AUTH_FAILED') { - msg = "Incorrect verification code"; - } - msgElements.push(
{msg}
); - } - Modal.createDialog(TextInputDialog, { - title: "Enter Code", - description:
{msgElements}
, - button: "Submit", - onFinished: (should_verify, token) => { - if (!should_verify) { - this._addThreepid = null; - return; - } - this.setState({msisdn_add_pending: true}); - this._addThreepid.haveMsisdnToken(token).then(() => { - this._addThreepid = null; - this.setState({phoneNumber: ''}); - return this._refreshFromServer(); - }).catch((err) => { - this._promptForMsisdnVerificationCode(msisdn, err); - }).finally(() => { - this.setState({msisdn_add_pending: false}); - }).done(); - } - }); - }, - onRemoveThreepidClicked: function(threepid) { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); Modal.createDialog(QuestionDialog, { @@ -897,7 +819,6 @@ module.exports = React.createClass({ ); }); let addEmailSection; - let addMsisdnSection; if (this.state.email_add_pending) { addEmailSection = ; } else if (!MatrixClientPeg.get().isGuest()) { @@ -920,35 +841,10 @@ module.exports = React.createClass({
); } - if (this.state.msisdn_add_pending) { - addMsisdnSection = ; - } else if (!MatrixClientPeg.get().isGuest()) { - const CountryDropdown = sdk.getComponent('views.login.CountryDropdown'); - addMsisdnSection = ( -
-
-
-
-
- - - -
-
- Add -
-
- ); - } + const AddPhoneNumber = sdk.getComponent('views.settings.AddPhoneNumber'); + const addMsisdnSection = ( + + ); threepidsSection.push(addEmailSection); threepidsSection.push(addMsisdnSection); diff --git a/src/components/views/settings/AddPhoneNumber.js b/src/components/views/settings/AddPhoneNumber.js new file mode 100644 index 0000000000..905a21f61d --- /dev/null +++ b/src/components/views/settings/AddPhoneNumber.js @@ -0,0 +1,170 @@ +/* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +import sdk from '../../../index'; +import AddThreepid from '../../../AddThreepid'; +import WithMatrixClient from '../../../wrappers/WithMatrixClient'; +import Modal from '../../../Modal'; + + +class AddPhoneNumber extends React.Component { + constructor(props, context) { + super(props, context); + + this._addThreepid = null; + this._addMsisdnInput = null; + + this.state = { + busy: false, + phoneCountry: null, + phoneNumber: "", + }; + + this._onPhoneCountryChange = this._onPhoneCountryChange.bind(this); + this._onPhoneNumberChange = this._onPhoneNumberChange.bind(this); + this._onAddMsisdnEditFinished = this._onAddMsisdnEditFinished.bind(this); + this._onAddMsisdnSubmit = this._onAddMsisdnSubmit.bind(this); + this._collectAddMsisdnInput = this._collectAddMsisdnInput.bind(this); + this._addMsisdn = this._addMsisdn.bind(this); + this._promptForMsisdnVerificationCode = this._promptForMsisdnVerificationCode.bind(this); + } + + _onPhoneCountryChange(phoneCountry) { + this.setState({ phoneCountry: phoneCountry }); + } + + _onPhoneNumberChange(ev) { + this.setState({ phoneNumber: ev.target.value }); + } + + _onAddMsisdnEditFinished(value, shouldSubmit) { + if (!shouldSubmit) return; + this._addMsisdn(); + } + + _onAddMsisdnSubmit(ev) { + ev.preventDefault(); + this._addMsisdn(); + } + + _collectAddMsisdnInput(e) { + this._addMsisdnInput = e; + } + + _addMsisdn() { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + + this._addThreepid = new AddThreepid(); + // we always bind phone numbers when registering, so let's do the + // same here. + this._addThreepid.addMsisdn(this.state.phoneCountry, this.state.phoneNumber, true).then((resp) => { + this._promptForMsisdnVerificationCode(resp.msisdn); + }).catch((err) => { + console.error("Unable to add phone number: " + err); + let msg = err.message; + Modal.createDialog(ErrorDialog, { + title: "Error", + description: msg, + }); + }).finally(() => { + this.setState({msisdn_add_pending: false}); + }).done();; + this._addMsisdnInput.blur(); + this.setState({msisdn_add_pending: true}); + } + + _promptForMsisdnVerificationCode(msisdn, err) { + const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); + let msgElements = [ +
A text message has been sent to +{msisdn}. + Please enter the verification code it contains
+ ]; + if (err) { + let msg = err.error; + if (err.errcode == 'M_THREEPID_AUTH_FAILED') { + msg = "Incorrect verification code"; + } + msgElements.push(
{msg}
); + } + Modal.createDialog(TextInputDialog, { + title: "Enter Code", + description:
{msgElements}
, + button: "Submit", + onFinished: (should_verify, token) => { + if (!should_verify) { + this._addThreepid = null; + return; + } + this.setState({msisdn_add_pending: true}); + this._addThreepid.haveMsisdnToken(token).then(() => { + this._addThreepid = null; + this.setState({phoneNumber: ''}); + if (this.props.onThreepidAdded) this.props.onThreepidAdded(); + }).catch((err) => { + this._promptForMsisdnVerificationCode(msisdn, err); + }).finally(() => { + this.setState({msisdn_add_pending: false}); + }).done(); + } + }); + } + + render() { + const Loader = sdk.getComponent("elements.Spinner"); + if (this.state.msisdn_add_pending) { + return ; + } else if (!this.props.matrixClient.isGuest()) { + const CountryDropdown = sdk.getComponent('views.login.CountryDropdown'); + // XXX: This CSS relies on the CSS surrounding it in UserSettings as its in + // a tabular format to align the submit buttons + return ( +
+
+
+
+
+ + + +
+
+ Add +
+
+ ); + } + } +} + +AddPhoneNumber.propTypes = { + matrixClient: React.PropTypes.object.isRequired, + onThreepidAdded: React.PropTypes.func, +}; + +AddPhoneNumber = WithMatrixClient(AddPhoneNumber); +export default AddPhoneNumber; From cca607d4694256fc0a0f701e563db7456acfb6fe Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Mar 2017 15:39:09 +0000 Subject: [PATCH 4/9] Make phone number form a bit more semantic --- src/components/views/settings/AddPhoneNumber.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/views/settings/AddPhoneNumber.js b/src/components/views/settings/AddPhoneNumber.js index 905a21f61d..e058fce0f2 100644 --- a/src/components/views/settings/AddPhoneNumber.js +++ b/src/components/views/settings/AddPhoneNumber.js @@ -134,11 +134,11 @@ class AddPhoneNumber extends React.Component { // XXX: This CSS relies on the CSS surrounding it in UserSettings as its in // a tabular format to align the submit buttons return ( -
+
- +
- +
- Add +
-
+ ); } } From e39979a61f284aa873f38f659ab3a57d7f58f8d0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Mar 2017 16:15:45 +0000 Subject: [PATCH 5/9] Convert to old style react class --- .../views/settings/AddPhoneNumber.js | 67 ++++++++----------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/src/components/views/settings/AddPhoneNumber.js b/src/components/views/settings/AddPhoneNumber.js index e058fce0f2..c64ed4b545 100644 --- a/src/components/views/settings/AddPhoneNumber.js +++ b/src/components/views/settings/AddPhoneNumber.js @@ -22,51 +22,50 @@ import WithMatrixClient from '../../../wrappers/WithMatrixClient'; import Modal from '../../../Modal'; -class AddPhoneNumber extends React.Component { - constructor(props, context) { - super(props, context); +export default WithMatrixClient(React.createClass({ + displayName: 'AddPhoneNumber', - this._addThreepid = null; - this._addMsisdnInput = null; + propTypes: { + matrixClient: React.PropTypes.object.isRequired, + onThreepidAdded: React.PropTypes.func, + }, - this.state = { + getInitialState: function() { + return { busy: false, phoneCountry: null, phoneNumber: "", }; + }, - this._onPhoneCountryChange = this._onPhoneCountryChange.bind(this); - this._onPhoneNumberChange = this._onPhoneNumberChange.bind(this); - this._onAddMsisdnEditFinished = this._onAddMsisdnEditFinished.bind(this); - this._onAddMsisdnSubmit = this._onAddMsisdnSubmit.bind(this); - this._collectAddMsisdnInput = this._collectAddMsisdnInput.bind(this); - this._addMsisdn = this._addMsisdn.bind(this); - this._promptForMsisdnVerificationCode = this._promptForMsisdnVerificationCode.bind(this); - } + componentWillMount: function() { + this._addThreepid = null; + this._addMsisdnInput = null; + }, - _onPhoneCountryChange(phoneCountry) { + _onPhoneCountryChange: function(phoneCountry) { this.setState({ phoneCountry: phoneCountry }); - } + }, - _onPhoneNumberChange(ev) { + _onPhoneNumberChange: function(ev) { this.setState({ phoneNumber: ev.target.value }); - } + }, - _onAddMsisdnEditFinished(value, shouldSubmit) { + _onAddMsisdnEditFinished: function(value, shouldSubmit) { if (!shouldSubmit) return; this._addMsisdn(); - } + }, - _onAddMsisdnSubmit(ev) { + _onAddMsisdnSubmit: function(ev) { ev.preventDefault(); this._addMsisdn(); - } + }, - _collectAddMsisdnInput(e) { + _collectAddMsisdnInput: function(e) { this._addMsisdnInput = e; - } + }, - _addMsisdn() { + _addMsisdn: function() { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); @@ -87,9 +86,9 @@ class AddPhoneNumber extends React.Component { }).done();; this._addMsisdnInput.blur(); this.setState({msisdn_add_pending: true}); - } + }, - _promptForMsisdnVerificationCode(msisdn, err) { + _promptForMsisdnVerificationCode:function (msisdn, err) { const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); let msgElements = [
A text message has been sent to +{msisdn}. @@ -123,9 +122,9 @@ class AddPhoneNumber extends React.Component { }).done(); } }); - } + }, - render() { + render: function() { const Loader = sdk.getComponent("elements.Spinner"); if (this.state.msisdn_add_pending) { return ; @@ -159,12 +158,4 @@ class AddPhoneNumber extends React.Component { ); } } -} - -AddPhoneNumber.propTypes = { - matrixClient: React.PropTypes.object.isRequired, - onThreepidAdded: React.PropTypes.func, -}; - -AddPhoneNumber = WithMatrixClient(AddPhoneNumber); -export default AddPhoneNumber; +})) From 6b78440466234c6cc378e6baecd7c986333cc2e5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Mar 2017 16:36:42 +0000 Subject: [PATCH 6/9] Unmounted guard --- src/components/views/settings/AddPhoneNumber.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/AddPhoneNumber.js b/src/components/views/settings/AddPhoneNumber.js index c64ed4b545..83c331dd33 100644 --- a/src/components/views/settings/AddPhoneNumber.js +++ b/src/components/views/settings/AddPhoneNumber.js @@ -41,6 +41,11 @@ export default WithMatrixClient(React.createClass({ componentWillMount: function() { this._addThreepid = null; this._addMsisdnInput = null; + this._unmounted = false; + }, + + componentWillUnmount: function() { + this._unmounted = true; }, _onPhoneCountryChange: function(phoneCountry) { @@ -67,7 +72,6 @@ export default WithMatrixClient(React.createClass({ _addMsisdn: function() { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); this._addThreepid = new AddThreepid(); // we always bind phone numbers when registering, so let's do the @@ -89,6 +93,7 @@ export default WithMatrixClient(React.createClass({ }, _promptForMsisdnVerificationCode:function (msisdn, err) { + if (this._unmounted) return; const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); let msgElements = [
A text message has been sent to +{msisdn}. From b58d8bffe1c784485f529525020a2582fc5db6df Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Mar 2017 16:41:08 +0000 Subject: [PATCH 7/9] More PR feedback Unmounted guards, extra semicolon, return early to lose indent level, add keys. --- .../views/settings/AddPhoneNumber.js | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/components/views/settings/AddPhoneNumber.js b/src/components/views/settings/AddPhoneNumber.js index 83c331dd33..9680bdd12d 100644 --- a/src/components/views/settings/AddPhoneNumber.js +++ b/src/components/views/settings/AddPhoneNumber.js @@ -86,8 +86,9 @@ export default WithMatrixClient(React.createClass({ description: msg, }); }).finally(() => { + if (this._unmounted) return; this.setState({msisdn_add_pending: false}); - }).done();; + }).done(); this._addMsisdnInput.blur(); this.setState({msisdn_add_pending: true}); }, @@ -96,7 +97,7 @@ export default WithMatrixClient(React.createClass({ if (this._unmounted) return; const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); let msgElements = [ -
A text message has been sent to +{msisdn}. +
A text message has been sent to +{msisdn}. Please enter the verification code it contains
]; if (err) { @@ -104,7 +105,7 @@ export default WithMatrixClient(React.createClass({ if (err.errcode == 'M_THREEPID_AUTH_FAILED') { msg = "Incorrect verification code"; } - msgElements.push(
{msg}
); + msgElements.push(
{msg}
); } Modal.createDialog(TextInputDialog, { title: "Enter Code", @@ -123,6 +124,7 @@ export default WithMatrixClient(React.createClass({ }).catch((err) => { this._promptForMsisdnVerificationCode(msisdn, err); }).finally(() => { + if (this._unmounted) return; this.setState({msisdn_add_pending: false}); }).done(); } @@ -133,34 +135,36 @@ export default WithMatrixClient(React.createClass({ const Loader = sdk.getComponent("elements.Spinner"); if (this.state.msisdn_add_pending) { return ; - } else if (!this.props.matrixClient.isGuest()) { - const CountryDropdown = sdk.getComponent('views.login.CountryDropdown'); - // XXX: This CSS relies on the CSS surrounding it in UserSettings as its in - // a tabular format to align the submit buttons - return ( -
-
-
-
-
- - -
-
-
- -
-
- ); + } else if (this.props.matrixClient.isGuest()) { + return null; } + + const CountryDropdown = sdk.getComponent('views.login.CountryDropdown'); + // XXX: This CSS relies on the CSS surrounding it in UserSettings as its in + // a tabular format to align the submit buttons + return ( +
+
+
+
+
+ + +
+
+
+ +
+
+ ); } })) From d5272149f6ab734da3b683fffe3e513b987787be Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Mar 2017 16:42:44 +0000 Subject: [PATCH 8/9] Another unmounted guard --- src/components/views/settings/AddPhoneNumber.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/settings/AddPhoneNumber.js b/src/components/views/settings/AddPhoneNumber.js index 9680bdd12d..bb5ecd2694 100644 --- a/src/components/views/settings/AddPhoneNumber.js +++ b/src/components/views/settings/AddPhoneNumber.js @@ -116,6 +116,7 @@ export default WithMatrixClient(React.createClass({ this._addThreepid = null; return; } + if (this._unmounted) return; this.setState({msisdn_add_pending: true}); this._addThreepid.haveMsisdnToken(token).then(() => { this._addThreepid = null; From 5e3b991ec23deabb10bd8e78f6280ef46b3ed237 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Mar 2017 10:45:38 +0000 Subject: [PATCH 9/9] PR feedback fixes --- src/components/structures/UserSettings.js | 1 - src/components/views/settings/AddPhoneNumber.js | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 5633bd0bc7..0cb120019e 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -133,7 +133,6 @@ module.exports = React.createClass({ threePids: [], phase: "UserSettings.LOADING", // LOADING, DISPLAY email_add_pending: false, - msisdn_add_pending: false, vectorVersion: null, rejectingInvites: false, }; diff --git a/src/components/views/settings/AddPhoneNumber.js b/src/components/views/settings/AddPhoneNumber.js index bb5ecd2694..3a348393aa 100644 --- a/src/components/views/settings/AddPhoneNumber.js +++ b/src/components/views/settings/AddPhoneNumber.js @@ -35,6 +35,7 @@ export default WithMatrixClient(React.createClass({ busy: false, phoneCountry: null, phoneNumber: "", + msisdn_add_pending: false, }; }, @@ -137,7 +138,7 @@ export default WithMatrixClient(React.createClass({ if (this.state.msisdn_add_pending) { return ; } else if (this.props.matrixClient.isGuest()) { - return null; + return
; } const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');