adding credential editing
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
// Edit account parameters.
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { parsePhoneNumber } from 'libphonenumber-js/mobile';
|
||||
|
||||
import AvatarUpload from '../widgets/avatar-upload.jsx';
|
||||
import BadgeList from '../widgets/badge-list.jsx';
|
||||
|
||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH } from '../config.js';
|
||||
import CredentialEdit from '../widgets/credential-edit.jsx';
|
||||
|
||||
function _clip(str, length) {
|
||||
return str && str.substring(0, length);
|
||||
@@ -20,15 +22,57 @@ export default class AccountSettingsView extends React.Component {
|
||||
this.state = {
|
||||
fullName: _clip(me.public ? me.public.fn : undefined, MAX_TITLE_LENGTH),
|
||||
description: _clip(me.public ? me.public.note : undefined, MAX_TOPIC_DESCRIPTION_LENGTH),
|
||||
avatar: makeImageUrl(me.public ? me.public.photo : null)
|
||||
avatar: makeImageUrl(me.public ? me.public.photo : null),
|
||||
credentials: me.getCredentials() || [],
|
||||
credEdit: undefined
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.credEdit) {
|
||||
return (
|
||||
<CredentialEdit
|
||||
method={this.state.credEdit.meth}
|
||||
val={this.state.credEdit.val}
|
||||
done={this.state.credEdit.done}
|
||||
onShowCountrySelector={this.props.onShowCountrySelector}
|
||||
onSubmit={cred => {this.setState({credEdit: undefined}); console.log("Credential editing done", cred)}}
|
||||
onCancel={_ => this.setState({credEdit: undefined})} />
|
||||
);
|
||||
}
|
||||
|
||||
const credentials = [];
|
||||
this.state.credentials.forEach((cred, idx) => {
|
||||
if (!['email', 'tel'].includes(cred.meth)) {
|
||||
// Skip unknown methods.
|
||||
return ;
|
||||
}
|
||||
|
||||
let val = cred.val
|
||||
if (cred.meth == 'tel') {
|
||||
const number = parsePhoneNumber(cred.val);
|
||||
val = number ? number.formatInternational() : cred.val;
|
||||
}
|
||||
|
||||
credentials.push(
|
||||
<div className="group quoted" key={idx}>
|
||||
<tt className="clickable" onClick={e => {e.preventDefault(); this.setState({credEdit: cred});}}>{val}</tt>
|
||||
<span> {cred.done ? null : <i className="material-icons">pending</i>}</span>
|
||||
</div>);
|
||||
});
|
||||
if (credentials.length > 0) {
|
||||
// Add title as the first element.
|
||||
credentials.unshift(
|
||||
<label className="small" key={'title'}>
|
||||
<FormattedMessage id="label_user_contacts" defaultMessage="Contacts"
|
||||
description="Label for user contacts" />
|
||||
</label>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="scrollable-panel">
|
||||
<div className="panel-form-column">
|
||||
<a href="#" className="flat-button float-right" onClick={(e) => {e.preventDefault(); this.props.onNavigate('general');}}>
|
||||
<a href="#" className="flat-button float-right" onClick={e => {e.preventDefault(); this.props.onNavigate('general');}}>
|
||||
<i className="material-icons">edit</i>
|
||||
<FormattedMessage id="button_edit" defaultMessage="Edit" description="Call to action [Edit]" />
|
||||
</a>
|
||||
@@ -61,10 +105,14 @@ export default class AccountSettingsView extends React.Component {
|
||||
<FormattedMessage id="label_description" defaultMessage="Description"
|
||||
description="Label for editing topic description" />
|
||||
</label>
|
||||
<div>{this.state.description}</div>
|
||||
<div className="quoted">{this.state.description}</div>
|
||||
</div> : null}
|
||||
</div>
|
||||
<div className="hr" />
|
||||
<div className="panel-form-column">
|
||||
<div className="group">{credentials}</div>
|
||||
</div>
|
||||
<div className="hr" />
|
||||
<div className="panel-form-column">
|
||||
<a href="#" className="flat-button" onClick={(e) => {e.preventDefault(); this.props.onNavigate('notif');}}>
|
||||
<i className="material-icons">notifications</i> <FormattedMessage id="sidepanel_title_acc_notifications"
|
||||
|
||||
@@ -30,7 +30,6 @@ export default class CreateAccountView extends React.PureComponent {
|
||||
uploadUrl: null,
|
||||
newAvatar: null,
|
||||
newAvatarMime: null,
|
||||
errorCleared: false,
|
||||
buttonDisabled: false,
|
||||
saveToken: LocalStorageUtil.getObject('keep-logged-in')
|
||||
};
|
||||
@@ -85,7 +84,6 @@ export default class CreateAccountView extends React.PureComponent {
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.setState({errorCleared: false});
|
||||
this.props.onCreateAccount(
|
||||
this.state.login.trim(),
|
||||
this.state.password.trim(),
|
||||
@@ -144,9 +142,6 @@ export default class CreateAccountView extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
// "reqCred":{"auth":["email"]}
|
||||
const method = (this.props.tinode.getServerParam('reqCred', {}).auth || [])[0] || 'email';
|
||||
|
||||
if (this.state.newAvatar) {
|
||||
return (
|
||||
<AvatarCrop
|
||||
@@ -192,7 +187,7 @@ export default class CreateAccountView extends React.PureComponent {
|
||||
value={this.state.fn} onChange={this.handleFnChange} required/>
|
||||
}</FormattedMessage>
|
||||
</div>
|
||||
{method == 'email' ?
|
||||
{props.reqCredMethod == 'email' ?
|
||||
<div className="panel-form-row">
|
||||
<FormattedMessage id="email_prompt" defaultMessage="Email, e.g. jdoe@example.com"
|
||||
description="Input placeholder for email entry">{
|
||||
@@ -200,7 +195,7 @@ export default class CreateAccountView extends React.PureComponent {
|
||||
value={this.state.email} onChange={this.handleEmailChange} required/>
|
||||
}</FormattedMessage>
|
||||
</div>
|
||||
: method == 'tel' ?
|
||||
: props.reqCredMethod == 'tel' ?
|
||||
<><div className="panel-form-row">
|
||||
<label className="small gray"><FormattedMessage id="mobile_phone_number" defaultMessage="Mobile phone number"
|
||||
description="Prompt for entering a mobile phone number" /></label>
|
||||
|
||||
@@ -12,7 +12,7 @@ import ErrorPanel from '../widgets/error-panel.jsx';
|
||||
import GroupManager from '../widgets/group-manager.jsx';
|
||||
import MenuCancel from '../widgets/menu-cancel.jsx';
|
||||
import PermissionsEditor from '../widgets/permissions-editor.jsx';
|
||||
import TopicCommon from '../widgets/topic-common.jsx';
|
||||
import TopicCommonView from './topic-common-view.jsx';
|
||||
import TopicSecurity from '../widgets/topic-security.jsx';
|
||||
|
||||
import { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH, NO_ACCESS_MODE } from '../config.js';
|
||||
@@ -476,9 +476,10 @@ class InfoView extends React.Component {
|
||||
onCancel={this.handleBackNavigate} />
|
||||
:
|
||||
view == 'general' ?
|
||||
<TopicCommon
|
||||
<TopicCommonView
|
||||
tinode={this.props.tinode}
|
||||
topic={this.props.topic}
|
||||
reqCredMethod={this.props.reqCredMethod}
|
||||
onCredAdd={this.props.onCredAdd}
|
||||
onUpdateTagsRequest={this.props.onTopicTagsUpdateRequest}
|
||||
onCredConfirm={this.props.onCredConfirm}
|
||||
|
||||
@@ -46,19 +46,19 @@ export default class PasswordResetView extends React.PureComponent {
|
||||
scheme: 'token',
|
||||
token: this.state.token
|
||||
});
|
||||
} else if (this.state.code && this.method) {
|
||||
} else if (this.state.code && this.props.reqCredMethod) {
|
||||
// Reset using security code.
|
||||
// The secret is structured as <code>:<cred_method>:<cred_value>, "123456:email:alice@example.com".
|
||||
const cred = this.state.email.trim() || this.state.tel.trim();
|
||||
this.props.onReset(this.state.password.trim(), {
|
||||
scheme: 'code',
|
||||
secret: `${this.state.code}:${this.method}:${cred}`
|
||||
secret: `${this.state.code}:${this.props.reqCredMethod}:${cred}`
|
||||
});
|
||||
} else {
|
||||
const email = this.state.email.trim();
|
||||
const tel = this.state.tel.trim();
|
||||
this.setState({email: email, tel: tel});
|
||||
this.props.onRequest(this.method, email || tel).then(_ => this.setState({sent: true}));
|
||||
this.props.onRequest(this.props.reqCredMethod, email || tel).then(_ => this.setState({sent: true}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,9 +80,6 @@ export default class PasswordResetView extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
// "reqCred":{"auth":["email"]}
|
||||
this.method = (this.props.tinode.getServerParam('reqCred', {}).auth || [])[0] || 'email';
|
||||
|
||||
const showCredentialInput = !(this.state.token && this.state.scheme);
|
||||
const showPasswordInput = !showCredentialInput || this.state.haveCode || this.state.sent;
|
||||
|
||||
@@ -151,12 +148,12 @@ export default class PasswordResetView extends React.PureComponent {
|
||||
</>);
|
||||
|
||||
const sentNotification = (<>{
|
||||
this.method == 'email' ?
|
||||
this.props.reqCredMethod == 'email' ?
|
||||
<FormattedMessage id="password_reset_email_sent"
|
||||
defaultMessage="An email with security code has been sent to {email}. Enter the code below."
|
||||
values={{email: <tt>{this.state.email}</tt>}}
|
||||
description="Notification that the email with password reset instructions has been sent" />
|
||||
: this.method == 'tel' ?
|
||||
: this.props.reqCredMethod == 'tel' ?
|
||||
<FormattedMessage id="password_reset_sms_sent"
|
||||
defaultMessage="A text message with security code has been sent to {phone}. Enter the code below."
|
||||
values={{phone: <tt>{this.state.tel}</tt>}}
|
||||
@@ -183,7 +180,8 @@ export default class PasswordResetView extends React.PureComponent {
|
||||
</div>
|
||||
</>);
|
||||
|
||||
const credentialInput = this.method == 'email' ? emailInput : this.method == 'tel' ? phoneInput : null;
|
||||
const credentialInput = this.props.reqCredMethod == 'email' ?
|
||||
emailInput : this.props.reqCredMethod == 'tel' ? phoneInput : null;
|
||||
|
||||
return (
|
||||
<form id="password-reset-form" onSubmit={this.handleSubmit}>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ErrorPanel from '../widgets/error-panel.jsx';
|
||||
import LoadSpinner from '../widgets/load-spinner.jsx';
|
||||
import SideNavbar from '../widgets/side-navbar.jsx';
|
||||
import TopicCommon from '../widgets/topic-common.jsx';
|
||||
import TopicCommonView from './topic-common-view.jsx';
|
||||
|
||||
import AccountSettingsView from './account-settings-view.jsx';
|
||||
import ContactsView from './contacts-view.jsx';
|
||||
@@ -157,7 +157,7 @@ class SidepanelView extends React.PureComponent {
|
||||
view === 'register' ?
|
||||
<CreateAccountView
|
||||
tinode={this.props.tinode}
|
||||
serverVersion={this.props.serverVersion}
|
||||
reqCredMethod={this.props.reqCredMethod}
|
||||
onShowCountrySelector={this.props.onShowCountrySelector}
|
||||
onCreateAccount={this.props.onCreateAccount}
|
||||
onCancel={this.props.onCancel}
|
||||
@@ -176,13 +176,15 @@ class SidepanelView extends React.PureComponent {
|
||||
tinode={this.props.tinode}
|
||||
myUserId={this.props.myUserId}
|
||||
trustedBadges={this.props.trustedBadges}
|
||||
onShowCountrySelector={this.props.onShowCountrySelector}
|
||||
onNavigate={this.props.onNavigate} /> :
|
||||
|
||||
(view === 'general' || view === 'crop') ?
|
||||
<TopicCommon
|
||||
<TopicCommonView
|
||||
topic="me"
|
||||
tinode={this.props.tinode}
|
||||
myUserId={this.props.myUserId}
|
||||
reqCredMethod={this.props.reqCredMethod}
|
||||
onUpdateTopicDesc={this.props.onUpdateAccountDesc}
|
||||
onUpdateTagsRequest={this.props.onUpdateAccountTags}
|
||||
onCredAdd={this.props.onCredAdd}
|
||||
@@ -248,7 +250,7 @@ class SidepanelView extends React.PureComponent {
|
||||
view === 'reset' ?
|
||||
<PasswordResetView
|
||||
tinode={this.props.tinode}
|
||||
serverVersion={this.props.serverVersion}
|
||||
reqCredMethod={this.props.reqCredMethod}
|
||||
onShowCountrySelector={this.props.onShowCountrySelector}
|
||||
onRequest={this.props.onPasswordResetRequest}
|
||||
onReset={this.props.onResetPassword}
|
||||
|
||||
@@ -266,6 +266,7 @@ class TinodeWeb extends React.Component {
|
||||
// Merged results of a search query and p2p chats.
|
||||
searchableContacts: [],
|
||||
// Credential validation.
|
||||
reqCredMethod: undefined,
|
||||
credMethod: undefined,
|
||||
credCode: undefined,
|
||||
credToken: undefined,
|
||||
@@ -615,7 +616,9 @@ class TinodeWeb extends React.Component {
|
||||
|
||||
const params = this.tinode.getServerInfo();
|
||||
this.setState({
|
||||
serverVersion: params.ver + ' ' + (params.build ? params.build : 'none')
|
||||
serverVersion: params.ver + ' ' + (params.build ? params.build : 'none'),
|
||||
// "reqCred":{"auth":["email"]}
|
||||
reqCredMethod: ((params.reqCred || {}).auth || [])[0] || 'email'
|
||||
});
|
||||
|
||||
if (this.state.autoLogin) {
|
||||
@@ -2012,6 +2015,7 @@ class TinodeWeb extends React.Component {
|
||||
serverAddress={this.state.serverAddress}
|
||||
secureConnection={this.state.secureConnection}
|
||||
serverVersion={this.state.serverVersion}
|
||||
reqCredMethod={this.state.reqCredMethod}
|
||||
|
||||
onGlobalSettings={this.handleGlobalSettings}
|
||||
onSignUp={this.handleNewAccount}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// View for editing topic parameters (including 'me' topic).
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import TopicDescEdit from '../widgets/topic-desc-edit.jsx';
|
||||
|
||||
import { arrayEqual } from '../lib/utils.js';
|
||||
|
||||
export default class TopicCommonView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||
const acs = topic.getAccessMode();
|
||||
this.state = {
|
||||
tags: []
|
||||
};
|
||||
|
||||
this.previousTagsUpdated = undefined;
|
||||
|
||||
this.onTagsUpdated = this.onTagsUpdated.bind(this);
|
||||
this.handleTagsUpdated = this.handleTagsUpdated.bind(this);
|
||||
}
|
||||
|
||||
// No need to separately handle component mount.
|
||||
componentDidUpdate(props) {
|
||||
const topic = this.props.tinode.getTopic(props.topic);
|
||||
if (!topic) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (topic.onTagsUpdated != this.onTagsUpdated) {
|
||||
if (topic.getType() == 'grp') {
|
||||
this.previousTagsUpdated = topic.onTagsUpdated;
|
||||
topic.onTagsUpdated = this.onTagsUpdated;
|
||||
} else {
|
||||
this.previousTagsUpdated = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.topic != props.topic) {
|
||||
this.setState({topic: props.topic});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||
topic.onTagsUpdated = this.previousTagsUpdated;
|
||||
}
|
||||
|
||||
// Server informs that the tags have been updated.
|
||||
onTagsUpdated(tags) {
|
||||
this.setState({tags: tags});
|
||||
|
||||
if (this.previousTagsUpdated && this.previousTagsUpdated != this.onTagsUpdated) {
|
||||
this.previousTagsUpdated(tags);
|
||||
}
|
||||
}
|
||||
|
||||
// Request server to change tags.
|
||||
handleTagsUpdated(tags) {
|
||||
if (!arrayEqual(this.state.tags.slice(0), tags.slice(0))) {
|
||||
this.props.onUpdateTagsRequest(this.props.topic, tags);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="scrollable-panel">
|
||||
<TopicDescEdit
|
||||
tinode={this.props.tinode}
|
||||
topic={this.props.topic}
|
||||
onUpdateTopicDesc={this.props.onUpdateTopicDesc}
|
||||
onUpdateTags={this.handleTagsUpdated}
|
||||
onError={this.props.onError} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user