Store for EditPassword Modal (#3979)

* External store (WIP)

* address & meta

* Add editable (WIP)

* View converted (WIP)

* Single API stub creation

* Testing (WIP)

* Simplified meta assign

* Tests running

* Fix duplicate exports

* Fix tags not editable
This commit is contained in:
Jaco Greeff
2016-12-28 18:09:45 +01:00
committed by Gav Wood
parent 3067a8de3e
commit 7e600b5a82
13 changed files with 658 additions and 304 deletions

View File

@@ -14,54 +14,55 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import ContentClear from 'material-ui/svg-icons/content/clear';
import CheckIcon from 'material-ui/svg-icons/navigation/check';
import SendIcon from 'material-ui/svg-icons/content/send';
import { Tabs, Tab } from 'material-ui/Tabs';
import Paper from 'material-ui/Paper';
import { Tabs, Tab } from 'material-ui/Tabs';
import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { showSnackbar } from '~/redux/providers/snackbarActions';
import Form, { Input } from '~/ui/Form';
import { newError, showSnackbar } from '~/redux/actions';
import { Button, Modal, IdentityName, IdentityIcon } from '~/ui';
import Form, { Input } from '~/ui/Form';
import { CancelIcon, CheckIcon, SendIcon } from '~/ui/Icons';
import Store, { CHANGE_ACTION, TEST_ACTION } from './store';
import styles from './passwordManager.css';
const TEST_ACTION = 'TEST_ACTION';
const CHANGE_ACTION = 'CHANGE_ACTION';
const MSG_SUCCESS_STYLE = {
backgroundColor: 'rgba(174, 213, 129, 0.75)'
};
const MSG_FAILURE_STYLE = {
backgroundColor: 'rgba(229, 115, 115, 0.75)'
};
const TABS_INKBAR_STYLE = {
backgroundColor: 'rgba(255, 255, 255, 0.55)'
};
const TABS_ITEM_STYLE = {
backgroundColor: 'rgba(255, 255, 255, 0.05)'
};
class PasswordManager extends Component {
@observer
export default class PasswordManager extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
static propTypes = {
account: PropTypes.object.isRequired,
showSnackbar: PropTypes.func.isRequired,
onClose: PropTypes.func
}
state = {
action: TEST_ACTION,
waiting: false,
showMessage: false,
message: { value: '', success: true },
currentPass: '',
newPass: '',
repeatNewPass: '',
repeatValid: true,
passwordHint: this.props.account.meta && this.props.account.meta.passwordHint || ''
}
store = new Store(this.context.api, this.props.account);
render () {
return (
<Modal
actions={ this.renderDialogActions() }
title='Password Manager'
title={
<FormattedMessage
id='passwordChange.title'
defaultMessage='Password Manager' />
}
visible>
{ this.renderAccount() }
{ this.renderPage() }
@@ -71,150 +72,168 @@ class PasswordManager extends Component {
}
renderMessage () {
const { message, showMessage } = this.state;
const { infoMessage } = this.store;
const style = message.success
? {
backgroundColor: 'rgba(174, 213, 129, 0.75)'
}
: {
backgroundColor: 'rgba(229, 115, 115, 0.75)'
};
const classes = [ styles.message ];
if (!showMessage) {
classes.push(styles.hideMessage);
if (!infoMessage) {
return null;
}
return (
<Paper
zDepth={ 1 }
style={ style }
className={ classes.join(' ') }>
{ message.value }
className={ `${styles.message}` }
style={
infoMessage.success
? MSG_SUCCESS_STYLE
: MSG_FAILURE_STYLE
}
zDepth={ 1 }>
{ infoMessage.value }
</Paper>
);
}
renderAccount () {
const { account } = this.props;
const { address, meta } = account;
const passwordHint = meta && meta.passwordHint
? (
<span className={ styles.passwordHint }>
<span className={ styles.hintLabel }>Hint </span>
{ meta.passwordHint }
</span>
)
: null;
const { address, passwordHint } = this.store;
return (
<div className={ styles.accountContainer }>
<IdentityIcon
address={ address }
/>
<IdentityIcon address={ address } />
<div className={ styles.accountInfos }>
<IdentityName
className={ styles.accountName }
address={ address }
unknown
/>
className={ styles.accountName }
unknown />
<span className={ styles.accountAddress }>
{ address }
</span>
{ passwordHint }
<span className={ styles.passwordHint }>
<span className={ styles.hintLabel }>Hint </span>
{ passwordHint || '-' }
</span>
</div>
</div>
);
}
renderPage () {
const { account } = this.props;
const { waiting, repeatValid } = this.state;
const disabled = !!waiting;
const repeatError = repeatValid
? null
: 'the two passwords differ';
const { meta } = account;
const passwordHint = meta && meta.passwordHint || '';
const { busy, isRepeatValid, passwordHint } = this.store;
return (
<Tabs
inkBarStyle={ {
backgroundColor: 'rgba(255, 255, 255, 0.55)'
} }
tabItemContainerStyle={ {
backgroundColor: 'rgba(255, 255, 255, 0.05)'
} }
>
inkBarStyle={ TABS_INKBAR_STYLE }
tabItemContainerStyle={ TABS_ITEM_STYLE }>
<Tab
onActive={ this.handleTestActive }
label='Test Password'
>
<Form
className={ styles.form }
>
label={
<FormattedMessage
id='passwordChange.tabTest.label'
defaultMessage='Test Password' />
}
onActive={ this.onActivateTestTab }>
<Form className={ styles.form }>
<div>
<Input
label='password'
hint='your current password for this account'
type='password'
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.testPassword.hint'
defaultMessage='your account password' />
}
label={
<FormattedMessage
id='passwordChange.testPassword.label'
defaultMessage='password' />
}
onChange={ this.onEditTestPassword }
onSubmit={ this.testPassword }
submitOnBlur={ false }
disabled={ disabled }
onSubmit={ this.handleTestPassword }
onChange={ this.onEditCurrent } />
type='password' />
</div>
</Form>
</Tab>
<Tab
onActive={ this.handleChangeActive }
label='Change Password'
>
<Form
className={ styles.form }
>
label={
<FormattedMessage
id='passwordChange.tabChange.label'
defaultMessage='Change Password' />
}
onActive={ this.onActivateChangeTab }>
<Form className={ styles.form }>
<div>
<Input
label='current password'
hint='your current password for this account'
type='password'
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.currentPassword.hint'
defaultMessage='your current password for this account' />
}
label={
<FormattedMessage
id='passwordChange.currentPassword.label'
defaultMessage='current password' />
}
onChange={ this.onEditCurrentPassword }
onSubmit={ this.changePassword }
submitOnBlur={ false }
disabled={ disabled }
onSubmit={ this.handleChangePassword }
onChange={ this.onEditCurrent } />
type='password' />
<Input
label='(optional) new password hint'
hint='hint for the new password'
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.passwordHint.hint'
defaultMessage='hint for the new password' />
}
label={
<FormattedMessage
id='passwordChange.passwordHint.label'
defaultMessage='(optional) new password hint' />
}
onChange={ this.onEditNewPasswordHint }
onSubmit={ this.changePassword }
submitOnBlur={ false }
value={ passwordHint }
disabled={ disabled }
onSubmit={ this.handleChangePassword }
onChange={ this.onEditHint } />
value={ passwordHint } />
<div className={ styles.passwords }>
<div className={ styles.password }>
<Input
label='new password'
hint='the new password for this account'
type='password'
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.newPassword.hint'
defaultMessage='the new password for this account' />
}
label={
<FormattedMessage
id='passwordChange.newPassword.label'
defaultMessage='new password' />
}
onChange={ this.onEditNewPassword }
onSubmit={ this.changePassword }
submitOnBlur={ false }
disabled={ disabled }
onSubmit={ this.handleChangePassword }
onChange={ this.onEditNew } />
type='password' />
</div>
<div className={ styles.password }>
<Input
label='repeat new password'
hint='repeat the new password for this account'
type='password'
disabled={ busy }
error={
isRepeatValid
? null
: <FormattedMessage
id='passwordChange.repeatPassword.error'
defaultMessage='the supplied passwords do not match' />
}
hint={
<FormattedMessage
id='passwordChange.repeatPassword.hint'
defaultMessage='repeat the new password for this account' />
}
label={
<FormattedMessage
id='passwordChange.repeatPassword.label'
defaultMessage='repeat new password' />
}
onChange={ this.onEditNewPasswordRepeat }
onSubmit={ this.changePassword }
submitOnBlur={ false }
error={ repeatError }
disabled={ disabled }
onSubmit={ this.handleChangePassword }
onChange={ this.onEditRepeatNew } />
type='password' />
</div>
</div>
</div>
@@ -225,176 +244,118 @@ class PasswordManager extends Component {
}
renderDialogActions () {
const { actionTab, busy, isRepeatValid } = this.store;
const { onClose } = this.props;
const { action, waiting, repeatValid } = this.state;
const cancelBtn = (
<Button
icon={ <ContentClear /> }
label='Cancel'
icon={ <CancelIcon /> }
key='cancel'
label={
<FormattedMessage
id='passwordChange.button.cancel'
defaultMessage='Cancel' />
}
onClick={ onClose } />
);
if (waiting) {
const waitingBtn = (
if (busy) {
return [
cancelBtn,
<Button
disabled
label='Wait...' />
);
return [ cancelBtn, waitingBtn ];
key='wait'
label={
<FormattedMessage
id='passwordChange.button.wait'
defaultMessage='Wait...' />
} />
];
}
if (action === TEST_ACTION) {
const testBtn = (
if (actionTab === TEST_ACTION) {
return [
cancelBtn,
<Button
icon={ <CheckIcon /> }
label='Test'
onClick={ this.handleTestPassword } />
);
return [ cancelBtn, testBtn ];
key='test'
label={
<FormattedMessage
id='passwordChange.button.test'
defaultMessage='Test' />
}
onClick={ this.testPassword } />
];
}
const changeBtn = (
return [
cancelBtn,
<Button
disabled={ !repeatValid }
disabled={ !isRepeatValid }
icon={ <SendIcon /> }
label='Change'
onClick={ this.handleChangePassword } />
);
return [ cancelBtn, changeBtn ];
}
onEditCurrent = (event, value) => {
this.setState({
currentPass: value,
showMessage: false
});
}
onEditNew = (event, value) => {
const repeatValid = value === this.state.repeatNewPass;
this.setState({
newPass: value,
showMessage: false,
repeatValid
});
}
onEditRepeatNew = (event, value) => {
const repeatValid = value === this.state.newPass;
this.setState({
repeatNewPass: value,
showMessage: false,
repeatValid
});
}
onEditHint = (event, value) => {
this.setState({
passwordHint: value,
showMessage: false
});
}
handleTestActive = () => {
this.setState({
action: TEST_ACTION,
showMessage: false
});
}
handleChangeActive = () => {
this.setState({
action: CHANGE_ACTION,
showMessage: false
});
}
handleTestPassword = () => {
const { account } = this.props;
const { currentPass } = this.state;
this.setState({ waiting: true, showMessage: false });
this.context
.api.parity
.testPassword(account.address, currentPass)
.then(correct => {
const message = correct
? { value: 'This password is correct', success: true }
: { value: 'This password is not correct', success: false };
this.setState({ waiting: false, message, showMessage: true });
})
.catch(e => {
console.error('passwordManager::handleTestPassword', e);
this.setState({ waiting: false });
});
}
handleChangePassword = () => {
const { account, showSnackbar, onClose } = this.props;
const { currentPass, newPass, repeatNewPass, passwordHint } = this.state;
if (repeatNewPass !== newPass) {
return;
}
this.setState({ waiting: true, showMessage: false });
this.context
.api.parity
.testPassword(account.address, currentPass)
.then(correct => {
if (!correct) {
const message = {
value: 'This provided current password is not correct',
success: false
};
this.setState({ waiting: false, message, showMessage: true });
return false;
key='change'
label={
<FormattedMessage
id='passwordChange.button.change'
defaultMessage='Change' />
}
onClick={ this.changePassword } />
];
}
const meta = Object.assign({}, account.meta, {
passwordHint
});
onActivateChangeTab = () => {
this.store.setActionTab(CHANGE_ACTION);
}
return Promise.all([
this.context
.api.parity
.setAccountMeta(account.address, meta),
onActivateTestTab = () => {
this.store.setActionTab(TEST_ACTION);
}
this.context
.api.parity
.changePassword(account.address, currentPass, newPass)
])
.then(() => {
showSnackbar(<div>Your password has been successfully changed.</div>);
this.setState({ waiting: false, showMessage: false });
onClose();
});
onEditCurrentPassword = (event, password) => {
this.store.setPassword(password);
}
onEditNewPassword = (event, password) => {
this.store.setNewPassword(password);
}
onEditNewPasswordHint = (event, passwordHint) => {
this.store.setNewPasswordHint(passwordHint);
}
onEditNewPasswordRepeat = (event, password) => {
this.store.setNewPasswordRepeat(password);
}
onEditTestPassword = (event, password) => {
this.store.setValidatePassword(password);
}
changePassword = () => {
return this.store
.changePassword()
.then((result) => {
if (result) {
showSnackbar(
<div>
<FormattedMessage
id='passwordChange.success'
defaultMessage='Your password has been successfully changed' />
</div>
);
this.props.onClose();
}
})
.catch(e => {
console.error('passwordManager::handleChangePassword', e);
this.setState({ waiting: false });
.catch((error) => {
newError(error);
});
}
testPassword = () => {
return this.store
.testPassword()
.catch((error) => {
newError(error);
});
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
showSnackbar
}, dispatch);
}
export default connect(
null,
mapDispatchToProps
)(PasswordManager);