Bring over email address management
This commit is contained in:
parent
fa1ce61a06
commit
aa7afe819f
11 changed files with 342 additions and 11 deletions
|
@ -127,6 +127,7 @@
|
|||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||
@import "./views/settings/_DevicesPanel.scss";
|
||||
@import "./views/settings/_EmailAddresses.scss";
|
||||
@import "./views/settings/_IntegrationsManager.scss";
|
||||
@import "./views/settings/_KeyBackupPanel.scss";
|
||||
@import "./views/settings/_Notifications.scss";
|
||||
|
|
|
@ -42,3 +42,35 @@ limitations under the License.
|
|||
color: $button-primary-disabled-fg-color;
|
||||
background-color: $button-primary-disabled-bg-color;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_primary_sm {
|
||||
padding: 5px 12px !important;
|
||||
color: $button-primary-fg-color;
|
||||
background-color: $button-primary-bg-color;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_primary_sm.mx_AccessibleButton_disabled {
|
||||
color: $button-primary-disabled-fg-color;
|
||||
background-color: $button-primary-disabled-bg-color;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_danger {
|
||||
color: $button-danger-fg-color;
|
||||
background-color: $button-danger-bg-color;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled {
|
||||
color: $button-danger-disabled-fg-color;
|
||||
background-color: $button-danger-disabled-bg-color;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_danger_sm {
|
||||
padding: 5px 12px !important;
|
||||
color: $button-danger-fg-color;
|
||||
background-color: $button-danger-bg-color;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_danger_sm.mx_AccessibleButton_disabled {
|
||||
color: $button-danger-disabled-fg-color;
|
||||
background-color: $button-danger-disabled-bg-color;
|
||||
}
|
41
res/css/views/settings/_EmailAddresses.scss
Normal file
41
res/css/views/settings/_EmailAddresses.scss
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Copyright 2019 New Vector 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.
|
||||
*/
|
||||
|
||||
.mx_ExistingEmailAddress {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.mx_ExistingEmailAddress_delete {
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_ExistingEmailAddress_email {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_ExistingEmailAddress_promptText {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.mx_ExistingEmailAddress_confirmBtn {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.mx_EmailAddresses_new .mx_Field input {
|
||||
width: calc(100% - 20px);
|
||||
}
|
|
@ -30,4 +30,8 @@ limitations under the License.
|
|||
|
||||
.mx_GeneralSettingsTab_changePassword .mx_Field:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mx_GeneralSettingsTab_accountSection > .mx_EmailAddresses {
|
||||
margin-right: 100px; // Align with the other fields on the page
|
||||
}
|
|
@ -211,6 +211,10 @@ $button-primary-fg-color: #ffffff;
|
|||
$button-primary-bg-color: #7ac9a1;
|
||||
$button-primary-disabled-fg-color: #ffffff;
|
||||
$button-primary-disabled-bg-color: #bce4d0;
|
||||
$button-danger-fg-color: #ffffff;
|
||||
$button-danger-bg-color: #f56679;
|
||||
$button-danger-disabled-fg-color: #ffffff;
|
||||
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
|
||||
|
||||
// unused?
|
||||
$progressbar-color: #000;
|
||||
|
|
|
@ -207,6 +207,10 @@ $button-primary-fg-color: #ffffff;
|
|||
$button-primary-bg-color: #7ac9a1;
|
||||
$button-primary-disabled-fg-color: #ffffff;
|
||||
$button-primary-disabled-bg-color: #bce4d0;
|
||||
$button-danger-fg-color: #ffffff;
|
||||
$button-danger-bg-color: #f56679;
|
||||
$button-danger-disabled-fg-color: #ffffff;
|
||||
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
|
||||
|
||||
// unused?
|
||||
$progressbar-color: #000;
|
||||
|
|
|
@ -26,7 +26,7 @@ import { _t } from './languageHandler';
|
|||
* the client owns the given email address, which is then passed to the
|
||||
* add threepid API on the homeserver.
|
||||
*/
|
||||
class AddThreepid {
|
||||
export default class AddThreepid {
|
||||
constructor() {
|
||||
this.clientSecret = MatrixClientPeg.get().generateClientSecret();
|
||||
}
|
||||
|
@ -124,5 +124,3 @@ class AddThreepid {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AddThreepid;
|
||||
|
|
|
@ -38,6 +38,13 @@ export default class Field extends React.PureComponent {
|
|||
return this.refs.fieldInput.value;
|
||||
}
|
||||
|
||||
set value(newValue) {
|
||||
if (!this.refs.fieldInput) {
|
||||
throw new Error("No field input reference");
|
||||
}
|
||||
this.refs.fieldInput.value = newValue;
|
||||
}
|
||||
|
||||
render() {
|
||||
const extraProps = Object.assign({}, this.props);
|
||||
|
||||
|
|
231
src/components/views/settings/EmailAddresses.js
Normal file
231
src/components/views/settings/EmailAddresses.js
Normal file
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
Copyright 2019 New Vector 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 PropTypes from 'prop-types';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import Field from "../elements/Field";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import * as Email from "../../../email";
|
||||
import AddThreepid from "../../../AddThreepid";
|
||||
const sdk = require('../../../index');
|
||||
const Modal = require("../../../Modal");
|
||||
|
||||
/*
|
||||
TODO: Improve the UX for everything in here.
|
||||
It's very much placeholder, but it gets the job done. The old way of handling
|
||||
email addresses in user settings was to use dialogs to communicate state, however
|
||||
due to our dialog system overriding dialogs (causing unmounts) this creates problems
|
||||
for a sane UX. For instance, the user could easily end up entering an email address
|
||||
and receive a dialog to verify the address, which then causes the component here
|
||||
to forget what it was doing and ultimately fail. Dialogs are still used in some
|
||||
places to communicate errors - these should be replaced with inline validation when
|
||||
that is available.
|
||||
*/
|
||||
|
||||
export class ExistingEmailAddress extends React.Component {
|
||||
static propTypes = {
|
||||
email: PropTypes.object.isRequired,
|
||||
onRemoved: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
verifyRemove: false,
|
||||
};
|
||||
}
|
||||
|
||||
_onRemove = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({verifyRemove: true});
|
||||
};
|
||||
|
||||
_onDontRemove = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({verifyRemove: false});
|
||||
};
|
||||
|
||||
_onActuallyRemove = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
MatrixClientPeg.get().deleteThreePid(this.props.email.medium, this.props.email.address).then(() => {
|
||||
return this.props.onRemoved(this.props.email);
|
||||
}).catch((err) => {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Unable to remove contact information: " + err);
|
||||
Modal.createTrackedDialog('Remove 3pid failed', '', ErrorDialog, {
|
||||
title: _t("Unable to remove contact information"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.verifyRemove) {
|
||||
return (
|
||||
<div className="mx_ExistingEmailAddress">
|
||||
<span className="mx_ExistingEmailAddress_promptText">
|
||||
{_t("Are you sure?")}
|
||||
</span>
|
||||
<AccessibleButton onClick={this._onActuallyRemove} kind="primary_sm"
|
||||
className="mx_ExistingEmailAddress_confirmBtn">
|
||||
{_t("Yes")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton onClick={this._onDontRemove} kind="danger_sm"
|
||||
className="mx_ExistingEmailAddress_confirmBtn">
|
||||
{_t("No")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_ExistingEmailAddress">
|
||||
<img src={require("../../../../res/img/feather-icons/cancel.svg")} width={14} height={14}
|
||||
onClick={this._onRemove} className="mx_ExistingEmailAddress_delete" alt={_t("Remove")} />
|
||||
<span className="mx_ExistingEmailAddress_email">{this.props.email.address}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class EmailAddresses extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
emails: [],
|
||||
verifying: false,
|
||||
addTask: null,
|
||||
continueDisabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
client.getThreePids().then((addresses) => {
|
||||
this.setState({emails: addresses.threepids.filter((a) => a.medium === 'email')});
|
||||
});
|
||||
}
|
||||
|
||||
_onRemoved = (address) => {
|
||||
this.setState({emails: this.state.emails.filter((e) => e !== address)});
|
||||
};
|
||||
|
||||
_onAddClick = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.refs.newEmailAddress) return;
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const email = this.refs.newEmailAddress.value;
|
||||
|
||||
// TODO: Inline field validation
|
||||
if (!Email.looksValid(email)) {
|
||||
Modal.createTrackedDialog('Invalid email address', '', ErrorDialog, {
|
||||
title: _t("Invalid Email Address"),
|
||||
description: _t("This doesn't appear to be a valid email address"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const task = new AddThreepid();
|
||||
this.setState({verifying: true, continueDisabled: true, addTask: task});
|
||||
|
||||
task.addEmailAddress(email, true).then(() => {
|
||||
this.setState({continueDisabled: false});
|
||||
}).catch((err) => {
|
||||
console.error("Unable to add email address " + email + " " + err);
|
||||
this.setState({verifying: false, continueDisabled: false, addTask: null});
|
||||
Modal.createTrackedDialog('Unable to add email address', '', ErrorDialog, {
|
||||
title: _t("Unable to add email address"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
_onContinueClick = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({continueDisabled: true});
|
||||
this.state.addTask.checkEmailLinkClicked().then(() => {
|
||||
const email = this.refs.newEmailAddress.value;
|
||||
this.setState({
|
||||
emails: [...this.state.emails, {address: email, medium: "email"}],
|
||||
addTask: null,
|
||||
continueDisabled: false,
|
||||
verifying: false,
|
||||
});
|
||||
this.refs.newEmailAddress.value = "";
|
||||
}).catch((err) => {
|
||||
this.setState({continueDisabled: false});
|
||||
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Unable to verify email address: " + err);
|
||||
Modal.createTrackedDialog('Unable to verify email address', '', ErrorDialog, {
|
||||
title: _t("Unable to verify email address."),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const existingEmailElements = this.state.emails.map((e) => {
|
||||
return <ExistingEmailAddress email={e} onRemoved={this._onRemoved} key={e.address}/>;
|
||||
});
|
||||
|
||||
let addButton = (
|
||||
<AccessibleButton onClick={this._onAddClick} kind="primary">
|
||||
{_t("Add")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
if (this.state.verifying) {
|
||||
addButton = (
|
||||
<div>
|
||||
<div>{_t("We've sent you an email to verify your address. Please follow the instructions there and then click the button below.")}</div>
|
||||
<AccessibleButton onClick={this._onContinueClick} kind="primary"
|
||||
disabled={this.state.continueDisabled}>
|
||||
{_t("Continue")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_EmailAddresses">
|
||||
{existingEmailElements}
|
||||
<form onSubmit={this._onAddClick} autoComplete={false}
|
||||
noValidate={true} className="mx_EmailAddresses_new">
|
||||
<Field id="newEmailAddress" ref="newEmailAddress" label={_t("Email Address")}
|
||||
type="text" autoComplete="off" disabled={this.state.verifying}/>
|
||||
{addButton}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import PropTypes from "prop-types";
|
|||
import {MatrixClient} from "matrix-js-sdk";
|
||||
import { DragDropContext } from 'react-beautiful-dnd';
|
||||
import ProfileSettings from "../ProfileSettings";
|
||||
import EmailAddresses from "../EmailAddresses";
|
||||
const sdk = require('../../../../index');
|
||||
const Modal = require("../../../../Modal");
|
||||
|
||||
|
@ -95,12 +96,15 @@ export default class GeneralSettingsTab extends React.Component {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<div className="mx_SettingsTab_section mx_GeneralSettingsTab_accountSection">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Account")}</span>
|
||||
<p className="mx_SettingsTab_subsectionText">
|
||||
{_t("Set a new account password...")}
|
||||
</p>
|
||||
{passwordChangeForm}
|
||||
|
||||
<span className="mx_SettingsTab_subheading">{_t("Email addresses")}</span>
|
||||
<EmailAddresses />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -352,6 +352,17 @@
|
|||
"Last seen": "Last seen",
|
||||
"Select devices": "Select devices",
|
||||
"Failed to set display name": "Failed to set display name",
|
||||
"Unable to remove contact information": "Unable to remove contact information",
|
||||
"Are you sure?": "Are you sure?",
|
||||
"Yes": "Yes",
|
||||
"No": "No",
|
||||
"Remove": "Remove",
|
||||
"Invalid Email Address": "Invalid Email Address",
|
||||
"This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address",
|
||||
"Unable to add email address": "Unable to add email address",
|
||||
"Unable to verify email address.": "Unable to verify email address.",
|
||||
"We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.",
|
||||
"Email Address": "Email Address",
|
||||
"Disable Notifications": "Disable Notifications",
|
||||
"Enable Notifications": "Enable Notifications",
|
||||
"Delete Backup": "Delete Backup",
|
||||
|
@ -413,6 +424,7 @@
|
|||
"Flair": "Flair",
|
||||
"Account": "Account",
|
||||
"Set a new account password...": "Set a new account password...",
|
||||
"Email addresses": "Email addresses",
|
||||
"Language and region": "Language and region",
|
||||
"Theme": "Theme",
|
||||
"Account management": "Account management",
|
||||
|
@ -464,7 +476,6 @@
|
|||
"Failed to toggle moderator status": "Failed to toggle moderator status",
|
||||
"Failed to change power level": "Failed to change power level",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
|
||||
"Are you sure?": "Are you sure?",
|
||||
"No devices with registered encryption keys": "No devices with registered encryption keys",
|
||||
"Devices": "Devices",
|
||||
"Unignore": "Unignore",
|
||||
|
@ -737,7 +748,6 @@
|
|||
"Flair will not appear": "Flair will not appear",
|
||||
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?",
|
||||
"Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.",
|
||||
"Remove": "Remove",
|
||||
"Failed to remove room from community": "Failed to remove room from community",
|
||||
"Failed to remove '%(roomName)s' from %(groupId)s": "Failed to remove '%(roomName)s' from %(groupId)s",
|
||||
"Something went wrong!": "Something went wrong!",
|
||||
|
@ -982,12 +992,8 @@
|
|||
"We encountered an error trying to restore your previous session.": "We encountered an error trying to restore your previous session.",
|
||||
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.",
|
||||
"Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.",
|
||||
"Invalid Email Address": "Invalid Email Address",
|
||||
"This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address",
|
||||
"Verification Pending": "Verification Pending",
|
||||
"Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.",
|
||||
"Unable to add email address": "Unable to add email address",
|
||||
"Unable to verify email address.": "Unable to verify email address.",
|
||||
"Email address": "Email address",
|
||||
"This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.",
|
||||
"Skip": "Skip",
|
||||
|
@ -1253,7 +1259,6 @@
|
|||
"Server may be unavailable or overloaded": "Server may be unavailable or overloaded",
|
||||
"Remove Contact Information?": "Remove Contact Information?",
|
||||
"Remove %(threePid)s?": "Remove %(threePid)s?",
|
||||
"Unable to remove contact information": "Unable to remove contact information",
|
||||
"Refer a friend to Riot:": "Refer a friend to Riot:",
|
||||
"Interface Language": "Interface Language",
|
||||
"User Interface": "User Interface",
|
||||
|
|
Loading…
Reference in a new issue