From 284dd85a99f538b77fd477f4952117d1792f64a5 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 6 Apr 2018 19:59:51 -0230 Subject: first commit --- ui/app/actions.js | 2 + .../page-container-content.component.js | 18 ++ .../page-container-footer.component.js | 41 ++++ .../page-container-header.component.js | 35 ++++ .../page-container/page-container.component.js | 18 ++ .../tests/page-container-content-component.test.js | 0 .../tests/page-container-footer-component.test.js | 0 .../tests/page-container-header-component.test.js | 0 ui/app/components/send_/README.md | 0 .../send_/send-content/send-amount-row/README.md | 0 .../amount-max-button.component.js | 0 .../amount-max-button.container.js | 0 .../amount-max-button.selectors.js | 0 .../amount-max-button/amount-max-button.utils.js | 0 .../tests/amount-max-button-component.test.js | 0 .../tests/amount-max-button-container.test.js | 0 .../tests/amount-max-button-selectors.test.js | 0 .../tests/amount-max-button-utils.test.js | 0 .../send-amount-row/send-amount-row.component.js | 0 .../send-amount-row/send-amount-row.container.js | 48 +++++ .../send-amount-row/send-amount-row.scss | 0 .../send-amount-row/send-amount-row.selectors.js | 0 .../send-amount-row/send-amount-row.utils.js | 0 .../tests/send-amount-row-component.test.js | 0 .../tests/send-amount-row-container.test.js | 0 .../tests/send-amount-row-selectors.test.js | 0 .../tests/send-amount-row-utils.test.js | 0 .../send_/send-content/send-content-README.md | 0 .../send_/send-content/send-content.component.js | 0 .../send_/send-content/send-content.scss | 0 .../from-dropdown/from-dropdown-README.md | 0 .../from-dropdown/from-dropdown.component.js | 0 .../send-from-row/from-dropdown/from-dropdown.scss | 0 .../tests/from-dropdown-component.test.js | 0 .../send-from-row/send-from-row-README.md | 0 .../send-from-row/send-from-row.component.js | 64 ++++++ .../send-from-row/send-from-row.container.js | 44 +++++ .../send-from-row/send-from-row.selectors.js | 9 + .../send-from-row/send-from-row.utils.js | 12 ++ .../tests/send-from-row-component.test.js | 0 .../tests/send-from-row-container.test.js | 0 .../tests/send-from-row-selectors.test.js | 0 .../send_/send-content/send-gas-row/README.md | 0 .../send-gas-row/send-gas-row.component.js | 0 .../send-gas-row/send-gas-row.container.js | 0 .../send-content/send-gas-row/send-gas-row.scss | 0 .../send-gas-row/send-gas-row.selectors.js | 0 .../tests/send-gas-row-component.test.js | 0 .../tests/send-gas-row-container.test.js | 0 .../tests/send-gas-row-selectors.test.js | 0 .../send-row-error-message-README.md | 0 .../send-row-error-message.component.js | 23 +++ .../send-row-error-message.container.js | 11 ++ .../send-row-error-message.scss | 0 .../send-row-wrapper/send-row-wrapper-README.md | 0 .../send-row-wrapper/send-row-wrapper.component.js | 39 ++++ .../send-row-wrapper/send-row-wrapper.scss | 0 .../tests/send-row-wrapper-component.test.js | 0 .../send-content/send-to-row/send-to-row-README.md | 0 .../send-to-row/send-to-row.component.js | 62 ++++++ .../send-to-row/send-to-row.container.js | 43 ++++ .../send-to-row/send-to-row.selectors.js | 14 ++ .../send-content/send-to-row/send-to-row.utils.js | 17 ++ .../tests/send-to-row-component.test.js | 0 .../tests/send-to-row-container.test.js | 0 .../tests/send-to-row-selectors.test.js | 0 .../tests/send-content-component.test.js | 0 ui/app/components/send_/send-footer/README.md | 0 .../send_/send-footer/send-footer.component.js | 0 .../send_/send-footer/send-footer.container.js | 0 .../components/send_/send-footer/send-footer.scss | 0 .../send_/send-footer/send-footer.selectors.js | 0 .../send_/send-footer/send-footer.utils.js | 0 .../tests/send-footer-component.test.js | 0 .../tests/send-footer-container.test.js | 0 .../tests/send-footer-selectors.test.js | 0 .../send-footer/tests/send-footer-utils.test.js | 0 ui/app/components/send_/send-header/README.md | 0 .../send_/send-header/send-header.component.js | 32 +++ .../send_/send-header/send-header.container.js | 19 ++ .../tests/send-header-component.test.js | 0 .../tests/send-header-container.test.js | 0 ui/app/components/send_/send.component.js | 0 ui/app/components/send_/send.container.js | 0 ui/app/components/send_/send.scss | 0 ui/app/components/send_/send.selectors.js | 217 +++++++++++++++++++++ ui/app/components/send_/send.utils.js | 0 .../components/send_/tests/send-component.test.js | 0 .../components/send_/tests/send-container.test.js | 0 .../components/send_/tests/send-selectors.test.js | 0 ui/app/components/send_/tests/send-utils.test.js | 0 ui/app/ducks/send.js | 54 +++++ ui/app/reducers.js | 7 + ui/app/send-v2.js | 53 ++--- 94 files changed, 845 insertions(+), 37 deletions(-) create mode 100644 ui/app/components/page-container/page-container-content.component.js create mode 100644 ui/app/components/page-container/page-container-footer.component.js create mode 100644 ui/app/components/page-container/page-container-header.component.js create mode 100644 ui/app/components/page-container/page-container.component.js create mode 100644 ui/app/components/page-container/tests/page-container-content-component.test.js create mode 100644 ui/app/components/page-container/tests/page-container-footer-component.test.js create mode 100644 ui/app/components/page-container/tests/page-container-header-component.test.js create mode 100644 ui/app/components/send_/README.md create mode 100644 ui/app/components/send_/send-content/send-amount-row/README.md create mode 100644 ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/send-amount-row.scss create mode 100644 ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-selectors.test.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-utils.test.js create mode 100644 ui/app/components/send_/send-content/send-content-README.md create mode 100644 ui/app/components/send_/send-content/send-content.component.js create mode 100644 ui/app/components/send_/send-content/send-content.scss create mode 100644 ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown-README.md create mode 100644 ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js create mode 100644 ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.scss create mode 100644 ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js create mode 100644 ui/app/components/send_/send-content/send-from-row/send-from-row-README.md create mode 100644 ui/app/components/send_/send-content/send-from-row/send-from-row.component.js create mode 100644 ui/app/components/send_/send-content/send-from-row/send-from-row.container.js create mode 100644 ui/app/components/send_/send-content/send-from-row/send-from-row.selectors.js create mode 100644 ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js create mode 100644 ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js create mode 100644 ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js create mode 100644 ui/app/components/send_/send-content/send-from-row/tests/send-from-row-selectors.test.js create mode 100644 ui/app/components/send_/send-content/send-gas-row/README.md create mode 100644 ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js create mode 100644 ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js create mode 100644 ui/app/components/send_/send-content/send-gas-row/send-gas-row.scss create mode 100644 ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js create mode 100644 ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js create mode 100644 ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js create mode 100644 ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper-README.md create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.scss create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js create mode 100644 ui/app/components/send_/send-content/send-to-row/send-to-row-README.md create mode 100644 ui/app/components/send_/send-content/send-to-row/send-to-row.component.js create mode 100644 ui/app/components/send_/send-content/send-to-row/send-to-row.container.js create mode 100644 ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js create mode 100644 ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js create mode 100644 ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js create mode 100644 ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js create mode 100644 ui/app/components/send_/send-content/send-to-row/tests/send-to-row-selectors.test.js create mode 100644 ui/app/components/send_/send-content/tests/send-content-component.test.js create mode 100644 ui/app/components/send_/send-footer/README.md create mode 100644 ui/app/components/send_/send-footer/send-footer.component.js create mode 100644 ui/app/components/send_/send-footer/send-footer.container.js create mode 100644 ui/app/components/send_/send-footer/send-footer.scss create mode 100644 ui/app/components/send_/send-footer/send-footer.selectors.js create mode 100644 ui/app/components/send_/send-footer/send-footer.utils.js create mode 100644 ui/app/components/send_/send-footer/tests/send-footer-component.test.js create mode 100644 ui/app/components/send_/send-footer/tests/send-footer-container.test.js create mode 100644 ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js create mode 100644 ui/app/components/send_/send-footer/tests/send-footer-utils.test.js create mode 100644 ui/app/components/send_/send-header/README.md create mode 100644 ui/app/components/send_/send-header/send-header.component.js create mode 100644 ui/app/components/send_/send-header/send-header.container.js create mode 100644 ui/app/components/send_/send-header/tests/send-header-component.test.js create mode 100644 ui/app/components/send_/send-header/tests/send-header-container.test.js create mode 100644 ui/app/components/send_/send.component.js create mode 100644 ui/app/components/send_/send.container.js create mode 100644 ui/app/components/send_/send.scss create mode 100644 ui/app/components/send_/send.selectors.js create mode 100644 ui/app/components/send_/send.utils.js create mode 100644 ui/app/components/send_/tests/send-component.test.js create mode 100644 ui/app/components/send_/tests/send-container.test.js create mode 100644 ui/app/components/send_/tests/send-selectors.test.js create mode 100644 ui/app/components/send_/tests/send-utils.test.js create mode 100644 ui/app/ducks/send.js (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index ad4270cef..aae037a2d 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -167,6 +167,8 @@ var actions = { UPDATE_MAX_MODE: 'UPDATE_MAX_MODE', UPDATE_SEND: 'UPDATE_SEND', CLEAR_SEND: 'CLEAR_SEND', + OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN', + CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN', updateGasLimit, updateGasPrice, updateGasTotal, diff --git a/ui/app/components/page-container/page-container-content.component.js b/ui/app/components/page-container/page-container-content.component.js new file mode 100644 index 000000000..ffd62894c --- /dev/null +++ b/ui/app/components/page-container/page-container-content.component.js @@ -0,0 +1,18 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +export default class PageContainerContent extends Component { + + static propTypes = { + children: PropTypes.node.isRequired, + }; + + render () { + return ( +
+ {this.props.children} +
+ ); + } + +} diff --git a/ui/app/components/page-container/page-container-footer.component.js b/ui/app/components/page-container/page-container-footer.component.js new file mode 100644 index 000000000..0ef14c9d7 --- /dev/null +++ b/ui/app/components/page-container/page-container-footer.component.js @@ -0,0 +1,41 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +export default class PageContainerFooter extends Component { + + static propTypes = { + onCancel: PropTypes.func, + onSubmit: PropTypes.func, + disabled: PropTypes.bool, + }; + + render () { + const { onCancel, onSubmit, disabled } = this.props + + return ( +
+ + + + + +
+ ); + } + +} + +PageContainerFooter.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/page-container/page-container-header.component.js b/ui/app/components/page-container/page-container-header.component.js new file mode 100644 index 000000000..9adc88fb3 --- /dev/null +++ b/ui/app/components/page-container/page-container-header.component.js @@ -0,0 +1,35 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +export default class PageContainerHeader extends Component { + + static propTypes = { + title: PropTypes.string, + subtitle: PropTypes.string, + onClose: PropTypes.func, + }; + + render () { + const { title, subtitle, onClose } = this.props + + return ( +
+ +
+ {title} +
+ +
+ {subtitle} +
+ +
onClose()} + /> + +
+ ); + } + +} diff --git a/ui/app/components/page-container/page-container.component.js b/ui/app/components/page-container/page-container.component.js new file mode 100644 index 000000000..7df1d48d8 --- /dev/null +++ b/ui/app/components/page-container/page-container.component.js @@ -0,0 +1,18 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +export default class PageContainer extends Component { + + static propTypes = { + children: PropTypes.node.isRequired, + }; + + render () { + return ( +
+ {this.props.children} +
+ ); + } + +} diff --git a/ui/app/components/page-container/tests/page-container-content-component.test.js b/ui/app/components/page-container/tests/page-container-content-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/page-container/tests/page-container-footer-component.test.js b/ui/app/components/page-container/tests/page-container-footer-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/page-container/tests/page-container-header-component.test.js b/ui/app/components/page-container/tests/page-container-header-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/README.md b/ui/app/components/send_/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/README.md b/ui/app/components/send_/send-content/send-amount-row/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js new file mode 100644 index 000000000..6ae80e7f2 --- /dev/null +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js @@ -0,0 +1,48 @@ +import { + getSelectedToken, + getPrimaryCurrency, + getAmountConversionRate, + getConvertedCurrency, + getSendAmount, + getGasTotal, + getSelectedBalance, + getTokenBalance, +} from '../../send.selectors.js' +import { + getMaxModeOn, + getSendAmountError, +} from './send-amount-row.selectors.js' +import { getAmountErrorObject } from './send-to-row.utils.js' +import { + updateSendErrors, + updateSendTo, +} from '../../../actions' +import { + openToDropdown, + closeToDropdown, +} from '../../../ducks/send' +import SendToRow from './send-to-row.component' + +export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) + +function mapStateToProps (state) { +updateSendTo +return { + to: getSendTo(state), + toAccounts: getSendToAccounts(state), + toDropdownOpen: getToDropdownOpen(state), + inError: sendToIsInError(state), + network: getCurrentNetwork(state), +} +} + +function mapDispatchToProps (dispatch) { +return { + updateSendToError: (to) => { + dispatch(updateSendErrors(getToErrorObject(to))) + }, + updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)), + openToDropdown: () => dispatch(()), + closeToDropdown: () => dispatch(()), +} +} \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.scss b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-selectors.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-selectors.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-utils.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-utils.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-content-README.md b/ui/app/components/send_/send-content/send-content-README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-content.scss b/ui/app/components/send_/send-content/send-content.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown-README.md b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown-README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.scss b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row-README.md b/ui/app/components/send_/send-content/send-from-row/send-from-row-README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js new file mode 100644 index 000000000..7582cb2e6 --- /dev/null +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js @@ -0,0 +1,64 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import SendRowWrapper from '../../../send/from-dropdown' +import FromDropdown from '' + +export default class SendFromRow extends Component { + + static propTypes = { + closeFromDropdown: PropTypes.func, + conversionRate: PropTypes.string, + from: PropTypes.string, + fromAccounts: PropTypes.array, + fromDropdownOpen: PropTypes.bool, + openFromDropdown: PropTypes.func, + tokenContract: PropTypes.object, + updateSendFrom: PropTypes.func, + updateSendTokenBalance: PropTypes.func, + }; + + async handleFromChange (newFrom) { + const { + updateSendFrom, + tokenContract, + updateSendTokenBalance, + } = this.props + + if (tokenContract) { + const usersToken = await tokenContract.balanceOf(newFrom.address) + updateSendTokenBalance(usersToken) + } + updateSendFrom(newFrom) + } + + render () { + const { + from, + fromAccounts, + conversionRate, + fromDropdownOpen, + tokenContract, + openFromDropdown, + closeFromDropdown, + } = this.props + + return ( + + this.handleFromChange(newFrom)} + openDropdown={() => openFromDropdown()} + closeDropdown={() => closeFromDropdown()} + conversionRate={conversionRate} + /> + + ); + } + +} + +SendFromRow.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js new file mode 100644 index 000000000..2ff3f0ccd --- /dev/null +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js @@ -0,0 +1,44 @@ +import { + getSendFrom, + conversionRateSelector, + getSelectedTokenContract, + getCurrentAccountWithSendEtherInfo, + accountsWithSendEtherInfoSelector, +} from '../../send.selectors.js' +import { getFromDropdownOpen } from './send-from-row.selectors.js' +import { calcTokenUpdateAmount } from './send-from-row.utils.js' +import { + updateSendTokenBalance, + updateSendFrom, +} from '../../../actions' +import { + openFromDropdown, + closeFromDropdown, +} from '../../../ducks/send' +import SendFromRow from './send-from-row.component' + +export default connect(mapStateToProps, mapDispatchToProps)(SendFromRow) + +function mapStateToProps (state) { + return { + from: getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state), + fromAccounts: accountsWithSendEtherInfoSelector(state), + conversionRate: conversionRateSelector(state), + fromDropdownOpen: getFromDropdownOpen(state), + tokenContract: getSelectedTokenContract(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + updateSendTokenBalance: (usersToken, selectedToken) => { + if (!usersToken) return + + const tokenBalance = calcTokenUpdateAmount(selectedToken, selectedToken) + dispatch(updateSendTokenBalance(tokenBalance)) + }, + updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)), + openFromDropdown: () => dispatch(()), + closeFromDropdown: () => dispatch(()), + } +} diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.selectors.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.selectors.js new file mode 100644 index 000000000..03ef4806b --- /dev/null +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.selectors.js @@ -0,0 +1,9 @@ +const selectors = { + getFromDropdownOpen, +} + +module.exports = selectors + +function getFromDropdownOpen (state) { + return state.send.fromDropdownOpen +} diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js new file mode 100644 index 000000000..2be25816f --- /dev/null +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js @@ -0,0 +1,12 @@ +const { + calcTokenAmount, +} = require('../../token-util') + +function calcTokenUpdateAmount (usersToken, selectedToken) { + const { decimals } = selectedToken || {} + return calcTokenAmount(usersToken.balance.toString(), decimals) +} + +module.exports = { + calcTokenUpdateAmount +} diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-selectors.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-selectors.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-gas-row/README.md b/ui/app/components/send_/send-content/send-gas-row/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.scss b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js new file mode 100644 index 000000000..08f830cc5 --- /dev/null +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js @@ -0,0 +1,23 @@ +export default class SendRowErrorMessage extends Component { + + static propTypes = { + errors: PropTypes.object, + errorType: PropTypes.string, + }; + + render () { + const { errors, errorType } = this.props + const errorMessage = errors[errorType] + + return ( + errorMessage + ?
{errorMessage}
+ : null + ); + } + +} + +SendRowErrorMessage.contextTypes = { + t: PropTypes.func, +} \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js new file mode 100644 index 000000000..2278dbe63 --- /dev/null +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js @@ -0,0 +1,11 @@ +import { getSendErrors } from '../../../send.selectors' +import SendRowErrorMessage from './send-row-error-message.component' + +export default connect(mapStateToProps)(SendRowErrorMessage) + +function mapStateToProps (state, ownProps) { + return { + errors: getSendErrors(state), + errorType: ownProps.errorType, + } +} \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper-README.md b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper-README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js new file mode 100644 index 000000000..a1ac591b9 --- /dev/null +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js @@ -0,0 +1,39 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import SendRowErrorMessage from './send-row-error-message/send-row-error-message.container' + +export default class SendRowWrapper extends Component { + + static propTypes = { + label: PropTypes.string, + showError: PropTypes.bool, + children: PropTypes.node, + errorType: PropTypes.string, + }; + + render () { + const { + label, + errorType = '', + showError = false, + children, + } = this.props + + return ( +
+
+ {label} + (showError && ) +
+
+ {children} +
+
+ ); + } + +} + +SendRowWrapper.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.scss b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js b/ui/app/components/send_/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row-README.md b/ui/app/components/send_/send-content/send-to-row/send-to-row-README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js new file mode 100644 index 000000000..abcb54efc --- /dev/null +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js @@ -0,0 +1,62 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import SendRowWrapper from '../../../send/from-dropdown' +import ToDropdown from '../../../ens-input' + +export default class SendToRow extends Component { + + static propTypes = { + to: PropTypes.string, + toAccounts: PropTypes.array, + toDropdownOpen: PropTypes.bool, + inError: PropTypes.bool, + updateSendTo: PropTypes.func, + updateSendToError: PropTypes.func, + openToDropdown: PropTypes.func, + closeToDropdown: PropTypes.func, + network: PropTypes.number, + }; + + handleToChange (to, nickname = '') { + const { updateSendTo, updateSendToError } = this.props + updateSendTo(to, nickname) + updateSendErrors(to) + } + + render () { + const { + from, + fromAccounts, + conversionRate, + fromDropdownOpen, + tokenContract, + openToDropdown, + closeToDropdown, + network, + inError, + } = this.props + + return ( + + openToDropdown()} + closeDropdown={() => closeToDropdown()} + onChange={this.handleToChange} + inError={inError} + /> + + ); + } + +} + +SendToRow.contextTypes = { + t: PropTypes.func, +} + diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js new file mode 100644 index 000000000..1c446c168 --- /dev/null +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js @@ -0,0 +1,43 @@ +import { + getSendTo, + getToAccounts, + getCurrentNetwork, +} from '../../send.selectors.js' +import { + getToDropdownOpen, + sendToIsInError, +} from './send-to-row.selectors.js' +import { getToErrorObject } from './send-to-row.utils.js' +import { + updateSendErrors, + updateSendTo, +} from '../../../actions' +import { + openToDropdown, + closeToDropdown, +} from '../../../ducks/send' +import SendToRow from './send-to-row.component' + +export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) + +function mapStateToProps (state) { + updateSendTo + return { + to: getSendTo(state), + toAccounts: getSendToAccounts(state), + toDropdownOpen: getToDropdownOpen(state), + inError: sendToIsInError(state), + network: getCurrentNetwork(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + updateSendToError: (to) => { + dispatch(updateSendErrors(getToErrorObject(to))) + }, + updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)), + openToDropdown: () => dispatch(()), + closeToDropdown: () => dispatch(()), + } +} \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js new file mode 100644 index 000000000..05bb65fa3 --- /dev/null +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js @@ -0,0 +1,14 @@ +const selectors = { + getToDropdownOpen, + sendToIsInError, +} + +module.exports = selectors + +function getToDropdownOpen (state) { + return state.send.toDropdownOpen +} + +function sendToIsInError (state) { + return Boolean(state.metamask.send.to) +} diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js new file mode 100644 index 000000000..52bfde009 --- /dev/null +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js @@ -0,0 +1,17 @@ +const { isValidAddress } = require('../../../../util') + +function getToErrorObject (to) { + let toError = null + + if (!to) { + toError = 'required' + } else if (!isValidAddress(to)) { + toError = 'invalidAddressRecipient' + } + + return { to: toError } +} + +module.exports = { + getToErrorObject +} diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-selectors.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-selectors.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/tests/send-content-component.test.js b/ui/app/components/send_/send-content/tests/send-content-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-footer/README.md b/ui/app/components/send_/send-footer/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-footer/send-footer.component.js b/ui/app/components/send_/send-footer/send-footer.component.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-footer/send-footer.scss b/ui/app/components/send_/send-footer/send-footer.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-footer/send-footer.selectors.js b/ui/app/components/send_/send-footer/send-footer.selectors.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-footer/send-footer.utils.js b/ui/app/components/send_/send-footer/send-footer.utils.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-footer/tests/send-footer-container.test.js b/ui/app/components/send_/send-footer/tests/send-footer-container.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js b/ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js b/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-header/README.md b/ui/app/components/send_/send-header/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-header/send-header.component.js b/ui/app/components/send_/send-header/send-header.component.js new file mode 100644 index 000000000..99adfc7e8 --- /dev/null +++ b/ui/app/components/send_/send-header/send-header.component.js @@ -0,0 +1,32 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import PageContainerHeader from '../../page-container/page-container-header.component' + +export default class SendHeader extends Component { + + static propTypes = { + isToken: PropTypes.bool, + clearSend: PropTypes.func, + goHome: PropTypes.func, + }; + + render () { + const { isToken, clearSend, goHome } = this.props + + return ( + { + clearSend() + goHome() + }} + /> + ); + } + +} + +SendHeader.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-header/send-header.container.js b/ui/app/components/send_/send-header/send-header.container.js new file mode 100644 index 000000000..a4d3ac54f --- /dev/null +++ b/ui/app/components/send_/send-header/send-header.container.js @@ -0,0 +1,19 @@ +import { connect } from 'react-redux' +import { goHome, clearSend } from '../../../actions' +import SendHeader from './send-header.component' +import { getSelectedToken } from '../../../selectors' + +export default connect(mapStateToProps, mapDispatchToProps)(SendHeader) + +function mapStateToProps (state) { + return { + isToken: Boolean(getSelectedToken(state)) + } +} + +function mapDispatchToProps (dispatch) { + return { + goHome: () => dispatch(goHome()), + clearSend: () => dispatch(clearSend()), + } +} diff --git a/ui/app/components/send_/send-header/tests/send-header-component.test.js b/ui/app/components/send_/send-header/tests/send-header-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-header/tests/send-header-container.test.js b/ui/app/components/send_/send-header/tests/send-header-container.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send.scss b/ui/app/components/send_/send.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js new file mode 100644 index 000000000..8c088098e --- /dev/null +++ b/ui/app/components/send_/send.selectors.js @@ -0,0 +1,217 @@ +import { valuesFor } from '../../util' +import abi from 'human-standard-token-abi' +import { + multiplyCurrencies, +} from './conversion-util' + +const selectors = { + getSelectedAddress, + getSelectedIdentity, + getSelectedAccount, + getSelectedToken, + getSelectedTokenExchangeRate, + getTokenExchangeRate, + conversionRateSelector, + transactionsSelector, + accountsWithSendEtherInfoSelector, + getCurrentAccountWithSendEtherInfo, + getGasPrice, + getGasLimit, + getForceGasMin, + getAddressBook, + getSendFrom, + getCurrentCurrency, + getSendAmount, + getSelectedTokenToFiatRate, + getSelectedTokenContract, + autoAddToBetaUI, + getSendMaxModeState, + getCurrentViewContext, + getSendErrors, + getSendTo, + getCurrentNetwork, +} + +module.exports = selectors + +function getSelectedAddress (state) { + const selectedAddress = state.metamask.selectedAddress || Object.keys(state.metamask.accounts)[0] + + return selectedAddress +} + +function getSelectedIdentity (state) { + const selectedAddress = getSelectedAddress(state) + const identities = state.metamask.identities + + return identities[selectedAddress] +} + +function getSelectedAccount (state) { + const accounts = state.metamask.accounts + const selectedAddress = getSelectedAddress(state) + + return accounts[selectedAddress] +} + +function getSelectedToken (state) { + const tokens = state.metamask.tokens || [] + const selectedTokenAddress = state.metamask.selectedTokenAddress + const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0] + const sendToken = state.metamask.send.token + + return selectedToken || sendToken || null +} + +function getSelectedTokenExchangeRate (state) { + const tokenExchangeRates = state.metamask.tokenExchangeRates + const selectedToken = getSelectedToken(state) || {} + const { symbol = '' } = selectedToken + + const pair = `${symbol.toLowerCase()}_eth` + const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {} + + return tokenExchangeRate +} + +function getTokenExchangeRate (state, tokenSymbol) { + const pair = `${tokenSymbol.toLowerCase()}_eth` + const tokenExchangeRates = state.metamask.tokenExchangeRates + const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {} + + return tokenExchangeRate +} + +function conversionRateSelector (state) { + return state.metamask.conversionRate +} + +function getAddressBook (state) { + return state.metamask.addressBook +} + +function accountsWithSendEtherInfoSelector (state) { + const { + accounts, + identities, + } = state.metamask + + const accountsWithSendEtherInfo = Object.entries(accounts).map(([key, account]) => { + return Object.assign({}, account, identities[key]) + }) + + return accountsWithSendEtherInfo +} + +function getCurrentAccountWithSendEtherInfo (state) { + const currentAddress = getSelectedAddress(state) + const accounts = accountsWithSendEtherInfoSelector(state) + + return accounts.find(({ address }) => address === currentAddress) +} + +function transactionsSelector (state) { + const { network, selectedTokenAddress } = state.metamask + const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs) + const shapeShiftTxList = (network === '1') ? state.metamask.shapeShiftTxList : undefined + const transactions = state.metamask.selectedAddressTxList || [] + const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList) + + // console.log({txsToRender, selectedTokenAddress}) + return selectedTokenAddress + ? txsToRender + .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress) + .sort((a, b) => b.time - a.time) + : txsToRender + .sort((a, b) => b.time - a.time) +} + +function getGasPrice (state) { + return state.metamask.send.gasPrice +} + +function getGasLimit (state) { + return state.metamask.send.gasLimit +} + +function getForceGasMin (state) { + return state.metamask.send.forceGasMin +} + +function getSendFrom (state) { + return state.metamask.send.from +} + +function getSendAmount (state) { + return state.metamask.send.amount +} + +function getSendMaxModeState (state) { + return state.metamask.send.maxModeOn +} + +function getCurrentCurrency (state) { + return state.metamask.currentCurrency +} + +function getSelectedTokenToFiatRate (state) { + const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state) + const conversionRate = conversionRateSelector(state) + + const tokenToFiatRate = multiplyCurrencies( + conversionRate, + selectedTokenExchangeRate, + { toNumericBase: 'dec' } + ) + + return tokenToFiatRate +} + +function getSelectedTokenContract (state) { + const selectedToken = getSelectedToken(state) + return selectedToken + ? global.eth.contract(abi).at(selectedToken.address) + : null +} + +function autoAddToBetaUI (state) { + const autoAddTransactionThreshold = 12 + const autoAddAccountsThreshold = 2 + const autoAddTokensThreshold = 1 + + const numberOfTransactions = state.metamask.selectedAddressTxList.length + const numberOfAccounts = Object.keys(state.metamask.accounts).length + const numberOfTokensAdded = state.metamask.tokens.length + + const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) && + (numberOfAccounts > autoAddAccountsThreshold) && + (numberOfTokensAdded > autoAddTokensThreshold) + const userIsNotInBeta = !state.metamask.featureFlags.betaUI + + return userIsNotInBeta && userPassesThreshold +} + +function getCurrentViewContext (state) { + const { currentView = {} } = state.appState + return currentView.context +} + +function getSendErrors (state) { + return state.metamask.send.errors +} + +function getSendTo (state) { + return state.metamask.send.to +} + +function getSendToAccounts (state) { + const fromAccounts = accountsWithSendEtherInfoSelector(state) + const addressBookAccounts = getAddressBook(state) + const allAccounts = [...fromAccounts, ...addressBookAccounts] + // TODO: figure out exactly what the below returns and put a descriptive variable name on it + return Object.entries(allAccounts).map(([key, account]) => account) +} + +function getCurrentNetwork (state) { + return state.metamask.network +} \ No newline at end of file diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/tests/send-container.test.js b/ui/app/components/send_/tests/send-container.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/tests/send-selectors.test.js b/ui/app/components/send_/tests/send-selectors.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/ducks/send.js b/ui/app/ducks/send.js new file mode 100644 index 000000000..aeca9f92f --- /dev/null +++ b/ui/app/ducks/send.js @@ -0,0 +1,54 @@ +import extend from 'xtend' + +// Actions +const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN'; +const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN'; +const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'; +const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'; + +// TODO: determine if this approach to initState is consistent with conventional ducks pattern +const initState = { + fromDropdownOpen: false, + toDropdownOpen: false, +} + +// Reducer +export default function reducer(state = initState, action = {}) { + switch (action.type) { + case OPEN_FROM_DROPDOWN: + return extend(sendState, { + fromDropdownOpen: true, + }) + case CLOSE_FROM_DROPDOWN: + return extend(sendState, { + fromDropdownOpen: false, + }) + case OPEN_TO_DROPDOWN: + return extend(sendState, { + toDropdownOpen: true, + }) + case CLOSE_TO_DROPDOWN: + return extend(sendState, { + toDropdownOpen: false, + }) + default: + return sendState + } +} + +// Action Creators +export function openFromDropdown() { + return { type: OPEN_FROM_DROPDOWN }; +} + +export function closeFromDropdown() { + return { type: CLOSE_FROM_DROPDOWN }; +} + +export function openToDropdown() { + return { type: OPEN_TO_DROPDOWN }; +} + +export function closeToDropdown() { + return { type: CLOSE_TO_DROPDOWN }; +} \ No newline at end of file diff --git a/ui/app/reducers.js b/ui/app/reducers.js index f155b2bf3..ff766e856 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -8,6 +8,7 @@ const reduceIdentities = require('./reducers/identities') const reduceMetamask = require('./reducers/metamask') const reduceApp = require('./reducers/app') const reduceLocale = require('./reducers/locale') +const reduceSend = require('./ducks/send') window.METAMASK_CACHED_LOG_STATE = null @@ -45,6 +46,12 @@ function rootReducer (state, action) { state.localeMessages = reduceLocale(state, action) + // + // Send + // + + state.send = reduceSend(state, action) + window.METAMASK_CACHED_LOG_STATE = state return state } diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index 094743ff0..d608957c8 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -31,6 +31,11 @@ const { } = require('./components/send/send-utils') const { isValidAddress } = require('./util') +import PageContainer from './components/page-container/page-container.component' +import SendHeader from './components/send_/send-header/send-header.container' +import PageContainerContent from './components/page-container/page-container-content.component' +import PageContainerFooter from './components/page-container/page-container-footer.component' + SendTransactionScreen.contextTypes = { t: PropTypes.func, } @@ -181,25 +186,6 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { } } -SendTransactionScreen.prototype.renderHeader = function () { - const { selectedToken, clearSend, goHome } = this.props - - return h('div.page-container__header', [ - - h('div.page-container__title', selectedToken ? this.context.t('sendTokens') : this.context.t('sendETH')), - - h('div.page-container__subtitle', this.context.t('onlySendToEtherAddress')), - - h('div.page-container__header-close', { - onClick: () => { - clearSend() - goHome() - }, - }), - - ]) -} - SendTransactionScreen.prototype.renderErrorMessage = function (errorType) { const { errors } = this.props const errorMessage = errors[errorType] @@ -477,7 +463,7 @@ SendTransactionScreen.prototype.renderMemoRow = function () { } SendTransactionScreen.prototype.renderForm = function () { - return h('.page-container__content', {}, [ + return h(PageContainerContent, [ h('.send-v2__form', [ this.renderFromRow(), @@ -486,9 +472,6 @@ SendTransactionScreen.prototype.renderForm = function () { this.renderAmountRow(), this.renderGasRow(), - - // this.renderMemoRow(), - ]), ]) } @@ -506,26 +489,22 @@ SendTransactionScreen.prototype.renderFooter = function () { const missingTokenBalance = selectedToken && !tokenBalance const noErrors = !amountError && toError === null - return h('div.page-container__footer', [ - h('button.btn-secondary--lg.page-container__footer-button', { - onClick: () => { - clearSend() - goHome() - }, - }, this.context.t('cancel')), - h('button.btn-primary--lg.page-container__footer-button', { - disabled: !noErrors || !gasTotal || missingTokenBalance, - onClick: event => this.onSubmit(event), - }, this.context.t('next')), - ]) + return h(PageContainerFooter, { + onCancel: () => { + clearSend() + goHome() + }, + onSubmit: e => this.onSubmit(e), + disabled: !noErrors || !gasTotal || missingTokenBalance, + }) } SendTransactionScreen.prototype.render = function () { return ( - h('div.page-container', [ + h(PageContainer, [ - this.renderHeader(), + h(SendHeader), this.renderForm(), -- cgit From f96c13d616e429447ac0a6a24c6aeee902162b88 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 10 Apr 2018 15:28:52 -0700 Subject: Refactor page-container component structure --- ui/app/components/page-container/index.js | 1 + .../page-container-content.component.js | 18 ------- .../page-container-footer.component.js | 41 --------------- .../page-container/page-container-footer/index.js | 1 + .../page-container-footer.component.js | 49 ++++++++++++++++++ .../tests/page-container-footer.component.test.js | 0 .../page-container-header.component.js | 35 ------------- .../page-container/page-container-header/index.js | 1 + .../page-container-header.component.js | 57 ++++++++++++++++++++ .../tests/page-container-header.component.test.js | 0 .../page-container/page-container.component.js | 60 ++++++++++++++++++++-- .../tests/page-container-content-component.test.js | 0 .../tests/page-container-footer-component.test.js | 0 .../tests/page-container-header-component.test.js | 0 .../tests/page-container.component.test.js | 0 ui/app/components/send/send-v2-container.js | 1 + ui/app/send-v2.js | 46 +++++++++++++---- 17 files changed, 204 insertions(+), 106 deletions(-) create mode 100644 ui/app/components/page-container/index.js delete mode 100644 ui/app/components/page-container/page-container-content.component.js delete mode 100644 ui/app/components/page-container/page-container-footer.component.js create mode 100644 ui/app/components/page-container/page-container-footer/index.js create mode 100644 ui/app/components/page-container/page-container-footer/page-container-footer.component.js create mode 100644 ui/app/components/page-container/page-container-footer/tests/page-container-footer.component.test.js delete mode 100644 ui/app/components/page-container/page-container-header.component.js create mode 100644 ui/app/components/page-container/page-container-header/index.js create mode 100644 ui/app/components/page-container/page-container-header/page-container-header.component.js create mode 100644 ui/app/components/page-container/page-container-header/tests/page-container-header.component.test.js delete mode 100644 ui/app/components/page-container/tests/page-container-content-component.test.js delete mode 100644 ui/app/components/page-container/tests/page-container-footer-component.test.js delete mode 100644 ui/app/components/page-container/tests/page-container-header-component.test.js create mode 100644 ui/app/components/page-container/tests/page-container.component.test.js (limited to 'ui') diff --git a/ui/app/components/page-container/index.js b/ui/app/components/page-container/index.js new file mode 100644 index 000000000..415870b37 --- /dev/null +++ b/ui/app/components/page-container/index.js @@ -0,0 +1 @@ +export { default } from './page-container.component' diff --git a/ui/app/components/page-container/page-container-content.component.js b/ui/app/components/page-container/page-container-content.component.js deleted file mode 100644 index ffd62894c..000000000 --- a/ui/app/components/page-container/page-container-content.component.js +++ /dev/null @@ -1,18 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' - -export default class PageContainerContent extends Component { - - static propTypes = { - children: PropTypes.node.isRequired, - }; - - render () { - return ( -
- {this.props.children} -
- ); - } - -} diff --git a/ui/app/components/page-container/page-container-footer.component.js b/ui/app/components/page-container/page-container-footer.component.js deleted file mode 100644 index 0ef14c9d7..000000000 --- a/ui/app/components/page-container/page-container-footer.component.js +++ /dev/null @@ -1,41 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' - -export default class PageContainerFooter extends Component { - - static propTypes = { - onCancel: PropTypes.func, - onSubmit: PropTypes.func, - disabled: PropTypes.bool, - }; - - render () { - const { onCancel, onSubmit, disabled } = this.props - - return ( -
- - - - - -
- ); - } - -} - -PageContainerFooter.contextTypes = { - t: PropTypes.func, -} diff --git a/ui/app/components/page-container/page-container-footer/index.js b/ui/app/components/page-container/page-container-footer/index.js new file mode 100644 index 000000000..7825c4520 --- /dev/null +++ b/ui/app/components/page-container/page-container-footer/index.js @@ -0,0 +1 @@ +export { default } from './page-container-footer.component' diff --git a/ui/app/components/page-container/page-container-footer/page-container-footer.component.js b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js new file mode 100644 index 000000000..fafe1c19e --- /dev/null +++ b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js @@ -0,0 +1,49 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +export default class PageContainerFooter extends Component { + + static propTypes = { + onCancel: PropTypes.func, + cancelText: PropTypes.string, + onSubmit: PropTypes.func, + submitText: PropTypes.string, + disabled: PropTypes.bool, + } + + static contextTypes = { + t: PropTypes.func, + } + + render () { + const { + onCancel, + cancelText, + onSubmit, + submitText, + disabled, + } = this.props + + return ( +
+ + + + + +
+ ) + } + +} diff --git a/ui/app/components/page-container/page-container-footer/tests/page-container-footer.component.test.js b/ui/app/components/page-container/page-container-footer/tests/page-container-footer.component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/page-container/page-container-header.component.js b/ui/app/components/page-container/page-container-header.component.js deleted file mode 100644 index 9adc88fb3..000000000 --- a/ui/app/components/page-container/page-container-header.component.js +++ /dev/null @@ -1,35 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' - -export default class PageContainerHeader extends Component { - - static propTypes = { - title: PropTypes.string, - subtitle: PropTypes.string, - onClose: PropTypes.func, - }; - - render () { - const { title, subtitle, onClose } = this.props - - return ( -
- -
- {title} -
- -
- {subtitle} -
- -
onClose()} - /> - -
- ); - } - -} diff --git a/ui/app/components/page-container/page-container-header/index.js b/ui/app/components/page-container/page-container-header/index.js new file mode 100644 index 000000000..b194af057 --- /dev/null +++ b/ui/app/components/page-container/page-container-header/index.js @@ -0,0 +1 @@ +export { default } from './page-container-header.component' diff --git a/ui/app/components/page-container/page-container-header/page-container-header.component.js b/ui/app/components/page-container/page-container-header/page-container-header.component.js new file mode 100644 index 000000000..28882edce --- /dev/null +++ b/ui/app/components/page-container/page-container-header/page-container-header.component.js @@ -0,0 +1,57 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +export default class PageContainerHeader extends Component { + + static propTypes = { + title: PropTypes.string.isRequired, + subtitle: PropTypes.string, + onClose: PropTypes.func, + showBackButton: PropTypes.bool, + onBackButtonClick: PropTypes.func, + backButtonStyles: PropTypes.object, + backButtonString: PropTypes.string, + }; + + renderHeaderRow () { + const { showBackButton, onBackButtonClick, backButtonStyles, backButtonString } = this.props + + return showBackButton && ( +
+ + { backButtonString || 'Back' } + +
+ ) + } + + render () { + const { title, subtitle, onClose } = this.props + + return ( +
+ + { this.renderHeaderRow() } + +
+ {title} +
+ +
+ {subtitle} +
+ +
onClose()} + /> + +
+ ) + } + +} diff --git a/ui/app/components/page-container/page-container-header/tests/page-container-header.component.test.js b/ui/app/components/page-container/page-container-header/tests/page-container-header.component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/page-container/page-container.component.js b/ui/app/components/page-container/page-container.component.js index 7df1d48d8..9bfb99ade 100644 --- a/ui/app/components/page-container/page-container.component.js +++ b/ui/app/components/page-container/page-container.component.js @@ -1,18 +1,72 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' +import PageContainerHeader from './page-container-header' +import PageContainerFooter from './page-container-footer' + export default class PageContainer extends Component { static propTypes = { - children: PropTypes.node.isRequired, + // PageContainerHeader props + title: PropTypes.string.isRequired, + subtitle: PropTypes.string, + onClose: PropTypes.func, + showBackButton: PropTypes.bool, + onBackButtonClick: PropTypes.func, + backButtonStyles: PropTypes.object, + backButtonString: PropTypes.string, + // Content props + ContentComponent: PropTypes.func, + contentComponentProps: PropTypes.object, + // PageContainerFooter props + onCancel: PropTypes.func, + cancelText: PropTypes.string, + onSubmit: PropTypes.func, + submitText: PropTypes.string, + disabled: PropTypes.bool, }; render () { + const { + title, + subtitle, + onClose, + showBackButton, + onBackButtonClick, + backButtonStyles, + backButtonString, + ContentComponent, + contentComponentProps, + onCancel, + cancelText, + onSubmit, + submitText, + disabled, + } = this.props + return (
- {this.props.children} + +
+ +
+
- ); + ) } } diff --git a/ui/app/components/page-container/tests/page-container-content-component.test.js b/ui/app/components/page-container/tests/page-container-content-component.test.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/app/components/page-container/tests/page-container-footer-component.test.js b/ui/app/components/page-container/tests/page-container-footer-component.test.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/app/components/page-container/tests/page-container-header-component.test.js b/ui/app/components/page-container/tests/page-container-header-component.test.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/app/components/page-container/tests/page-container.component.test.js b/ui/app/components/page-container/tests/page-container.component.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js index 08c26a91f..edd0657f7 100644 --- a/ui/app/components/send/send-v2-container.js +++ b/ui/app/components/send/send-v2-container.js @@ -53,6 +53,7 @@ function mapStateToProps (state) { tokenContract: getSelectedTokenContract(state), unapprovedTxs: state.metamask.unapprovedTxs, network: state.metamask.network, + isToken: Boolean(getSelectedToken(state)), } } diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index d608957c8..228cb22d0 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -31,10 +31,10 @@ const { } = require('./components/send/send-utils') const { isValidAddress } = require('./util') -import PageContainer from './components/page-container/page-container.component' -import SendHeader from './components/send_/send-header/send-header.container' -import PageContainerContent from './components/page-container/page-container-content.component' -import PageContainerFooter from './components/page-container/page-container-footer.component' +import PageContainer from './components/page-container' +// import SendHeader from './components/send_/send-header/send-header.container' +// import PageContainerContent from './components/page-container/page-container-content.component' +// import PageContainerFooter from './components/page-container/page-container-footer.component' SendTransactionScreen.contextTypes = { t: PropTypes.func, @@ -500,16 +500,44 @@ SendTransactionScreen.prototype.renderFooter = function () { } SendTransactionScreen.prototype.render = function () { + const { + isToken, + clearSend, + goHome, + gasTotal, + tokenBalance, + selectedToken, + errors: { amount: amountError, to: toError }, + } = this.props + + const missingTokenBalance = selectedToken && !tokenBalance + const noErrors = !amountError && toError === null + return ( - h(PageContainer, [ + h(PageContainer, { + title: isToken ? this.context.t('sendTokens') : this.context.t('sendETH'), + subtitle: this.context.t('onlySendToEtherAddress'), + onClose: () => { + clearSend() + goHome() + }, + ContentComponent: this.renderForm, + onCancel: () => { + clearSend() + goHome() + }, + onSubmit: e => this.onSubmit(e), + disabled: !noErrors || !gasTotal || missingTokenBalance, + }) + // , [ - h(SendHeader), + // h(SendHeader), - this.renderForm(), + // this.renderForm(), - this.renderFooter(), - ]) + // this.renderFooter(), + // ]) ) } -- cgit From 59c887301aba5d746d669441ec78ef7ec5de3146 Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 11 Apr 2018 11:51:54 -0230 Subject: second commit --- .../amount-max-button.component.js | 54 +++++++++++++ .../amount-max-button.container.js | 36 +++++++++ .../amount-max-button.selectors.js | 0 .../amount-max-button/amount-max-button.utils.js | 22 ++++++ .../send-amount-row/send-amount-row.component.js | 91 ++++++++++++++++++++++ .../send-amount-row/send-amount-row.container.js | 45 +++++------ .../send-amount-row/send-amount-row.selectors.js | 14 ++++ .../send-amount-row/send-amount-row.utils.js | 55 +++++++++++++ .../send_/send-content/send-content.component.js | 23 ++++++ .../send-from-row/send-from-row.component.js | 4 +- .../send-from-row/send-from-row.container.js | 4 +- .../send-gas-row/send-gas-row.component.js | 60 ++++++++++++++ .../send-gas-row/send-gas-row.container.js | 26 +++++++ .../send-gas-row/send-gas-row.selectors.js | 9 +++ .../send-row-wrapper/send-row-wrapper.component.js | 11 ++- .../send-to-row/send-to-row.component.js | 8 +- .../send-to-row/send-to-row.selectors.js | 2 +- ui/app/components/send_/send.selectors.js | 47 ++++++----- 18 files changed, 461 insertions(+), 50 deletions(-) delete mode 100644 ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js index e69de29bb..59a1fd6db 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js @@ -0,0 +1,54 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +export default class AmountMaxButton extends Component { + + static propTypes = { + tokenBalance: PropTypes.string, + gasTotal: PropTypes.string, + balance: PropTypes.string, + selectedToken: PropTypes.object, + setAmountToMax: PropTypes.func, + setMaxModeTo: PropTypes.func, + maxModeOn: PropTypes.bool, + }; + + setAmountToMax = function () { + const { + balance, + tokenBalance, + selectedToken, + gasTotal, + setAmountToMax, + } = this.props + + setAmountToMax({ + tokenBalance, + selectedToken, + gasTotal, + setAmountToMax, + }) + } + + render () { + const { setMaxModeTo } = this.props + + return ( +
{ + event.preventDefault() + setMaxModeTo(true) + this.setAmountToMax() + }} + > + {!maxModeOn ? this.context.t('max') : '' ])} +
+ ); + } + +} + +AmountMaxButton.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js index e69de29bb..572e1fc46 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js @@ -0,0 +1,36 @@ +import { + getSelectedToken, + getGasTotal, + getTokenBalance, + getSendFromBalance, +} from '../../../send.selectors.js' +import { getMaxModeOn } from '../send-amount-row.selectors.js' +import { calcMaxAmount } from './amount-max-button.utils.js' +import { + updateSendAmount, + setMaxModeTo, +} from '../../../actions' +import AmountMaxButton from './amount-max-button.component' + +export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) + +function mapStateToProps (state) { + + return { + selectedToken: getSelectedToken(state), + maxModeOn: getMaxModeOn(state), + gasTotal: getGasTotal(state), + tokenBalance: getTokenBalance(state), + balance: getSendFromBalance(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + setAmountToMax: maxAmountDataObject => { + updateSendErrors({ amount: null }) + updateSendAmount(calcMaxAmount(maxAmountDataObject)) + } + setMaxModeTo: bool => dispatch(setMaxModeTo(bool)), + } +} \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js index e69de29bb..54aacc8d7 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js @@ -0,0 +1,22 @@ +const { + multiplyCurrencies, + subtractCurrencies, +} = require('../../../../conversion-util') +const ethUtil = require('ethereumjs-util') + +function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) { + const { decimals } = selectedToken || {} + const multiplier = Math.pow(10, Number(decimals || 0)) + + return selectedToken + ? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'}) + : subtractCurrencies( + ethUtil.addHexPrefix(balance), + ethUtil.addHexPrefix(gasTotal), + { toNumericBase: 'hex' } + ) +} + +module.exports = { + calcMaxAmount +} diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index e69de29bb..78038f714 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -0,0 +1,91 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' +import AmountMaxButton from '../amount-max-button/amount-max-button.component' +import CurrencyDisplay from '../../../send/currency-display' + +export default class SendAmountRow extends Component { + + static propTypes = { + amountConversionRate: PropTypes.string, + conversionRate: PropTypes.string, + from: PropTypes.object, + gasTotal: PropTypes.string, + primaryCurrency: PropTypes.string, + selectedToken: PropTypes.object, + tokenBalance: PropTypes.string, + updateSendAmountError: PropTypes.func, + updateSendAmount: PropTypes.func, + setMaxModeTo: PropTypes.func + } + + validateAmount (amount) { + const { + amountConversionRate, + conversionRate, + from: { balance }, + gasTotal, + primaryCurrency, + selectedToken, + tokenBalance, + updateSendAmountError, + } = this.props + + updateSendAmountError({ + amount, + amountConversionRate, + balance, + conversionRate, + gasTotal, + primaryCurrency, + selectedToken, + tokenBalance, + }) + } + + handleAmountChange (amount) { + const { updateSendAmount, setMaxModeTo } = this.props + + setMaxModeTo(false) + this.validateAmount(amount) + updateSendAmount(amount) + } + + render () { + const { + amount, + amountConversionRate, + convertedCurrency, + inError, + gasTotal, + maxModeOn, + primaryCurrency = 'ETH', + selectedToken, + } = this.props + + return ( + + !inError && gasTotal && + + + ); + } + +} + +SendAmountRow.contextTypes = { + t: PropTypes.func, +} + diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js index 6ae80e7f2..098855a02 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js @@ -7,42 +7,43 @@ import { getGasTotal, getSelectedBalance, getTokenBalance, + getSendFromBalance, } from '../../send.selectors.js' import { getMaxModeOn, - getSendAmountError, + sendAmountIsInError, } from './send-amount-row.selectors.js' -import { getAmountErrorObject } from './send-to-row.utils.js' +import { getAmountErrorObject } from './send-amount-row.utils.js' import { - updateSendErrors, - updateSendTo, + updateSendAmount, + setMaxModeTo, } from '../../../actions' -import { - openToDropdown, - closeToDropdown, -} from '../../../ducks/send' -import SendToRow from './send-to-row.component' +import SendAmountRow from './send-amount-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) function mapStateToProps (state) { updateSendTo return { - to: getSendTo(state), - toAccounts: getSendToAccounts(state), - toDropdownOpen: getToDropdownOpen(state), - inError: sendToIsInError(state), - network: getCurrentNetwork(state), + selectedToken: getSelectedToken(state), + primaryCurrency: getPrimaryCurrency(state), + convertedCurrency: getConvertedCurrency(state), + amountConversionRate: getAmountConversionRate(state), + inError: sendAmountIsInError(state), + amount: getSendAmount(state), + maxModeOn: getMaxModeOn(state), + gasTotal: getGasTotal(state), + tokenBalance: getTokenBalance(state), + balance: getSendFromBalance(state), } } function mapDispatchToProps (dispatch) { -return { - updateSendToError: (to) => { - dispatch(updateSendErrors(getToErrorObject(to))) - }, - updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)), - openToDropdown: () => dispatch(()), - closeToDropdown: () => dispatch(()), -} + return { + updateSendAmountError: (amountDataObject) => { + dispatch(updateSendErrors(getAmountErrorObject(amountDataObject))) + }, + updateSendAmount: newAmount => dispatch(updateSendAmount(newAmount)), + setMaxModeTo: bool => dispatch(setMaxModeTo(bool)), + } } \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js index e69de29bb..724f345af 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js @@ -0,0 +1,14 @@ +const selectors = { + getMaxModeOn, + sendAmountIsInError, +} + +module.exports = selectors + +function getMaxModeOn (state) { + return state.metamask.send.maxModeOn +} + +function sendAmountIsInError (state) { + return Boolean(state.metamask.send.errors.amount) +} diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js index e69de29bb..5b01b4594 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js @@ -0,0 +1,55 @@ +const { isValidAddress } = require('../../../../util') + +function getAmountErrorObject ({ + amount, + balance, + amountConversionRate, + conversionRate, + primaryCurrency, + selectedToken, + gasTotal, + tokenBalance, +}) { + let insufficientFunds = false + if (gasTotal && conversionRate) { + insufficientFunds = !isBalanceSufficient({ + amount: selectedToken ? '0x0' : amount, + gasTotal, + balance, + primaryCurrency, + amountConversionRate, + conversionRate, + }) + } + + let inSufficientTokens = false + if (selectedToken && tokenBalance !== null) { + const { decimals } = selectedToken + inSufficientTokens = !isTokenBalanceSufficient({ + tokenBalance, + amount, + decimals, + }) + } + + const amountLessThanZero = conversionGreaterThan( + { value: 0, fromNumericBase: 'dec' }, + { value: amount, fromNumericBase: 'hex' }, + ) + + let amountError = null + + if (insufficientFunds) { + amountError = this.context.t('insufficientFunds') + } else if (insufficientTokens) { + amountError = this.context.t('insufficientTokens') + } else if (amountLessThanZero) { + amountError = this.context.t('negativeETH') + } + + return { amount: amountError } +} + +module.exports = { + getAmountErrorObject +} diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js index e69de29bb..ad6b4a982 100644 --- a/ui/app/components/send_/send-content/send-content.component.js +++ b/ui/app/components/send_/send-content/send-content.component.js @@ -0,0 +1,23 @@ +import React, { Component } from 'react' +import PageContainerContent from '../../page-container/page-container-header.component' +import SendFromRow from './send-from-row/send-from-row.component' +import SendToRow from './send-to-row/send-to-row.component' +import SendAmountRow from './send-amount-row/send-amount-row.component' +import SendGasRow from './send-gas-row/send-gas-row.component' + +export default class SendContent extends Component { + + render () { + return ( + +
+ + + + +
+
+ ); + } + +} diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js index 7582cb2e6..b17f749a6 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import SendRowWrapper from '../../../send/from-dropdown' -import FromDropdown from '' +import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' +import FromDropdown from '../../../send/from-dropdown' export default class SendFromRow extends Component { diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js index 2ff3f0ccd..eeeb51629 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js @@ -1,6 +1,6 @@ import { getSendFrom, - conversionRateSelector, + getConversionRate, getSelectedTokenContract, getCurrentAccountWithSendEtherInfo, accountsWithSendEtherInfoSelector, @@ -23,7 +23,7 @@ function mapStateToProps (state) { return { from: getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state), fromAccounts: accountsWithSendEtherInfoSelector(state), - conversionRate: conversionRateSelector(state), + conversionRate: getConversionRate(state), fromDropdownOpen: getFromDropdownOpen(state), tokenContract: getSelectedTokenContract(state), } diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js index e69de29bb..8c1f14f48 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js @@ -0,0 +1,60 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' +import GasFeeDisplay from '../../../send/gas-fee-display-v2' + +export default class SendGasRow extends Component { + + static propTypes = { + closeFromDropdown: PropTypes.func, + conversionRate: PropTypes.string, + from: PropTypes.string, + fromAccounts: PropTypes.array, + fromDropdownOpen: PropTypes.bool, + openFromDropdown: PropTypes.func, + tokenContract: PropTypes.object, + updateSendFrom: PropTypes.func, + updateSendTokenBalance: PropTypes.func, + }; + + async handleFromChange (newFrom) { + const { + updateSendFrom, + tokenContract, + updateSendTokenBalance, + } = this.props + + if (tokenContract) { + const usersToken = await tokenContract.balanceOf(newFrom.address) + updateSendTokenBalance(usersToken) + } + updateSendFrom(newFrom) + } + + render () { + const { + conversionRate, + convertedCurrency, + showCustomizeGasModal, + gasTotal, + gasLoadingError, + } = this.props + + return ( + + showCustomizeGasModal()}, + gasLoadingError={gasLoadingError}, + /> + + ); + } + +} + +SendGasRow.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js index e69de29bb..7fb3a68be 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js @@ -0,0 +1,26 @@ +import { + getConversionRate, + getConvertedCurrency, + getGasTotal, +} from '../../send.selectors.js' +import { getGasLoadingError } from './send-gas-row.selectors.js' +import { calcTokenUpdateAmount } from './send-gas-row.utils.js' +import { showModal } from '../../../actions' +import SendGasRow from './send-from-row.component' + +export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow) + +function mapStateToProps (state) { + return { + conversionRate: getConversionRate(state), + convertedCurrency: getConvertedCurrency(state), + gasTotal: getGasTotal(state), + gasLoadingError: getGasLoadingError(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + showCustomizeGasModal: () => dispatch(showModal({ name: 'CUSTOMIZE_GAS' })), + } +} diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js index e69de29bb..d069ae8c6 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js @@ -0,0 +1,9 @@ +const selectors = { + sendGasIsInError, +} + +module.exports = selectors + +function sendGasIsInError (state) { + return state.send.errors.gasLoading +} diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js index a1ac591b9..92382da01 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js @@ -19,14 +19,23 @@ export default class SendRowWrapper extends Component { children, } = this.props + let formField = children[0] + let customLabelContent = null + + if (children.length === 2) { + formField = children[1] + customLabelContent = children[0] + } + return (
{label} (showError && ) + {customLabelContent}
- {children} + {formField}
); diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js index abcb54efc..5f81402d8 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import SendRowWrapper from '../../../send/from-dropdown' +import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' import ToDropdown from '../../../ens-input' export default class SendToRow extends Component { @@ -37,7 +37,11 @@ export default class SendToRow extends Component { } = this.props return ( - + Date: Thu, 26 Apr 2018 14:08:38 -0230 Subject: Core of the refactor complete --- .../page-container/page-container.component.js | 1 + .../account-list-item/account-list-item-README.md | 0 .../account-list-item.component.js | 74 ++++ .../account-list-item.container.js | 15 + .../send_/account-list-item/account-list-item.scss | 0 .../amount-max-button.component.js | 4 +- .../amount-max-button.container.js | 1 + .../send-amount-row/send-amount-row.component.js | 27 +- .../send-amount-row/send-amount-row.container.js | 37 +- .../send-amount-row/send-amount-row.selectors.js | 21 +- .../send-amount-row/send-amount-row.utils.js | 23 +- .../send_/send-content/send-content.component.js | 11 +- .../from-dropdown/from-dropdown.component.js | 75 ++++ .../send-from-row/send-from-row.component.js | 8 +- .../send-from-row/send-from-row.container.js | 19 +- .../send-from-row/send-from-row.utils.js | 2 +- .../send-gas-row/send-gas-row.component.js | 13 +- .../send-gas-row/send-gas-row.container.js | 10 +- .../send-gas-row/send-gas-row.selectors.js | 2 +- .../send-row-error-message.component.js | 5 +- .../send-row-error-message.container.js | 1 + .../send-row-wrapper/send-row-wrapper.component.js | 11 +- .../send-to-row/send-to-row.component.js | 17 +- .../send-to-row/send-to-row.container.js | 10 +- .../send_/send-footer/send-footer.component.js | 93 +++++ .../send_/send-footer/send-footer.container.js | 107 ++++++ .../send_/send-footer/send-footer.selectors.js | 12 + .../send_/send-footer/send-footer.utils.js | 84 +++++ ui/app/components/send_/send.constants.js | 33 ++ ui/app/components/send_/send.selectors.js | 46 ++- ui/app/components/send_/send.utils.js | 78 ++++ ui/app/ducks/send.js | 3 +- ui/app/reducers.js | 2 +- ui/app/send-v2.js | 401 +-------------------- 34 files changed, 757 insertions(+), 489 deletions(-) create mode 100644 ui/app/components/send_/account-list-item/account-list-item-README.md create mode 100644 ui/app/components/send_/account-list-item/account-list-item.component.js create mode 100644 ui/app/components/send_/account-list-item/account-list-item.container.js create mode 100644 ui/app/components/send_/account-list-item/account-list-item.scss create mode 100644 ui/app/components/send_/send.constants.js (limited to 'ui') diff --git a/ui/app/components/page-container/page-container.component.js b/ui/app/components/page-container/page-container.component.js index 7df1d48d8..1b5e7f819 100644 --- a/ui/app/components/page-container/page-container.component.js +++ b/ui/app/components/page-container/page-container.component.js @@ -8,6 +8,7 @@ export default class PageContainer extends Component { }; render () { + console.log(`QQQQQQQQQQQQQQQQQ this.props.children`, this.props.children); return (
{this.props.children} diff --git a/ui/app/components/send_/account-list-item/account-list-item-README.md b/ui/app/components/send_/account-list-item/account-list-item-README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/account-list-item/account-list-item.component.js b/ui/app/components/send_/account-list-item/account-list-item.component.js new file mode 100644 index 000000000..6b63aecf0 --- /dev/null +++ b/ui/app/components/send_/account-list-item/account-list-item.component.js @@ -0,0 +1,74 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { checksumAddress } from '../../../util' +import Identicon from '../../identicon' +import CurrencyDisplay from '../../send/currency-display' + +export default class AccountListItem extends Component { + + static propTypes = { + account: PropTypes.object, + className: PropTypes.string, + conversionRate: PropTypes.number, + currentCurrency: PropTypes.string, + displayAddress: PropTypes.bool, + displayBalance: PropTypes.bool, + handleClick: PropTypes.func, + icon: PropTypes.node, + }; + + render () { + const { + className, + account, + handleClick, + icon = null, + conversionRate, + currentCurrency, + displayBalance = true, + displayAddress = false, + } = this.props + + const { name, address, balance } = account || {} + + return (
handleClick({ name, address, balance })} + > + +
+ + +
{ name || address }
+ + {icon &&
{ icon }
} + +
+ + {displayAddress && name &&
+ { checksumAddress(address) } +
} + + {displayBalance && } + +
) + } +} + +AccountListItem.contextTypes = { + t: PropTypes.func, +} + diff --git a/ui/app/components/send_/account-list-item/account-list-item.container.js b/ui/app/components/send_/account-list-item/account-list-item.container.js new file mode 100644 index 000000000..e1bc1dd79 --- /dev/null +++ b/ui/app/components/send_/account-list-item/account-list-item.container.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux' +import { + getConversionRate, + getConvertedCurrency, +} from '../send.selectors.js' +import AccountListItem from './account-list-item.component' + +export default connect(mapStateToProps)(AccountListItem) + +function mapStateToProps (state) { + return { + conversionRate: getConversionRate(state), + currentCurrency: getConvertedCurrency(state), + } +} \ No newline at end of file diff --git a/ui/app/components/send_/account-list-item/account-list-item.scss b/ui/app/components/send_/account-list-item/account-list-item.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js index 59a1fd6db..6705a332d 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js @@ -31,7 +31,7 @@ export default class AmountMaxButton extends Component { } render () { - const { setMaxModeTo } = this.props + const { setMaxModeTo, maxModeOn } = this.props return (
- {!maxModeOn ? this.context.t('max') : '' ])} + {!maxModeOn ? this.context.t('max') : ''}
); } diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js index 572e1fc46..1b694902f 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js @@ -1,3 +1,4 @@ +import { connect } from 'react-redux' import { getSelectedToken, getGasTotal, diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index 78038f714..544664dc8 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -1,15 +1,14 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' -import AmountMaxButton from '../amount-max-button/amount-max-button.component' +import AmountMaxButton from './amount-max-button/amount-max-button.component' import CurrencyDisplay from '../../../send/currency-display' export default class SendAmountRow extends Component { static propTypes = { - amountConversionRate: PropTypes.string, - conversionRate: PropTypes.string, - from: PropTypes.object, + amountConversionRate: PropTypes.number, + conversionRate: PropTypes.number, gasTotal: PropTypes.string, primaryCurrency: PropTypes.string, selectedToken: PropTypes.object, @@ -23,7 +22,7 @@ export default class SendAmountRow extends Component { const { amountConversionRate, conversionRate, - from: { balance }, + balance, gasTotal, primaryCurrency, selectedToken, @@ -69,16 +68,16 @@ export default class SendAmountRow extends Component { showError={inError} errorType={'amount'} > - !inError && gasTotal && + {!inError && gasTotal && } + inError={inError} + primaryCurrency={primaryCurrency} + convertedCurrency={convertedCurrency} + selectedToken={selectedToken} + value={amount || '0x0'} + conversionRate={amountConversionRate} + handleChange={newAmount => this.handleAmountChange(newAmount)} + /> ); } diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js index 098855a02..55df68e69 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js @@ -1,41 +1,44 @@ +import { connect } from 'react-redux' import { getSelectedToken, - getPrimaryCurrency, - getAmountConversionRate, getConvertedCurrency, getSendAmount, getGasTotal, getSelectedBalance, getTokenBalance, getSendFromBalance, + getConversionRate, } from '../../send.selectors.js' import { getMaxModeOn, sendAmountIsInError, + getPrimaryCurrency, + getAmountConversionRate, } from './send-amount-row.selectors.js' import { getAmountErrorObject } from './send-amount-row.utils.js' import { updateSendAmount, setMaxModeTo, -} from '../../../actions' + updateSendErrors, +} from '../../../../actions' import SendAmountRow from './send-amount-row.component' -export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) +export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow) function mapStateToProps (state) { -updateSendTo -return { - selectedToken: getSelectedToken(state), - primaryCurrency: getPrimaryCurrency(state), - convertedCurrency: getConvertedCurrency(state), - amountConversionRate: getAmountConversionRate(state), - inError: sendAmountIsInError(state), - amount: getSendAmount(state), - maxModeOn: getMaxModeOn(state), - gasTotal: getGasTotal(state), - tokenBalance: getTokenBalance(state), - balance: getSendFromBalance(state), -} + return { + selectedToken: getSelectedToken(state), + primaryCurrency: getPrimaryCurrency(state), + convertedCurrency: getConvertedCurrency(state), + amountConversionRate: getAmountConversionRate(state), + inError: sendAmountIsInError(state), + amount: getSendAmount(state), + maxModeOn: getMaxModeOn(state), + gasTotal: getGasTotal(state), + tokenBalance: getTokenBalance(state), + balance: getSendFromBalance(state), + conversionRate: getConversionRate(state), + } } function mapDispatchToProps (dispatch) { diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js index 724f345af..c2620b4dc 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js @@ -1,6 +1,14 @@ +import { + getSelectedToken, + getSelectedTokenToFiatRate, + getConversionRate, +} from '../../send.selectors.js' + const selectors = { getMaxModeOn, sendAmountIsInError, + getPrimaryCurrency, + getAmountConversionRate, } module.exports = selectors @@ -10,5 +18,16 @@ function getMaxModeOn (state) { } function sendAmountIsInError (state) { - return Boolean(state.metamask.send.errors.amount) + return Boolean(state.metamask.send.errors.amount) +} + +function getPrimaryCurrency (state) { + const selectedToken = getSelectedToken(state) + return selectedToken && selectedToken.symbol +} + +function getAmountConversionRate (state) { + return Boolean(getSelectedToken(state)) + ? getSelectedTokenToFiatRate(state) + : getConversionRate(state) } diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js index 5b01b4594..418b98c18 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js @@ -1,4 +1,11 @@ const { isValidAddress } = require('../../../../util') +const { + conversionGreaterThan, +} = require('../../../../conversion-util') +const { + isBalanceSufficient, + isTokenBalanceSufficient, +} = require('../../send.utils') function getAmountErrorObject ({ amount, @@ -10,6 +17,14 @@ function getAmountErrorObject ({ gasTotal, tokenBalance, }) { + console.log(`#& getAmountErrorObject amount`, amount); + console.log(`#& getAmountErrorObject balance`, balance); + console.log(`#& getAmountErrorObject amountConversionRate`, amountConversionRate); + console.log(`#& getAmountErrorObject conversionRate`, conversionRate); + console.log(`#& getAmountErrorObject primaryCurrency`, primaryCurrency); + console.log(`#& getAmountErrorObject selectedToken`, selectedToken); + console.log(`#& getAmountErrorObject gasTotal`, gasTotal); + console.log(`#& getAmountErrorObject tokenBalance`, tokenBalance); let insufficientFunds = false if (gasTotal && conversionRate) { insufficientFunds = !isBalanceSufficient({ @@ -40,11 +55,11 @@ function getAmountErrorObject ({ let amountError = null if (insufficientFunds) { - amountError = this.context.t('insufficientFunds') - } else if (insufficientTokens) { - amountError = this.context.t('insufficientTokens') + amountError = 'insufficientFunds' + } else if (inSufficientTokens) { + amountError = 'insufficientTokens' } else if (amountLessThanZero) { - amountError = this.context.t('negativeETH') + amountError = 'negativeETH' } return { amount: amountError } diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js index ad6b4a982..2d1fa52e9 100644 --- a/ui/app/components/send_/send-content/send-content.component.js +++ b/ui/app/components/send_/send-content/send-content.component.js @@ -1,13 +1,14 @@ import React, { Component } from 'react' -import PageContainerContent from '../../page-container/page-container-header.component' -import SendFromRow from './send-from-row/send-from-row.component' -import SendToRow from './send-to-row/send-to-row.component' -import SendAmountRow from './send-amount-row/send-amount-row.component' -import SendGasRow from './send-gas-row/send-gas-row.component' +import PageContainerContent from '../../page-container/page-container-content.component' +import SendFromRow from './send-from-row/send-from-row.container' +import SendToRow from './send-to-row/send-to-row.container' +import SendAmountRow from './send-amount-row/send-amount-row.container' +import SendGasRow from './send-gas-row/send-gas-row.container' export default class SendContent extends Component { render () { + console.log('111222333444555666777888999') return (
diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js index e69de29bb..f215179ba 100644 --- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js +++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js @@ -0,0 +1,75 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import AccountListItem from '../../../account-list-item/account-list-item.container' + +export default class FromDropdown extends Component { + + static propTypes = { + accounts: PropTypes.array, + closeDropdown: PropTypes.func, + dropdownOpen: PropTypes.bool, + onSelect: PropTypes.func, + openDropdown: PropTypes.func, + selectedAccount: PropTypes.object, + }; + + renderListItemIcon (icon, color) { + return + } + + getListItemIcon (currentAccount, selectedAccount) { + return currentAccount.address === selectedAccount.address + ? this.renderListItemIcon('fa-check', '#02c9b1') + : null + } + + renderDropdown () { + const { + accounts, + selectedAccount, + closeDropdown, + onSelect, + } = this.props + + return (
+
closeDropdown} + /> +
+ {...accounts.map(account => { + onSelect(account) + closeDropdown() + }} + icon={this.getListItemIcon(account, selectedAccount.address)} + />)} +
+
) + } + + render () { + const { + selectedAccount, + openDropdown, + dropdownOpen, + } = this.props + console.log(`&*& openDropdown`, openDropdown); + console.log(`&*& dropdownOpen`, dropdownOpen); + return
+ + {dropdownOpen && this.renderDropdown()}, +
+ } + +} + +FromDropdown.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js index b17f749a6..0ae72c4ae 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js @@ -1,14 +1,14 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' -import FromDropdown from '../../../send/from-dropdown' +import FromDropdown from './from-dropdown/from-dropdown.component' export default class SendFromRow extends Component { static propTypes = { closeFromDropdown: PropTypes.func, - conversionRate: PropTypes.string, - from: PropTypes.string, + conversionRate: PropTypes.number, + from: PropTypes.object, fromAccounts: PropTypes.array, fromDropdownOpen: PropTypes.bool, openFromDropdown: PropTypes.func, @@ -41,7 +41,7 @@ export default class SendFromRow extends Component { openFromDropdown, closeFromDropdown, } = this.props - + console.log(`$% SendFromRow fromAccounts`, fromAccounts); return ( dispatch(updateSendFrom(newFrom)), - openFromDropdown: () => dispatch(()), - closeFromDropdown: () => dispatch(()), + openFromDropdown: () => dispatch(openFromDropdown()), + closeFromDropdown: () => dispatch(closeFromDropdown()), } } diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js index 2be25816f..4faae48dc 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js @@ -1,6 +1,6 @@ const { calcTokenAmount, -} = require('../../token-util') +} = require('../../../../token-util') function calcTokenUpdateAmount (usersToken, selectedToken) { const { decimals } = selectedToken || {} diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js index 8c1f14f48..056a04aae 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js @@ -7,7 +7,7 @@ export default class SendGasRow extends Component { static propTypes = { closeFromDropdown: PropTypes.func, - conversionRate: PropTypes.string, + conversionRate: PropTypes.number, from: PropTypes.string, fromAccounts: PropTypes.array, fromDropdownOpen: PropTypes.bool, @@ -15,6 +15,7 @@ export default class SendGasRow extends Component { tokenContract: PropTypes.object, updateSendFrom: PropTypes.func, updateSendTokenBalance: PropTypes.func, + gasLoadingError: PropTypes.bool, }; async handleFromChange (newFrom) { @@ -43,11 +44,11 @@ export default class SendGasRow extends Component { return ( showCustomizeGasModal()}, - gasLoadingError={gasLoadingError}, + gasTotal={gasTotal} + conversionRate={conversionRate} + convertedCurrency={convertedCurrency} + onClick={() => showCustomizeGasModal()} + gasLoadingError={gasLoadingError} /> ); diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js index 7fb3a68be..20d3daa59 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js @@ -1,12 +1,12 @@ +import { connect } from 'react-redux' import { getConversionRate, getConvertedCurrency, getGasTotal, } from '../../send.selectors.js' -import { getGasLoadingError } from './send-gas-row.selectors.js' -import { calcTokenUpdateAmount } from './send-gas-row.utils.js' -import { showModal } from '../../../actions' -import SendGasRow from './send-from-row.component' +import { sendGasIsInError } from './send-gas-row.selectors.js' +import { showModal } from '../../../../actions' +import SendGasRow from './send-gas-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow) @@ -15,7 +15,7 @@ function mapStateToProps (state) { conversionRate: getConversionRate(state), convertedCurrency: getConvertedCurrency(state), gasTotal: getGasTotal(state), - gasLoadingError: getGasLoadingError(state), + gasLoadingError: sendGasIsInError(state), } } diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js index d069ae8c6..ad4ef4877 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js @@ -5,5 +5,5 @@ const selectors = { module.exports = selectors function sendGasIsInError (state) { - return state.send.errors.gasLoading + return state.metamask.send.errors.gasLoading } diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js index 08f830cc5..e553ae67e 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js @@ -1,3 +1,6 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + export default class SendRowErrorMessage extends Component { static propTypes = { @@ -11,7 +14,7 @@ export default class SendRowErrorMessage extends Component { return ( errorMessage - ?
{errorMessage}
+ ?
{this.context.t(errorMessage)}
: null ); } diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js index 2278dbe63..002426904 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js @@ -1,3 +1,4 @@ +import { connect } from 'react-redux' import { getSendErrors } from '../../../send.selectors' import SendRowErrorMessage from './send-row-error-message.component' diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js index 92382da01..99134a466 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js @@ -19,19 +19,14 @@ export default class SendRowWrapper extends Component { children, } = this.props - let formField = children[0] - let customLabelContent = null - - if (children.length === 2) { - formField = children[1] - customLabelContent = children[0] - } + let formField = Array.isArray(children) ? children[1] || children[0] : children + let customLabelContent = children.length === 1 ? children[0] : null return (
{label} - (showError && ) + {showError && } {customLabelContent}
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js index 5f81402d8..a20bbf434 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' -import ToDropdown from '../../../ens-input' +import EnsInput from '../../../ens-input' export default class SendToRow extends Component { @@ -14,19 +14,20 @@ export default class SendToRow extends Component { updateSendToError: PropTypes.func, openToDropdown: PropTypes.func, closeToDropdown: PropTypes.func, - network: PropTypes.number, + network: PropTypes.string, }; handleToChange (to, nickname = '') { const { updateSendTo, updateSendToError } = this.props updateSendTo(to, nickname) - updateSendErrors(to) + updateSendToError(to) } render () { const { from, fromAccounts, + toAccounts, conversionRate, fromDropdownOpen, tokenContract, @@ -34,6 +35,8 @@ export default class SendToRow extends Component { closeToDropdown, network, inError, + to, + toDropdownOpen, } = this.props return ( @@ -44,14 +47,14 @@ export default class SendToRow extends Component { > openToDropdown()} closeDropdown={() => closeToDropdown()} - onChange={this.handleToChange} + onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)} inError={inError} /> diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js index 1c446c168..661ec1f0c 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js @@ -1,7 +1,9 @@ +import { connect } from 'react-redux' import { getSendTo, getToAccounts, getCurrentNetwork, + getSendToAccounts, } from '../../send.selectors.js' import { getToDropdownOpen, @@ -11,11 +13,11 @@ import { getToErrorObject } from './send-to-row.utils.js' import { updateSendErrors, updateSendTo, -} from '../../../actions' +} from '../../../../actions' import { openToDropdown, closeToDropdown, -} from '../../../ducks/send' +} from '../../../../ducks/send' import SendToRow from './send-to-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) @@ -37,7 +39,7 @@ function mapDispatchToProps (dispatch) { dispatch(updateSendErrors(getToErrorObject(to))) }, updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)), - openToDropdown: () => dispatch(()), - closeToDropdown: () => dispatch(()), + openToDropdown: () => dispatch(openToDropdown()), + closeToDropdown: () => dispatch(closeToDropdown()), } } \ No newline at end of file diff --git a/ui/app/components/send_/send-footer/send-footer.component.js b/ui/app/components/send_/send-footer/send-footer.component.js index e69de29bb..64dd027cf 100644 --- a/ui/app/components/send_/send-footer/send-footer.component.js +++ b/ui/app/components/send_/send-footer/send-footer.component.js @@ -0,0 +1,93 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import PageContainerFooter from '../../page-container/page-container-footer.component' +import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../routes' + +export default class SendFooter extends Component { + + static propTypes = { + addToAddressBook: PropTypes.func, + amount: PropTypes.string, + clearSend: PropTypes.func, + editingTransactionId: PropTypes.string, + errors: PropTypes.object, + from: PropTypes.object, + gasLimit: PropTypes.string, + gasPrice: PropTypes.string, + gasTotal: PropTypes.string, + history: PropTypes.object, + selectedToken: PropTypes.object, + signTokenTx: PropTypes.func, + signTx: PropTypes.func, + to: PropTypes.string, + toAccounts: PropTypes.array, + tokenBalance: PropTypes.string, + unapprovedTxs: PropTypes.object, + updateTx: PropTypes.func, + }; + + onSubmit (event) { + event.preventDefault() + const { + addToAddressBookIfNew, + amount, + editingTransactionId, + from: {address: from}, + gasLimit: gas, + gasPrice, + selectedToken, + sign, + to, + unapprovedTxs, + // updateTx, + update, + toAccounts, + } = this.props + + // Should not be needed because submit should be disabled if there are no errors. + // const noErrors = !amountError && toError === null + + // if (!noErrors) { + // return + // } + + // TODO: add nickname functionality + addToAddressBookIfNew(to, toAccounts) + + editingTransactionId + ? update({ + from, + to, + amount, + gas, + gasPrice, + selectedToken, + editingTransactionId, + unapprovedTxs, + }) + : sign({ selectedToken, to, amount, from, gas, gasPrice }) + + this.props.history.push(CONFIRM_TRANSACTION_ROUTE) + } + + + render () { + const { clearSend, disabled, history } = this.props + + return ( + { + clearSend() + history.push(DEFAULT_ROUTE) + }} + onSubmit={e => this.onSubmit(e)} + disabled={disabled} + /> + ); + } + +} + +SendFooter.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js index e69de29bb..fff6e284f 100644 --- a/ui/app/components/send_/send-footer/send-footer.container.js +++ b/ui/app/components/send_/send-footer/send-footer.container.js @@ -0,0 +1,107 @@ +import { connect } from 'react-redux' +import ethUtil from 'ethereumjs-util' +import { + addToAddressBook, + clearSend, + goHome, + signTokenTx, + signTx, + updateTransaction, +} from '../../../actions' +import SendFooter from './send-footer.component' +import { + getGasLimit, + getGasPrice, + getGasTotal, + getSelectedToken, + getSendAmount, + getSendEditingTransactionId, + getSendFromObject, + getSendTo, + getSendToAccounts, + getTokenBalance, + getUnapprovedTxs, +} from '../send.selectors' +import { + isSendFormInError, +} from './send-footer.selectors' +import { + addressIsNew, + formShouldBeDisabled, + constructTxParams, +} from './send-footer.utils' + +export default connect(mapStateToProps, mapDispatchToProps)(SendFooter) + +function mapStateToProps (state) { + return { + isToken: Boolean(getSelectedToken(state)), + inError: isSendFormInError(state), + disabled: formShouldBeDisabled({ + inError: isSendFormInError(state), + selectedToken: getSelectedToken(state), + tokenBalance: getTokenBalance(state), + gasTotal: getGasTotal(state), + }), + amount: getSendAmount(state), + editingTransactionId: getSendEditingTransactionId(state), + from: getSendFromObject(state), + gasLimit: getGasLimit(state), + gasPrice: getGasPrice(state), + selectedToken: getSelectedToken(state), + to: getSendTo(state), + unapprovedTxs: getUnapprovedTxs(state), + toAccounts: getSendToAccounts(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + goHome: () => dispatch(goHome()), + clearSend: () => dispatch(clearSend()), + sign: ({ selectedToken, to, amount, from, gas, gasPrice }) => { + const txParams = constructTxParams({ + amount, + from, + gas, + gasPrice, + selectedToken, + to, + }) + + selectedToken + ? dispatch(signTokenTx(selectedToken.address, to, amount, txParams)) + : dispatch(signTx(txParams)) + }, + update: ({ + amount, + editingTransactionId, + from, + gas, + gasPrice, + selectedToken, + to, + unapprovedTxs, + }) => { + const editingTx = constructUpdatedTx({ + amount, + editingTransactionId, + from, + gas, + gasPrice, + selectedToken, + to, + unapprovedTxs, + }) + + dispatch(updateTransaction(editingTx)) + }, + addToAddressBookIfNew: (newAddress, toAccounts, nickname = '') => { + const hexPrefixedAddress = ethUtil.addHexPrefix(newAddress) + if (addressIsNew(toAccounts)) { + // TODO: nickname, i.e. addToAddressBook(recipient, nickname) + dispatch(addToAddressBook(hexPrefixedAddress, nickname)) + } + } + } +} diff --git a/ui/app/components/send_/send-footer/send-footer.selectors.js b/ui/app/components/send_/send-footer/send-footer.selectors.js index e69de29bb..ccd4706ea 100644 --- a/ui/app/components/send_/send-footer/send-footer.selectors.js +++ b/ui/app/components/send_/send-footer/send-footer.selectors.js @@ -0,0 +1,12 @@ +import { getSendErrors } from '../send.selectors' + +const selectors = { + isSendFormInError, +} + +module.exports = selectors + +function isSendFormInError (state) { + const { amount, to } = getSendErrors(state) + return Boolean(amount || to !== null) +} \ No newline at end of file diff --git a/ui/app/components/send_/send-footer/send-footer.utils.js b/ui/app/components/send_/send-footer/send-footer.utils.js index e69de29bb..23d5655c7 100644 --- a/ui/app/components/send_/send-footer/send-footer.utils.js +++ b/ui/app/components/send_/send-footer/send-footer.utils.js @@ -0,0 +1,84 @@ +import ethAbi from 'ethereumjs-abi' +import ethUtil from 'ethereumjs-util' +import { TOKEN_TRANSFER_FUNCTION_SIGNATURE } from '../send.constants' + +function formShouldBeDisabled ({ inError, selectedToken, tokenBalance, gasTotal }) { + const missingTokenBalance = selectedToken && !tokenBalance + return inError || !gasTotal || missingTokenBalance +} + +function addHexPrefixToObjectValues (obj) { + return Object.keys(obj).reduce((newObj, key) => { + return { ...newObj, [key]: ethUtil.addHexPrefix(obj[key]) } + }, {}) +} + +function constructTxParams ({ selectedToken, to, amount, from, gas, gasPrice }) { + const txParams = { + from, + value: '0', + gas, + gasPrice, + } + + if (!selectedToken) { + txParams.value = amount + txParams.to = to + } + + const hexPrefixedTxParams = addHexPrefixToObjectValues(txParams) + + return hexPrefixedTxParams +} + +function constructUpdatedTx ({ + amount, + editingTransactionId, + from, + gas, + gasPrice, + selectedToken, + to, + unapprovedTxs, +}) { + const editingTx = { + ...unapprovedTxs[editingTransactionId], + txParams: addHexPrefixToObjectValues({ from, gas, gasPrice }), + } + + if (selectedToken) { + const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call( + ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]), + x => ('00' + x.toString(16)).slice(-2) + ).join('') + + Object.assign(editingTx.txParams, addHexPrefixToObjectValues({ + value: '0', + to: selectedToken.address, + data, + })) + } else { + const { data } = unapprovedTxs[editingTransactionId].txParams + + Object.assign(editingTx.txParams, addHexPrefixToObjectValues({ + value: amount, + to, + data, + })) + + if (typeof editingTx.txParams.data === 'undefined') { + delete editingTx.txParams.data + } + } +} + +function addressIsNew (toAccounts, newAddress) { + return !toAccounts.find(({ address }) => newAddress === address) +} + +module.exports = { + addressIsNew, + formShouldBeDisabled, + constructTxParams, + constructUpdatedTx, +} diff --git a/ui/app/components/send_/send.constants.js b/ui/app/components/send_/send.constants.js new file mode 100644 index 000000000..b3ee0899a --- /dev/null +++ b/ui/app/components/send_/send.constants.js @@ -0,0 +1,33 @@ +const ethUtil = require('ethereumjs-util') +const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') + +const MIN_GAS_PRICE_HEX = (100000000).toString(16) +const MIN_GAS_PRICE_DEC = '100000000' +const MIN_GAS_LIMIT_DEC = '21000' +const MIN_GAS_LIMIT_HEX = (parseInt(MIN_GAS_LIMIT_DEC)).toString(16) + +const MIN_GAS_PRICE_GWEI = ethUtil.addHexPrefix(conversionUtil(MIN_GAS_PRICE_HEX, { + fromDenomination: 'WEI', + toDenomination: 'GWEI', + fromNumericBase: 'hex', + toNumericBase: 'hex', + numberOfDecimals: 1, +})) + +const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 16, +}) + +const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb' + +module.exports = { + MIN_GAS_PRICE_GWEI, + MIN_GAS_PRICE_HEX, + MIN_GAS_PRICE_DEC, + MIN_GAS_LIMIT_HEX, + MIN_GAS_LIMIT_DEC, + MIN_GAS_TOTAL, + TOKEN_TRANSFER_FUNCTION_SIGNATURE, +} diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js index 9ef13193c..4abebfa56 100644 --- a/ui/app/components/send_/send.selectors.js +++ b/ui/app/components/send_/send.selectors.js @@ -2,14 +2,14 @@ import { valuesFor } from '../../util' import abi from 'human-standard-token-abi' import { multiplyCurrencies, -} from './conversion-util' +} from '../../conversion-util' const selectors = { accountsWithSendEtherInfoSelector, autoAddToBetaUI, - getConversionRate, getAddressBook, getConversionRate, + getConvertedCurrency, getCurrentAccountWithSendEtherInfo, getCurrentCurrency, getCurrentNetwork, @@ -17,6 +17,7 @@ const selectors = { getForceGasMin, getGasLimit, getGasPrice, + getGasTotal, getSelectedAccount, getSelectedAddress, getSelectedIdentity, @@ -25,12 +26,18 @@ const selectors = { getSelectedTokenExchangeRate, getSelectedTokenToFiatRate, getSendAmount, + getSendEditingTransactionId, getSendErrors, getSendFrom, + getSendFromObject, getSendFromBalance, getSendMaxModeState, getSendTo, + getSendToAccounts, + getTokenBalance, getTokenExchangeRate, + getUnapprovedTxs, + isSendFormInError, transactionsSelector, } @@ -84,10 +91,18 @@ function getTokenExchangeRate (state, tokenSymbol) { return tokenExchangeRate } +function getUnapprovedTxs (state) { + return state.metamask.unapprovedTxs +} + function getConversionRate (state) { return state.metamask.conversionRate } +function getConvertedCurrency (state) { + return state.metamask.currentCurrency +} + function getAddressBook (state) { return state.metamask.addressBook } @@ -97,11 +112,13 @@ function accountsWithSendEtherInfoSelector (state) { accounts, identities, } = state.metamask - + console.log(`accountsWithSendEtherInfoSelector accounts`, accounts); + console.log(`accountsWithSendEtherInfoSelector identities`, identities); const accountsWithSendEtherInfo = Object.entries(accounts).map(([key, account]) => { return Object.assign({}, account, identities[key]) }) + console.log(`accountsWithSendEtherInfoSelector accountsWithSendEtherInfo`, accountsWithSendEtherInfo); return accountsWithSendEtherInfo } @@ -132,6 +149,10 @@ function getGasPrice (state) { return state.metamask.send.gasPrice } +function getGasTotal (state) { + return state.metamask.send.gasTotal +} + function getGasLimit (state) { return state.metamask.send.gasLimit } @@ -144,8 +165,12 @@ function getSendFrom (state) { return state.metamask.send.from } +function getSendFromObject (state) { + return getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state) +} + function getSendFromBalance (state) { - const from = state.metamask.send.from || {} + const from = getSendFrom(state) || getSelectedAccount(state) return from.balance } @@ -203,6 +228,10 @@ function getCurrentViewContext (state) { return currentView.context } +function getSendEditingTransactionId (state) { + return state.metamask.send.editingTransactionId +} + function getSendErrors (state) { return state.metamask.send.errors } @@ -211,6 +240,10 @@ function getSendTo (state) { return state.metamask.send.to } +function getTokenBalance (state) { + return state.metamask.send.tokenBalance +} + function getSendToAccounts (state) { const fromAccounts = accountsWithSendEtherInfoSelector(state) const addressBookAccounts = getAddressBook(state) @@ -221,4 +254,9 @@ function getSendToAccounts (state) { function getCurrentNetwork (state) { return state.metamask.network +} + +function isSendFormInError (state) { + const { amount, to } = getSendErrors(state) + return Boolean(amount || toError !== null) } \ No newline at end of file diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index e69de29bb..f56f91e48 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -0,0 +1,78 @@ +const { + addCurrencies, + conversionUtil, + conversionGTE, + multiplyCurrencies, +} = require('../../conversion-util') +const { + calcTokenAmount, +} = require('../../token-util') + +function isBalanceSufficient ({ + amount = '0x0', + gasTotal = '0x0', + balance, + primaryCurrency, + amountConversionRate, + conversionRate, +}) { + const totalAmount = addCurrencies(amount, gasTotal, { + aBase: 16, + bBase: 16, + toNumericBase: 'hex', + }) + + const balanceIsSufficient = conversionGTE( + { + value: balance, + fromNumericBase: 'hex', + fromCurrency: primaryCurrency, + conversionRate, + }, + { + value: totalAmount, + fromNumericBase: 'hex', + conversionRate: amountConversionRate || conversionRate, + fromCurrency: primaryCurrency, + }, + ) + + return balanceIsSufficient +} + +function isTokenBalanceSufficient ({ + amount = '0x0', + tokenBalance, + decimals, +}) { + const amountInDec = conversionUtil(amount, { + fromNumericBase: 'hex', + }) + + const tokenBalanceIsSufficient = conversionGTE( + { + value: tokenBalance, + fromNumericBase: 'dec', + }, + { + value: calcTokenAmount(amountInDec, decimals), + fromNumericBase: 'dec', + }, + ) + + return tokenBalanceIsSufficient +} + +function getGasTotal (gasLimit, gasPrice) { + return multiplyCurrencies(gasLimit, gasPrice, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 16, + }) +} + +module.exports = { + getGasTotal, + isBalanceSufficient, + isTokenBalanceSufficient, +} \ No newline at end of file diff --git a/ui/app/ducks/send.js b/ui/app/ducks/send.js index aeca9f92f..c4874aa8c 100644 --- a/ui/app/ducks/send.js +++ b/ui/app/ducks/send.js @@ -10,10 +10,11 @@ const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'; const initState = { fromDropdownOpen: false, toDropdownOpen: false, + errors: {}, } // Reducer -export default function reducer(state = initState, action = {}) { +export default function reducer({ send: sendState = initState }, action = {}) { switch (action.type) { case OPEN_FROM_DROPDOWN: return extend(sendState, { diff --git a/ui/app/reducers.js b/ui/app/reducers.js index ff766e856..26bf66c3c 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -8,7 +8,7 @@ const reduceIdentities = require('./reducers/identities') const reduceMetamask = require('./reducers/metamask') const reduceApp = require('./reducers/app') const reduceLocale = require('./reducers/locale') -const reduceSend = require('./ducks/send') +const reduceSend = require('./ducks/send').default window.METAMASK_CACHED_LOG_STATE = null diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index c5085d9ec..12e8b4e60 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -34,8 +34,8 @@ const { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } = require('./routes') import PageContainer from './components/page-container/page-container.component' import SendHeader from './components/send_/send-header/send-header.container' -import PageContainerContent from './components/page-container/page-container-content.component' -import PageContainerFooter from './components/page-container/page-container-footer.component' +import SendContent from './components/send_/send-content/send-content.component' +import SendFooter from './components/send_/send-footer/send-footer.container' SendTransactionScreen.contextTypes = { t: PropTypes.func, @@ -57,8 +57,6 @@ function SendTransactionScreen () { gasLoadingError: false, } - this.handleToChange = this.handleToChange.bind(this) - this.handleAmountChange = this.handleAmountChange.bind(this) this.validateAmount = this.validateAmount.bind(this) } @@ -176,158 +174,6 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { } } -SendTransactionScreen.prototype.renderHeader = function () { - const { selectedToken, clearSend, history } = this.props - - return h('div.page-container__header', [ - - h('div.page-container__title', selectedToken ? this.context.t('sendTokens') : this.context.t('sendETH')), - - h('div.page-container__subtitle', this.context.t('onlySendToEtherAddress')), - - h('div.page-container__header-close', { - onClick: () => { - clearSend() - history.push(DEFAULT_ROUTE) - }, - }), - - ]) -} - -SendTransactionScreen.prototype.renderErrorMessage = function (errorType) { - const { errors } = this.props - const errorMessage = errors[errorType] - - return errorMessage - ? h('div.send-v2__error', [ errorMessage ]) - : null -} - -SendTransactionScreen.prototype.handleFromChange = async function (newFrom) { - const { - updateSendFrom, - tokenContract, - } = this.props - - if (tokenContract) { - const usersToken = await tokenContract.balanceOf(newFrom.address) - this.updateSendTokenBalance(usersToken) - } - updateSendFrom(newFrom) -} - -SendTransactionScreen.prototype.renderFromRow = function () { - const { - from, - fromAccounts, - conversionRate, - } = this.props - - const { fromDropdownOpen } = this.state - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', 'From:'), - - h('div.send-v2__form-field', [ - h(FromDropdown, { - dropdownOpen: fromDropdownOpen, - accounts: fromAccounts, - selectedAccount: from, - onSelect: newFrom => this.handleFromChange(newFrom), - openDropdown: () => this.setState({ fromDropdownOpen: true }), - closeDropdown: () => this.setState({ fromDropdownOpen: false }), - conversionRate, - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.handleToChange = function (to, nickname = '') { - const { - updateSendTo, - updateSendErrors, - } = this.props - let toError = null - - if (!to) { - toError = this.context.t('required') - } else if (!isValidAddress(to)) { - toError = this.context.t('invalidAddressRecipient') - } - - updateSendTo(to, nickname) - updateSendErrors({ to: toError }) -} - -SendTransactionScreen.prototype.renderToRow = function () { - const { toAccounts, errors, to, network } = this.props - - const { toDropdownOpen } = this.state - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', [ - - this.context.t('to'), - - this.renderErrorMessage(this.context.t('to')), - - ]), - - h('div.send-v2__form-field', [ - h(EnsInput, { - name: 'address', - placeholder: 'Recipient Address', - network, - to, - accounts: Object.entries(toAccounts).map(([key, account]) => account), - dropdownOpen: toDropdownOpen, - openDropdown: () => this.setState({ toDropdownOpen: true }), - closeDropdown: () => this.setState({ toDropdownOpen: false }), - onChange: this.handleToChange, - inError: Boolean(errors.to), - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.handleAmountChange = function (value) { - const amount = value - const { updateSendAmount, setMaxModeTo } = this.props - - setMaxModeTo(false) - this.validateAmount(amount) - updateSendAmount(amount) -} - -SendTransactionScreen.prototype.setAmountToMax = function () { - const { - from: { balance }, - updateSendAmount, - updateSendErrors, - tokenBalance, - selectedToken, - gasTotal, - } = this.props - const { decimals } = selectedToken || {} - const multiplier = Math.pow(10, Number(decimals || 0)) - - const maxAmount = selectedToken - ? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'}) - : subtractCurrencies( - ethUtil.addHexPrefix(balance), - ethUtil.addHexPrefix(gasTotal), - { toNumericBase: 'hex' } - ) - - updateSendErrors({ amount: null }) - - updateSendAmount(maxAmount) -} SendTransactionScreen.prototype.validateAmount = function (value) { const { @@ -384,254 +230,19 @@ SendTransactionScreen.prototype.validateAmount = function (value) { updateSendErrors({ amount: amountError }) } -SendTransactionScreen.prototype.renderAmountRow = function () { - const { - selectedToken, - primaryCurrency = 'ETH', - convertedCurrency, - amountConversionRate, - errors, - amount, - setMaxModeTo, - maxModeOn, - gasTotal, - } = this.props - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', [ - 'Amount:', - this.renderErrorMessage('amount'), - !errors.amount && gasTotal && h('div.send-v2__amount-max', { - onClick: (event) => { - event.preventDefault() - setMaxModeTo(true) - this.setAmountToMax() - }, - }, [ !maxModeOn ? this.context.t('max') : '' ]), - ]), - - h('div.send-v2__form-field', [ - h(CurrencyDisplay, { - inError: Boolean(errors.amount), - primaryCurrency, - convertedCurrency, - selectedToken, - value: amount || '0x0', - conversionRate: amountConversionRate, - handleChange: this.handleAmountChange, - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.renderGasRow = function () { - const { - conversionRate, - convertedCurrency, - showCustomizeGasModal, - gasTotal, - } = this.props - const { gasLoadingError } = this.state - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', this.context.t('gasFee')), - - h('div.send-v2__form-field', [ - - h(GasFeeDisplay, { - gasTotal, - conversionRate, - convertedCurrency, - onClick: showCustomizeGasModal, - gasLoadingError, - }), - - ]), - - ]) -} - -SendTransactionScreen.prototype.renderMemoRow = function () { - const { updateSendMemo, memo } = this.props - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', 'Transaction Memo:'), - - h('div.send-v2__form-field', [ - h(MemoTextArea, { - memo, - onChange: (event) => updateSendMemo(event.target.value), - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.renderForm = function () { - return h(PageContainerContent, [ - h('.send-v2__form', [ - this.renderFromRow(), - - this.renderToRow(), - - this.renderAmountRow(), - - this.renderGasRow(), - ]), - ]) -} - -SendTransactionScreen.prototype.renderFooter = function () { - const { - clearSend, - gasTotal, - tokenBalance, - selectedToken, - errors: { amount: amountError, to: toError }, - history, - } = this.props - - const missingTokenBalance = selectedToken && !tokenBalance - const noErrors = !amountError && toError === null - - return h(PageContainerFooter, { - onCancel: () => { - clearSend() - history.push(DEFAULT_ROUTE) - }, - onSubmit: e => this.onSubmit(e), - disabled: !noErrors || !gasTotal || missingTokenBalance, - }) -} - SendTransactionScreen.prototype.render = function () { + const { history } = this.props + return ( h(PageContainer, [ h(SendHeader), - this.renderForm(), + h(SendContent), - this.renderFooter(), + h(SendFooter, { history }), ]) ) } - -SendTransactionScreen.prototype.addToAddressBookIfNew = function (newAddress, nickname = '') { - const { toAccounts, addToAddressBook } = this.props - if (!toAccounts.find(({ address }) => newAddress === address)) { - // TODO: nickname, i.e. addToAddressBook(recipient, nickname) - addToAddressBook(newAddress, nickname) - } -} - -SendTransactionScreen.prototype.getEditedTx = function () { - const { - from: {address: from}, - to, - amount, - gasLimit: gas, - gasPrice, - selectedToken, - editingTransactionId, - unapprovedTxs, - } = this.props - - const editingTx = { - ...unapprovedTxs[editingTransactionId], - txParams: { - from: ethUtil.addHexPrefix(from), - gas: ethUtil.addHexPrefix(gas), - gasPrice: ethUtil.addHexPrefix(gasPrice), - }, - } - - if (selectedToken) { - const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call( - ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]), - x => ('00' + x.toString(16)).slice(-2) - ).join('') - - Object.assign(editingTx.txParams, { - value: ethUtil.addHexPrefix('0'), - to: ethUtil.addHexPrefix(selectedToken.address), - data, - }) - } else { - const { data } = unapprovedTxs[editingTransactionId].txParams - - Object.assign(editingTx.txParams, { - value: ethUtil.addHexPrefix(amount), - to: ethUtil.addHexPrefix(to), - data, - }) - - if (typeof editingTx.txParams.data === 'undefined') { - delete editingTx.txParams.data - } - } - - return editingTx -} - -SendTransactionScreen.prototype.onSubmit = function (event) { - event.preventDefault() - const { - from: {address: from}, - to: _to, - amount, - gasLimit: gas, - gasPrice, - signTokenTx, - signTx, - updateTx, - selectedToken, - editingTransactionId, - toNickname, - errors: { amount: amountError, to: toError }, - } = this.props - - const noErrors = !amountError && toError === null - - if (!noErrors) { - return - } - - const to = ethUtil.addHexPrefix(_to) - - this.addToAddressBookIfNew(to, toNickname) - - if (editingTransactionId) { - const editedTx = this.getEditedTx() - updateTx(editedTx) - } else { - - const txParams = { - from, - value: '0', - gas, - gasPrice, - } - - if (!selectedToken) { - txParams.value = amount - txParams.to = to - } - - Object.keys(txParams).forEach(key => { - txParams[key] = ethUtil.addHexPrefix(txParams[key]) - }) - - selectedToken - ? signTokenTx(selectedToken.address, to, amount, txParams) - : signTx(txParams) - } - - this.props.history.push(CONFIRM_TRANSACTION_ROUTE) -} -- cgit From 91c201aa72581a59a0d2ef73a225b1768584dea7 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 26 Apr 2018 22:08:14 -0230 Subject: Lint fixes and alphabetization for i3725-refactor-send-component --- .../page-container-content.component.js | 2 +- .../page-container-footer.component.js | 2 +- .../page-container-header.component.js | 2 +- .../page-container/page-container.component.js | 3 +- .../account-list-item.component.js | 30 +-- .../amount-max-button.component.js | 20 +- .../amount-max-button.container.js | 17 +- .../amount-max-button/amount-max-button.utils.js | 2 +- .../send-amount-row/send-amount-row.component.js | 21 +- .../send-amount-row/send-amount-row.container.js | 37 ++- .../send-amount-row/send-amount-row.selectors.js | 6 +- .../send-amount-row/send-amount-row.utils.js | 21 +- .../send_/send-content/send-content.component.js | 9 +- .../from-dropdown/from-dropdown.component.js | 20 +- .../send-from-row/send-from-row.component.js | 17 +- .../send-from-row/send-from-row.container.js | 15 +- .../send-from-row/send-from-row.utils.js | 2 +- .../send-gas-row/send-gas-row.component.js | 17 +- .../send-row-error-message.component.js | 6 +- .../send-row-error-message.container.js | 2 +- .../send-row-wrapper/send-row-wrapper.component.js | 18 +- .../send-to-row/send-to-row.component.js | 37 ++- .../send-to-row/send-to-row.container.js | 16 +- .../send-content/send-to-row/send-to-row.utils.js | 4 +- .../send_/send-footer/send-footer.component.js | 18 +- .../send_/send-footer/send-footer.container.js | 15 +- .../send_/send-footer/send-footer.selectors.js | 2 +- .../send_/send-footer/send-footer.utils.js | 2 +- .../send_/send-header/send-header.component.js | 8 +- .../send_/send-header/send-header.container.js | 4 +- ui/app/components/send_/send.selectors.js | 249 ++++++++++----------- ui/app/components/send_/send.utils.js | 24 +- ui/app/ducks/send.js | 28 +-- ui/app/selectors.js | 2 +- ui/app/send-v2.js | 17 -- 35 files changed, 330 insertions(+), 365 deletions(-) (limited to 'ui') diff --git a/ui/app/components/page-container/page-container-content.component.js b/ui/app/components/page-container/page-container-content.component.js index ffd62894c..a1d6988cc 100644 --- a/ui/app/components/page-container/page-container-content.component.js +++ b/ui/app/components/page-container/page-container-content.component.js @@ -12,7 +12,7 @@ export default class PageContainerContent extends Component {
{this.props.children}
- ); + ) } } diff --git a/ui/app/components/page-container/page-container-footer.component.js b/ui/app/components/page-container/page-container-footer.component.js index 0ef14c9d7..475ce6b1c 100644 --- a/ui/app/components/page-container/page-container-footer.component.js +++ b/ui/app/components/page-container/page-container-footer.component.js @@ -31,7 +31,7 @@ export default class PageContainerFooter extends Component {
- ); + ) } } diff --git a/ui/app/components/page-container/page-container-header.component.js b/ui/app/components/page-container/page-container-header.component.js index 9adc88fb3..5c9d63221 100644 --- a/ui/app/components/page-container/page-container-header.component.js +++ b/ui/app/components/page-container/page-container-header.component.js @@ -29,7 +29,7 @@ export default class PageContainerHeader extends Component { />
- ); + ) } } diff --git a/ui/app/components/page-container/page-container.component.js b/ui/app/components/page-container/page-container.component.js index 1b5e7f819..dc3745d4a 100644 --- a/ui/app/components/page-container/page-container.component.js +++ b/ui/app/components/page-container/page-container.component.js @@ -8,12 +8,11 @@ export default class PageContainer extends Component { }; render () { - console.log(`QQQQQQQQQQQQQQQQQ this.props.children`, this.props.children); return (
{this.props.children}
- ); + ) } } diff --git a/ui/app/components/send_/account-list-item/account-list-item.component.js b/ui/app/components/send_/account-list-item/account-list-item.component.js index 6b63aecf0..b8407d147 100644 --- a/ui/app/components/send_/account-list-item/account-list-item.component.js +++ b/ui/app/components/send_/account-list-item/account-list-item.component.js @@ -19,14 +19,14 @@ export default class AccountListItem extends Component { render () { const { - className, account, - handleClick, - icon = null, + className, conversionRate, currentCurrency, - displayBalance = true, displayAddress = false, + displayBalance = true, + handleClick, + icon = null, } = this.props const { name, address, balance } = account || {} @@ -36,32 +36,32 @@ export default class AccountListItem extends Component { onClick={() => handleClick({ name, address, balance })} > -
+
-
{ name || address }
+
{ name || address }
- {icon &&
{ icon }
} + {icon &&
{ icon }
}
- {displayAddress && name &&
+ {displayAddress && name &&
{ checksumAddress(address) }
} {displayBalance && }
) diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js index 6705a332d..c3b5b6ae4 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js @@ -4,29 +4,29 @@ import PropTypes from 'prop-types' export default class AmountMaxButton extends Component { static propTypes = { - tokenBalance: PropTypes.string, - gasTotal: PropTypes.string, balance: PropTypes.string, + gasTotal: PropTypes.string, + maxModeOn: PropTypes.bool, selectedToken: PropTypes.object, setAmountToMax: PropTypes.func, setMaxModeTo: PropTypes.func, - maxModeOn: PropTypes.bool, + tokenBalance: PropTypes.string, }; setAmountToMax = function () { const { balance, - tokenBalance, - selectedToken, gasTotal, + selectedToken, setAmountToMax, + tokenBalance, } = this.props setAmountToMax({ - tokenBalance, - selectedToken, + balance, gasTotal, - setAmountToMax, + selectedToken, + tokenBalance, }) } @@ -35,7 +35,7 @@ export default class AmountMaxButton extends Component { return (
{ event.preventDefault() setMaxModeTo(true) @@ -44,7 +44,7 @@ export default class AmountMaxButton extends Component { > {!maxModeOn ? this.context.t('max') : ''}
- ); + ) } } diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js index 1b694902f..5bdb67995 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js @@ -1,28 +1,29 @@ import { connect } from 'react-redux' import { - getSelectedToken, getGasTotal, - getTokenBalance, + getSelectedToken, getSendFromBalance, + getTokenBalance, } from '../../../send.selectors.js' import { getMaxModeOn } from '../send-amount-row.selectors.js' import { calcMaxAmount } from './amount-max-button.utils.js' import { updateSendAmount, + updateSendErrors, setMaxModeTo, } from '../../../actions' import AmountMaxButton from './amount-max-button.component' -export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) +export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton) function mapStateToProps (state) { return { - selectedToken: getSelectedToken(state), - maxModeOn: getMaxModeOn(state), + balance: getSendFromBalance(state), gasTotal: getGasTotal(state), + maxModeOn: getMaxModeOn(state), + selectedToken: getSelectedToken(state), tokenBalance: getTokenBalance(state), - balance: getSendFromBalance(state), } } @@ -31,7 +32,7 @@ function mapDispatchToProps (dispatch) { setAmountToMax: maxAmountDataObject => { updateSendErrors({ amount: null }) updateSendAmount(calcMaxAmount(maxAmountDataObject)) - } + }, setMaxModeTo: bool => dispatch(setMaxModeTo(bool)), } -} \ No newline at end of file +} diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js index 54aacc8d7..d87f2424b 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js @@ -18,5 +18,5 @@ function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) { } module.exports = { - calcMaxAmount + calcMaxAmount, } diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index 544664dc8..5c3084365 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -7,22 +7,26 @@ import CurrencyDisplay from '../../../send/currency-display' export default class SendAmountRow extends Component { static propTypes = { + amount: PropTypes.string, amountConversionRate: PropTypes.number, + balance: PropTypes.string, conversionRate: PropTypes.number, + convertedCurrency: PropTypes.string, gasTotal: PropTypes.string, + inError: PropTypes.bool, primaryCurrency: PropTypes.string, selectedToken: PropTypes.object, + setMaxModeTo: PropTypes.func, tokenBalance: PropTypes.string, - updateSendAmountError: PropTypes.func, updateSendAmount: PropTypes.func, - setMaxModeTo: PropTypes.func + updateSendAmountError: PropTypes.func, } validateAmount (amount) { const { amountConversionRate, - conversionRate, balance, + conversionRate, gasTotal, primaryCurrency, selectedToken, @@ -55,9 +59,8 @@ export default class SendAmountRow extends Component { amount, amountConversionRate, convertedCurrency, - inError, gasTotal, - maxModeOn, + inError, primaryCurrency = 'ETH', selectedToken, } = this.props @@ -70,16 +73,16 @@ export default class SendAmountRow extends Component { > {!inError && gasTotal && } this.handleAmountChange(newAmount)} inError={inError} primaryCurrency={primaryCurrency} - convertedCurrency={convertedCurrency} selectedToken={selectedToken} value={amount || '0x0'} - conversionRate={amountConversionRate} - handleChange={newAmount => this.handleAmountChange(newAmount)} /> - ); + ) } } diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js index 55df68e69..16e88bede 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js @@ -1,24 +1,22 @@ import { connect } from 'react-redux' import { - getSelectedToken, + getConversionRate, getConvertedCurrency, - getSendAmount, getGasTotal, - getSelectedBalance, - getTokenBalance, + getSelectedToken, + getSendAmount, getSendFromBalance, - getConversionRate, + getTokenBalance, } from '../../send.selectors.js' import { - getMaxModeOn, - sendAmountIsInError, - getPrimaryCurrency, getAmountConversionRate, + getPrimaryCurrency, + sendAmountIsInError, } from './send-amount-row.selectors.js' import { getAmountErrorObject } from './send-amount-row.utils.js' import { - updateSendAmount, setMaxModeTo, + updateSendAmount, updateSendErrors, } from '../../../../actions' import SendAmountRow from './send-amount-row.component' @@ -27,26 +25,25 @@ export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow) function mapStateToProps (state) { return { - selectedToken: getSelectedToken(state), - primaryCurrency: getPrimaryCurrency(state), - convertedCurrency: getConvertedCurrency(state), - amountConversionRate: getAmountConversionRate(state), - inError: sendAmountIsInError(state), amount: getSendAmount(state), - maxModeOn: getMaxModeOn(state), - gasTotal: getGasTotal(state), - tokenBalance: getTokenBalance(state), + amountConversionRate: getAmountConversionRate(state), balance: getSendFromBalance(state), conversionRate: getConversionRate(state), + convertedCurrency: getConvertedCurrency(state), + gasTotal: getGasTotal(state), + inError: sendAmountIsInError(state), + primaryCurrency: getPrimaryCurrency(state), + selectedToken: getSelectedToken(state), + tokenBalance: getTokenBalance(state), } } function mapDispatchToProps (dispatch) { return { + setMaxModeTo: bool => dispatch(setMaxModeTo(bool)), + updateSendAmount: newAmount => dispatch(updateSendAmount(newAmount)), updateSendAmountError: (amountDataObject) => { dispatch(updateSendErrors(getAmountErrorObject(amountDataObject))) }, - updateSendAmount: newAmount => dispatch(updateSendAmount(newAmount)), - setMaxModeTo: bool => dispatch(setMaxModeTo(bool)), } -} \ No newline at end of file +} diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js index c2620b4dc..88dee0dcb 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js @@ -5,10 +5,10 @@ import { } from '../../send.selectors.js' const selectors = { + getAmountConversionRate, getMaxModeOn, - sendAmountIsInError, getPrimaryCurrency, - getAmountConversionRate, + sendAmountIsInError, } module.exports = selectors @@ -27,7 +27,7 @@ function getPrimaryCurrency (state) { } function getAmountConversionRate (state) { - return Boolean(getSelectedToken(state)) + return getSelectedToken(state) ? getSelectedTokenToFiatRate(state) : getConversionRate(state) } diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js index 418b98c18..6ec5463d3 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js @@ -1,4 +1,3 @@ -const { isValidAddress } = require('../../../../util') const { conversionGreaterThan, } = require('../../../../conversion-util') @@ -9,31 +8,23 @@ const { function getAmountErrorObject ({ amount, - balance, amountConversionRate, + balance, conversionRate, + gasTotal, primaryCurrency, selectedToken, - gasTotal, tokenBalance, }) { - console.log(`#& getAmountErrorObject amount`, amount); - console.log(`#& getAmountErrorObject balance`, balance); - console.log(`#& getAmountErrorObject amountConversionRate`, amountConversionRate); - console.log(`#& getAmountErrorObject conversionRate`, conversionRate); - console.log(`#& getAmountErrorObject primaryCurrency`, primaryCurrency); - console.log(`#& getAmountErrorObject selectedToken`, selectedToken); - console.log(`#& getAmountErrorObject gasTotal`, gasTotal); - console.log(`#& getAmountErrorObject tokenBalance`, tokenBalance); let insufficientFunds = false if (gasTotal && conversionRate) { insufficientFunds = !isBalanceSufficient({ amount: selectedToken ? '0x0' : amount, - gasTotal, - balance, - primaryCurrency, amountConversionRate, + balance, conversionRate, + gasTotal, + primaryCurrency, }) } @@ -66,5 +57,5 @@ function getAmountErrorObject ({ } module.exports = { - getAmountErrorObject + getAmountErrorObject, } diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js index 2d1fa52e9..1f5a06fcc 100644 --- a/ui/app/components/send_/send-content/send-content.component.js +++ b/ui/app/components/send_/send-content/send-content.component.js @@ -1,24 +1,23 @@ import React, { Component } from 'react' import PageContainerContent from '../../page-container/page-container-content.component' -import SendFromRow from './send-from-row/send-from-row.container' -import SendToRow from './send-to-row/send-to-row.container' import SendAmountRow from './send-amount-row/send-amount-row.container' +import SendFromRow from './send-from-row/send-from-row.container' import SendGasRow from './send-gas-row/send-gas-row.container' +import SendToRow from './send-to-row/send-to-row.container' export default class SendContent extends Component { render () { - console.log('111222333444555666777888999') return ( -
+
- ); + ) } } diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js index f215179ba..8af8eaf26 100644 --- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js +++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js @@ -26,25 +26,26 @@ export default class FromDropdown extends Component { renderDropdown () { const { accounts, - selectedAccount, closeDropdown, onSelect, + selectedAccount, } = this.props return (
closeDropdown} /> -
- {...accounts.map(account => + {...accounts.map((account, index) => { onSelect(account) closeDropdown() }} icon={this.getListItemIcon(account, selectedAccount.address)} + key={`from-dropdown-account-#${index}`} />)}
) @@ -52,13 +53,12 @@ export default class FromDropdown extends Component { render () { const { - selectedAccount, - openDropdown, dropdownOpen, + openDropdown, + selectedAccount, } = this.props - console.log(`&*& openDropdown`, openDropdown); - console.log(`&*& dropdownOpen`, dropdownOpen); - return
+ + return
this.handleFromChange(newFrom)} - openDropdown={() => openFromDropdown()} closeDropdown={() => closeFromDropdown()} conversionRate={conversionRate} + dropdownOpen={fromDropdownOpen} + onSelect={newFrom => this.handleFromChange(newFrom)} + openDropdown={() => openFromDropdown()} + selectedAccount={from} /> - ); + ) } } diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js index e2815a01f..d62782e9f 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js @@ -1,8 +1,8 @@ import { connect } from 'react-redux' import { + accountsWithSendEtherInfoSelector, getConversionRate, getSelectedTokenContract, - accountsWithSendEtherInfoSelector, getSendFromObject, } from '../../send.selectors.js' import { @@ -10,23 +10,22 @@ import { } from './send-from-row.selectors.js' import { calcTokenUpdateAmount } from './send-from-row.utils.js' import { - updateSendTokenBalance, updateSendFrom, + updateSendTokenBalance, } from '../../../../actions' import { - openFromDropdown, closeFromDropdown, + openFromDropdown, } from '../../../../ducks/send' import SendFromRow from './send-from-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendFromRow) function mapStateToProps (state) { - console.log(`$% mapStateToProps accountsWithSendEtherInfoSelector`, accountsWithSendEtherInfoSelector); return { + conversionRate: getConversionRate(state), from: getSendFromObject(state), fromAccounts: accountsWithSendEtherInfoSelector(state), - conversionRate: getConversionRate(state), fromDropdownOpen: getFromDropdownOpen(state), tokenContract: getSelectedTokenContract(state), } @@ -34,14 +33,14 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return { + closeFromDropdown: () => dispatch(closeFromDropdown()), + openFromDropdown: () => dispatch(openFromDropdown()), + updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)), updateSendTokenBalance: (usersToken, selectedToken) => { if (!usersToken) return const tokenBalance = calcTokenUpdateAmount(selectedToken, selectedToken) dispatch(updateSendTokenBalance(tokenBalance)) }, - updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)), - openFromDropdown: () => dispatch(openFromDropdown()), - closeFromDropdown: () => dispatch(closeFromDropdown()), } } diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js index 4faae48dc..0aaaef793 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js @@ -8,5 +8,5 @@ function calcTokenUpdateAmount (usersToken, selectedToken) { } module.exports = { - calcTokenUpdateAmount + calcTokenUpdateAmount, } diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js index 056a04aae..65dc6ad93 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js @@ -8,20 +8,23 @@ export default class SendGasRow extends Component { static propTypes = { closeFromDropdown: PropTypes.func, conversionRate: PropTypes.number, + convertedCurrency: PropTypes.string, from: PropTypes.string, fromAccounts: PropTypes.array, fromDropdownOpen: PropTypes.bool, + gasLoadingError: PropTypes.bool, + gasTotal: PropTypes.string, openFromDropdown: PropTypes.func, + showCustomizeGasModal: PropTypes.bool, tokenContract: PropTypes.object, updateSendFrom: PropTypes.func, updateSendTokenBalance: PropTypes.func, - gasLoadingError: PropTypes.bool, }; async handleFromChange (newFrom) { const { - updateSendFrom, tokenContract, + updateSendFrom, updateSendTokenBalance, } = this.props @@ -36,22 +39,22 @@ export default class SendGasRow extends Component { const { conversionRate, convertedCurrency, - showCustomizeGasModal, - gasTotal, gasLoadingError, + gasTotal, + showCustomizeGasModal, } = this.props return ( showCustomizeGasModal()} gasLoadingError={gasLoadingError} + gasTotal={gasTotal} + onClick={() => showCustomizeGasModal()} /> - ); + ) } } diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js index e553ae67e..c031248c7 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js @@ -14,13 +14,13 @@ export default class SendRowErrorMessage extends Component { return ( errorMessage - ?
{this.context.t(errorMessage)}
+ ?
{this.context.t(errorMessage)}
: null - ); + ) } } SendRowErrorMessage.contextTypes = { t: PropTypes.func, -} \ No newline at end of file +} diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js index 002426904..59622047f 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js @@ -9,4 +9,4 @@ function mapStateToProps (state, ownProps) { errors: getSendErrors(state), errorType: ownProps.errorType, } -} \ No newline at end of file +} diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js index 99134a466..c6b75eccd 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js @@ -5,22 +5,22 @@ import SendRowErrorMessage from './send-row-error-message/send-row-error-message export default class SendRowWrapper extends Component { static propTypes = { - label: PropTypes.string, - showError: PropTypes.bool, children: PropTypes.node, errorType: PropTypes.string, + label: PropTypes.string, + showError: PropTypes.bool, }; render () { const { - label, - errorType = '', - showError = false, - children, + children, + errorType = '', + label, + showError = false, } = this.props - let formField = Array.isArray(children) ? children[1] || children[0] : children - let customLabelContent = children.length === 1 ? children[0] : null + const formField = Array.isArray(children) ? children[1] || children[0] : children + const customLabelContent = children.length === 1 ? children[0] : null return (
@@ -33,7 +33,7 @@ export default class SendRowWrapper extends Component { {formField}
- ); + ) } } diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js index a20bbf434..a6e4c1624 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js @@ -6,15 +6,15 @@ import EnsInput from '../../../ens-input' export default class SendToRow extends Component { static propTypes = { + closeToDropdown: PropTypes.func, + inError: PropTypes.bool, + network: PropTypes.string, + openToDropdown: PropTypes.func, to: PropTypes.string, toAccounts: PropTypes.array, toDropdownOpen: PropTypes.bool, - inError: PropTypes.bool, updateSendTo: PropTypes.func, updateSendToError: PropTypes.func, - openToDropdown: PropTypes.func, - closeToDropdown: PropTypes.func, - network: PropTypes.string, }; handleToChange (to, nickname = '') { @@ -25,40 +25,35 @@ export default class SendToRow extends Component { render () { const { - from, - fromAccounts, - toAccounts, - conversionRate, - fromDropdownOpen, - tokenContract, - openToDropdown, closeToDropdown, - network, inError, + network, + openToDropdown, to, + toAccounts, toDropdownOpen, } = this.props return ( openToDropdown()} closeDropdown={() => closeToDropdown()} - onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)} + dropdownOpen={toDropdownOpen} inError={inError} + name={'address'} + network={network} + onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)} + openDropdown={() => openToDropdown()} + placeholder={this.context.t('recipientAddress')} + to={to} /> - ); + ) } } diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js index 661ec1f0c..b55455f8b 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js @@ -1,8 +1,7 @@ import { connect } from 'react-redux' import { - getSendTo, - getToAccounts, getCurrentNetwork, + getSendTo, getSendToAccounts, } from '../../send.selectors.js' import { @@ -23,23 +22,22 @@ import SendToRow from './send-to-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) function mapStateToProps (state) { - updateSendTo return { + inError: sendToIsInError(state), + network: getCurrentNetwork(state), to: getSendTo(state), toAccounts: getSendToAccounts(state), toDropdownOpen: getToDropdownOpen(state), - inError: sendToIsInError(state), - network: getCurrentNetwork(state), } } function mapDispatchToProps (dispatch) { return { + closeToDropdown: () => dispatch(closeToDropdown()), + openToDropdown: () => dispatch(openToDropdown()), + updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)), updateSendToError: (to) => { dispatch(updateSendErrors(getToErrorObject(to))) }, - updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)), - openToDropdown: () => dispatch(openToDropdown()), - closeToDropdown: () => dispatch(closeToDropdown()), } -} \ No newline at end of file +} diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js index 52bfde009..d290bf461 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js @@ -8,10 +8,10 @@ function getToErrorObject (to) { } else if (!isValidAddress(to)) { toError = 'invalidAddressRecipient' } - + return { to: toError } } module.exports = { - getToErrorObject + getToErrorObject, } diff --git a/ui/app/components/send_/send-footer/send-footer.component.js b/ui/app/components/send_/send-footer/send-footer.component.js index 64dd027cf..ffece433e 100644 --- a/ui/app/components/send_/send-footer/send-footer.component.js +++ b/ui/app/components/send_/send-footer/send-footer.component.js @@ -6,24 +6,24 @@ import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../routes' export default class SendFooter extends Component { static propTypes = { - addToAddressBook: PropTypes.func, + addToAddressBookIfNew: PropTypes.func, amount: PropTypes.string, clearSend: PropTypes.func, + disabled: PropTypes.bool, editingTransactionId: PropTypes.string, - errors: PropTypes.object, + errors: PropTypes.object, from: PropTypes.object, gasLimit: PropTypes.string, gasPrice: PropTypes.string, gasTotal: PropTypes.string, history: PropTypes.object, selectedToken: PropTypes.object, - signTokenTx: PropTypes.func, - signTx: PropTypes.func, + sign: PropTypes.func, to: PropTypes.string, toAccounts: PropTypes.array, tokenBalance: PropTypes.string, unapprovedTxs: PropTypes.object, - updateTx: PropTypes.func, + update: PropTypes.func, }; onSubmit (event) { @@ -56,13 +56,13 @@ export default class SendFooter extends Component { editingTransactionId ? update({ - from, - to, amount, + editingTransactionId, + from, gas, gasPrice, selectedToken, - editingTransactionId, + to, unapprovedTxs, }) : sign({ selectedToken, to, amount, from, gas, gasPrice }) @@ -83,7 +83,7 @@ export default class SendFooter extends Component { onSubmit={e => this.onSubmit(e)} disabled={disabled} /> - ); + ) } } diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js index fff6e284f..288c4daf2 100644 --- a/ui/app/components/send_/send-footer/send-footer.container.js +++ b/ui/app/components/send_/send-footer/send-footer.container.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux' -import ethUtil from 'ethereumjs-util' +import ethUtil from 'ethereumjs-util' import { addToAddressBook, clearSend, @@ -27,31 +27,32 @@ import { } from './send-footer.selectors' import { addressIsNew, - formShouldBeDisabled, constructTxParams, + constructUpdatedTx, + formShouldBeDisabled, } from './send-footer.utils' export default connect(mapStateToProps, mapDispatchToProps)(SendFooter) function mapStateToProps (state) { return { - isToken: Boolean(getSelectedToken(state)), - inError: isSendFormInError(state), + amount: getSendAmount(state), disabled: formShouldBeDisabled({ inError: isSendFormInError(state), selectedToken: getSelectedToken(state), tokenBalance: getTokenBalance(state), gasTotal: getGasTotal(state), }), - amount: getSendAmount(state), editingTransactionId: getSendEditingTransactionId(state), from: getSendFromObject(state), gasLimit: getGasLimit(state), gasPrice: getGasPrice(state), + inError: isSendFormInError(state), + isToken: Boolean(getSelectedToken(state)), selectedToken: getSelectedToken(state), to: getSendTo(state), - unapprovedTxs: getUnapprovedTxs(state), toAccounts: getSendToAccounts(state), + unapprovedTxs: getUnapprovedTxs(state), } } @@ -102,6 +103,6 @@ function mapDispatchToProps (dispatch) { // TODO: nickname, i.e. addToAddressBook(recipient, nickname) dispatch(addToAddressBook(hexPrefixedAddress, nickname)) } - } + }, } } diff --git a/ui/app/components/send_/send-footer/send-footer.selectors.js b/ui/app/components/send_/send-footer/send-footer.selectors.js index ccd4706ea..e8fef6be6 100644 --- a/ui/app/components/send_/send-footer/send-footer.selectors.js +++ b/ui/app/components/send_/send-footer/send-footer.selectors.js @@ -9,4 +9,4 @@ module.exports = selectors function isSendFormInError (state) { const { amount, to } = getSendErrors(state) return Boolean(amount || to !== null) -} \ No newline at end of file +} diff --git a/ui/app/components/send_/send-footer/send-footer.utils.js b/ui/app/components/send_/send-footer/send-footer.utils.js index 23d5655c7..353c0e347 100644 --- a/ui/app/components/send_/send-footer/send-footer.utils.js +++ b/ui/app/components/send_/send-footer/send-footer.utils.js @@ -1,5 +1,5 @@ import ethAbi from 'ethereumjs-abi' -import ethUtil from 'ethereumjs-util' +import ethUtil from 'ethereumjs-util' import { TOKEN_TRANSFER_FUNCTION_SIGNATURE } from '../send.constants' function formShouldBeDisabled ({ inError, selectedToken, tokenBalance, gasTotal }) { diff --git a/ui/app/components/send_/send-header/send-header.component.js b/ui/app/components/send_/send-header/send-header.component.js index 99adfc7e8..30cb5c749 100644 --- a/ui/app/components/send_/send-header/send-header.component.js +++ b/ui/app/components/send_/send-header/send-header.component.js @@ -5,9 +5,9 @@ import PageContainerHeader from '../../page-container/page-container-header.comp export default class SendHeader extends Component { static propTypes = { - isToken: PropTypes.bool, clearSend: PropTypes.func, goHome: PropTypes.func, + isToken: PropTypes.bool, }; render () { @@ -15,14 +15,14 @@ export default class SendHeader extends Component { return ( { clearSend() goHome() }} + subtitle={this.context.t('onlySendToEtherAddress')} + title={isToken ? this.context.t('sendTokens') : this.context.t('sendETH')} /> - ); + ) } } diff --git a/ui/app/components/send_/send-header/send-header.container.js b/ui/app/components/send_/send-header/send-header.container.js index a4d3ac54f..8eb2bbe2e 100644 --- a/ui/app/components/send_/send-header/send-header.container.js +++ b/ui/app/components/send_/send-header/send-header.container.js @@ -7,13 +7,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(SendHeader) function mapStateToProps (state) { return { - isToken: Boolean(getSelectedToken(state)) + isToken: Boolean(getSelectedToken(state)), } } function mapDispatchToProps (dispatch) { return { - goHome: () => dispatch(goHome()), clearSend: () => dispatch(clearSend()), + goHome: () => dispatch(goHome()), } } diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js index 4abebfa56..acce4afb7 100644 --- a/ui/app/components/send_/send.selectors.js +++ b/ui/app/components/send_/send.selectors.js @@ -29,8 +29,8 @@ const selectors = { getSendEditingTransactionId, getSendErrors, getSendFrom, - getSendFromObject, getSendFromBalance, + getSendFromObject, getSendMaxModeState, getSendTo, getSendToAccounts, @@ -43,56 +43,38 @@ const selectors = { module.exports = selectors -function getSelectedAddress (state) { - const selectedAddress = state.metamask.selectedAddress || Object.keys(state.metamask.accounts)[0] - - return selectedAddress -} - -function getSelectedIdentity (state) { - const selectedAddress = getSelectedAddress(state) - const identities = state.metamask.identities - - return identities[selectedAddress] -} - -function getSelectedAccount (state) { - const accounts = state.metamask.accounts - const selectedAddress = getSelectedAddress(state) - - return accounts[selectedAddress] -} +function accountsWithSendEtherInfoSelector (state) { + const { + accounts, + identities, + } = state.metamask -function getSelectedToken (state) { - const tokens = state.metamask.tokens || [] - const selectedTokenAddress = state.metamask.selectedTokenAddress - const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0] - const sendToken = state.metamask.send.token + const accountsWithSendEtherInfo = Object.entries(accounts).map(([key, account]) => { + return Object.assign({}, account, identities[key]) + }) - return selectedToken || sendToken || null + return accountsWithSendEtherInfo } -function getSelectedTokenExchangeRate (state) { - const tokenExchangeRates = state.metamask.tokenExchangeRates - const selectedToken = getSelectedToken(state) || {} - const { symbol = '' } = selectedToken - - const pair = `${symbol.toLowerCase()}_eth` - const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {} +function autoAddToBetaUI (state) { + const autoAddTransactionThreshold = 12 + const autoAddAccountsThreshold = 2 + const autoAddTokensThreshold = 1 - return tokenExchangeRate -} + const numberOfTransactions = state.metamask.selectedAddressTxList.length + const numberOfAccounts = Object.keys(state.metamask.accounts).length + const numberOfTokensAdded = state.metamask.tokens.length -function getTokenExchangeRate (state, tokenSymbol) { - const pair = `${tokenSymbol.toLowerCase()}_eth` - const tokenExchangeRates = state.metamask.tokenExchangeRates - const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {} + const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) && + (numberOfAccounts > autoAddAccountsThreshold) && + (numberOfTokensAdded > autoAddTokensThreshold) + const userIsNotInBeta = !state.metamask.featureFlags.betaUI - return tokenExchangeRate + return userIsNotInBeta && userPassesThreshold } -function getUnapprovedTxs (state) { - return state.metamask.unapprovedTxs +function getAddressBook (state) { + return state.metamask.addressBook } function getConversionRate (state) { @@ -103,25 +85,6 @@ function getConvertedCurrency (state) { return state.metamask.currentCurrency } -function getAddressBook (state) { - return state.metamask.addressBook -} - -function accountsWithSendEtherInfoSelector (state) { - const { - accounts, - identities, - } = state.metamask - console.log(`accountsWithSendEtherInfoSelector accounts`, accounts); - console.log(`accountsWithSendEtherInfoSelector identities`, identities); - const accountsWithSendEtherInfo = Object.entries(accounts).map(([key, account]) => { - return Object.assign({}, account, identities[key]) - }) - - console.log(`accountsWithSendEtherInfoSelector accountsWithSendEtherInfo`, accountsWithSendEtherInfo); - return accountsWithSendEtherInfo -} - function getCurrentAccountWithSendEtherInfo (state) { const currentAddress = getSelectedAddress(state) const accounts = accountsWithSendEtherInfoSelector(state) @@ -129,61 +92,80 @@ function getCurrentAccountWithSendEtherInfo (state) { return accounts.find(({ address }) => address === currentAddress) } -function transactionsSelector (state) { - const { network, selectedTokenAddress } = state.metamask - const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs) - const shapeShiftTxList = (network === '1') ? state.metamask.shapeShiftTxList : undefined - const transactions = state.metamask.selectedAddressTxList || [] - const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList) +function getCurrentCurrency (state) { + return state.metamask.currentCurrency +} - // console.log({txsToRender, selectedTokenAddress}) - return selectedTokenAddress - ? txsToRender - .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress) - .sort((a, b) => b.time - a.time) - : txsToRender - .sort((a, b) => b.time - a.time) +function getCurrentNetwork (state) { + return state.metamask.network } -function getGasPrice (state) { - return state.metamask.send.gasPrice +function getCurrentViewContext (state) { + const { currentView = {} } = state.appState + return currentView.context } -function getGasTotal (state) { - return state.metamask.send.gasTotal +function getForceGasMin (state) { + return state.metamask.send.forceGasMin +} + +function getGasPrice (state) { + return state.metamask.send.gasPrice } function getGasLimit (state) { return state.metamask.send.gasLimit } -function getForceGasMin (state) { - return state.metamask.send.forceGasMin +function getGasTotal (state) { + return state.metamask.send.gasTotal } -function getSendFrom (state) { - return state.metamask.send.from +function getSelectedAccount (state) { + const accounts = state.metamask.accounts + const selectedAddress = getSelectedAddress(state) + + return accounts[selectedAddress] } -function getSendFromObject (state) { - return getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state) +function getSelectedAddress (state) { + const selectedAddress = state.metamask.selectedAddress || Object.keys(state.metamask.accounts)[0] + + return selectedAddress } -function getSendFromBalance (state) { - const from = getSendFrom(state) || getSelectedAccount(state) - return from.balance +function getSelectedIdentity (state) { + const selectedAddress = getSelectedAddress(state) + const identities = state.metamask.identities + + return identities[selectedAddress] } -function getSendAmount (state) { - return state.metamask.send.amount +function getSelectedToken (state) { + const tokens = state.metamask.tokens || [] + const selectedTokenAddress = state.metamask.selectedTokenAddress + const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0] + const sendToken = state.metamask.send.token + + return selectedToken || sendToken || null } -function getSendMaxModeState (state) { - return state.metamask.send.maxModeOn +function getSelectedTokenContract (state) { + const selectedToken = getSelectedToken(state) + return selectedToken + ? global.eth.contract(abi).at(selectedToken.address) + : null } -function getCurrentCurrency (state) { - return state.metamask.currentCurrency +function getSelectedTokenExchangeRate (state) { + const tokenExchangeRates = state.metamask.tokenExchangeRates + const selectedToken = getSelectedToken(state) || {} + const { symbol = '' } = selectedToken + + const pair = `${symbol.toLowerCase()}_eth` + const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {} + + return tokenExchangeRate } function getSelectedTokenToFiatRate (state) { @@ -199,51 +181,39 @@ function getSelectedTokenToFiatRate (state) { return tokenToFiatRate } -function getSelectedTokenContract (state) { - const selectedToken = getSelectedToken(state) - return selectedToken - ? global.eth.contract(abi).at(selectedToken.address) - : null +function getSendAmount (state) { + return state.metamask.send.amount } -function autoAddToBetaUI (state) { - const autoAddTransactionThreshold = 12 - const autoAddAccountsThreshold = 2 - const autoAddTokensThreshold = 1 - - const numberOfTransactions = state.metamask.selectedAddressTxList.length - const numberOfAccounts = Object.keys(state.metamask.accounts).length - const numberOfTokensAdded = state.metamask.tokens.length +function getSendEditingTransactionId (state) { + return state.metamask.send.editingTransactionId +} - const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) && - (numberOfAccounts > autoAddAccountsThreshold) && - (numberOfTokensAdded > autoAddTokensThreshold) - const userIsNotInBeta = !state.metamask.featureFlags.betaUI +function getSendErrors (state) { + return state.metamask.send.errors +} - return userIsNotInBeta && userPassesThreshold +function getSendFrom (state) { + return state.metamask.send.from } -function getCurrentViewContext (state) { - const { currentView = {} } = state.appState - return currentView.context +function getSendFromBalance (state) { + const from = getSendFrom(state) || getSelectedAccount(state) + return from.balance } -function getSendEditingTransactionId (state) { - return state.metamask.send.editingTransactionId +function getSendFromObject (state) { + return getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state) } -function getSendErrors (state) { - return state.metamask.send.errors +function getSendMaxModeState (state) { + return state.metamask.send.maxModeOn } function getSendTo (state) { return state.metamask.send.to } -function getTokenBalance (state) { - return state.metamask.send.tokenBalance -} - function getSendToAccounts (state) { const fromAccounts = accountsWithSendEtherInfoSelector(state) const addressBookAccounts = getAddressBook(state) @@ -252,11 +222,38 @@ function getSendToAccounts (state) { return Object.entries(allAccounts).map(([key, account]) => account) } -function getCurrentNetwork (state) { - return state.metamask.network +function getTokenBalance (state) { + return state.metamask.send.tokenBalance +} + +function getTokenExchangeRate (state, tokenSymbol) { + const pair = `${tokenSymbol.toLowerCase()}_eth` + const tokenExchangeRates = state.metamask.tokenExchangeRates + const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {} + + return tokenExchangeRate +} + +function getUnapprovedTxs (state) { + return state.metamask.unapprovedTxs } function isSendFormInError (state) { const { amount, to } = getSendErrors(state) - return Boolean(amount || toError !== null) -} \ No newline at end of file + return Boolean(amount || to !== null) +} + +function transactionsSelector (state) { + const { network, selectedTokenAddress } = state.metamask + const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs) + const shapeShiftTxList = (network === '1') ? state.metamask.shapeShiftTxList : undefined + const transactions = state.metamask.selectedAddressTxList || [] + const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList) + + return selectedTokenAddress + ? txsToRender + .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress) + .sort((a, b) => b.time - a.time) + : txsToRender + .sort((a, b) => b.time - a.time) +} diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index f56f91e48..f289ea989 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -8,13 +8,21 @@ const { calcTokenAmount, } = require('../../token-util') +function getGasTotal (gasLimit, gasPrice) { + return multiplyCurrencies(gasLimit, gasPrice, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 16, + }) +} + function isBalanceSufficient ({ amount = '0x0', - gasTotal = '0x0', - balance, - primaryCurrency, amountConversionRate, + balance, conversionRate, + gasTotal = '0x0', + primaryCurrency, }) { const totalAmount = addCurrencies(amount, gasTotal, { aBase: 16, @@ -63,16 +71,8 @@ function isTokenBalanceSufficient ({ return tokenBalanceIsSufficient } -function getGasTotal (gasLimit, gasPrice) { - return multiplyCurrencies(gasLimit, gasPrice, { - toNumericBase: 'hex', - multiplicandBase: 16, - multiplierBase: 16, - }) -} - module.exports = { getGasTotal, isBalanceSufficient, isTokenBalanceSufficient, -} \ No newline at end of file +} diff --git a/ui/app/ducks/send.js b/ui/app/ducks/send.js index c4874aa8c..e0feb9f06 100644 --- a/ui/app/ducks/send.js +++ b/ui/app/ducks/send.js @@ -1,10 +1,10 @@ import extend from 'xtend' // Actions -const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN'; -const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN'; -const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'; -const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'; +const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN' +const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN' +const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN' +const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN' // TODO: determine if this approach to initState is consistent with conventional ducks pattern const initState = { @@ -14,7 +14,7 @@ const initState = { } // Reducer -export default function reducer({ send: sendState = initState }, action = {}) { +export default function reducer ({ send: sendState = initState }, action = {}) { switch (action.type) { case OPEN_FROM_DROPDOWN: return extend(sendState, { @@ -38,18 +38,18 @@ export default function reducer({ send: sendState = initState }, action = {}) { } // Action Creators -export function openFromDropdown() { - return { type: OPEN_FROM_DROPDOWN }; +export function openFromDropdown () { + return { type: OPEN_FROM_DROPDOWN } } -export function closeFromDropdown() { - return { type: CLOSE_FROM_DROPDOWN }; +export function closeFromDropdown () { + return { type: CLOSE_FROM_DROPDOWN } } -export function openToDropdown() { - return { type: OPEN_TO_DROPDOWN }; +export function openToDropdown () { + return { type: OPEN_TO_DROPDOWN } } -export function closeToDropdown() { - return { type: CLOSE_TO_DROPDOWN }; -} \ No newline at end of file +export function closeToDropdown () { + return { type: CLOSE_TO_DROPDOWN } +} diff --git a/ui/app/selectors.js b/ui/app/selectors.js index 60cc264da..a29294b86 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -185,4 +185,4 @@ function autoAddToBetaUI (state) { function getCurrentViewContext (state) { const { currentView = {} } = state.appState return currentView.context -} \ No newline at end of file +} diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index 12e8b4e60..efe06ee64 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -3,23 +3,8 @@ const PropTypes = require('prop-types') const PersistentForm = require('../lib/persistent-form') const h = require('react-hyperscript') -const ethAbi = require('ethereumjs-abi') -const ethUtil = require('ethereumjs-util') - -const FromDropdown = require('./components/send/from-dropdown') -const EnsInput = require('./components/ens-input') -const CurrencyDisplay = require('./components/send/currency-display') -const MemoTextArea = require('./components/send/memo-textarea') -const GasFeeDisplay = require('./components/send/gas-fee-display-v2') - -const { - TOKEN_TRANSFER_FUNCTION_SIGNATURE, -} = require('./components/send/send-constants') - const { - multiplyCurrencies, conversionGreaterThan, - subtractCurrencies, } = require('./conversion-util') const { calcTokenAmount, @@ -29,8 +14,6 @@ const { isTokenBalanceSufficient, getGasTotal, } = require('./components/send/send-utils') -const { isValidAddress } = require('./util') -const { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } = require('./routes') import PageContainer from './components/page-container/page-container.component' import SendHeader from './components/send_/send-header/send-header.container' -- cgit From 33c16d1bf62133a87d8f24232ee85438a6b6a0e6 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 27 Apr 2018 08:11:18 -0230 Subject: Fixes to get tests passing. --- ui/app/components/pending-tx/confirm-send-ether.js | 8 ++++---- ui/app/components/pending-tx/confirm-send-token.js | 4 ++-- .../send-from-row/from-dropdown/from-dropdown.component.js | 2 +- .../send_/send-content/send-gas-row/send-gas-row.component.js | 2 +- ui/app/send-v2.js | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index 16dbd273b..936db3bcb 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -141,7 +141,7 @@ ConfirmSendEther.prototype.updateComponentSendErrors = function (prevProps) { if (shouldUpdateBalanceSendErrors) { const balanceIsSufficient = this.isBalanceSufficient(txMeta) updateSendErrors({ - insufficientFunds: balanceIsSufficient ? false : this.context.t('insufficientFunds'), + insufficientFunds: balanceIsSufficient ? false : 'insufficientFunds', }) } @@ -149,7 +149,7 @@ ConfirmSendEther.prototype.updateComponentSendErrors = function (prevProps) { if (shouldUpdateSimulationSendError) { updateSendErrors({ - simulationFails: !txMeta.simulationFails ? false : this.context.t('transactionError'), + simulationFails: !txMeta.simulationFails ? false : 'transactionError', }) } } @@ -559,9 +559,9 @@ ConfirmSendEther.prototype.onSubmit = function (event) { if (valid && this.verifyGasParams() && balanceIsSufficient) { this.props.sendTransaction(txMeta, event) } else if (!balanceIsSufficient) { - updateSendErrors({ insufficientFunds: this.context.t('insufficientFunds') }) + updateSendErrors({ insufficientFunds: 'insufficientFunds' }) } else { - updateSendErrors({ invalidGasParams: this.context.t('invalidGasParams') }) + updateSendErrors({ invalidGasParams: 'invalidGasParams' }) this.setState({ submitting: false }) } } diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 656093b3d..281c42824 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -558,9 +558,9 @@ ConfirmSendToken.prototype.onSubmit = function (event) { if (valid && this.verifyGasParams() && balanceIsSufficient) { this.props.sendTransaction(txMeta, event) } else if (!balanceIsSufficient) { - updateSendErrors({ insufficientFunds: this.context.t('insufficientFunds') }) + updateSendErrors({ insufficientFunds: 'insufficientFunds' }) } else { - updateSendErrors({ invalidGasParams: this.context.t('invalidGasParams') }) + updateSendErrors({ invalidGasParams: 'invalidGasParams' }) this.setState({ submitting: false }) } } diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js index 8af8eaf26..337228122 100644 --- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js +++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js @@ -37,7 +37,7 @@ export default class FromDropdown extends Component { onClick={() => closeDropdown} />
- {...accounts.map((account, index) => { diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js index 65dc6ad93..a8441c92f 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js @@ -15,7 +15,7 @@ export default class SendGasRow extends Component { gasLoadingError: PropTypes.bool, gasTotal: PropTypes.string, openFromDropdown: PropTypes.func, - showCustomizeGasModal: PropTypes.bool, + showCustomizeGasModal: PropTypes.func, tokenContract: PropTypes.object, updateSendFrom: PropTypes.func, updateSendTokenBalance: PropTypes.func, diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index efe06ee64..ef67ebf5e 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -203,11 +203,11 @@ SendTransactionScreen.prototype.validateAmount = function (value) { ) if (conversionRate && !sufficientBalance) { - amountError = this.context.t('insufficientFunds') + amountError = 'insufficientFunds' } else if (verifyTokenBalance && !sufficientTokens) { - amountError = this.context.t('insufficientTokens') + amountError = 'insufficientTokens' } else if (amountLessThanZero) { - amountError = this.context.t('negativeETH') + amountError = 'negativeETH' } updateSendErrors({ amount: amountError }) -- cgit From 26f965bcceb26ce7cdec9df9f213b44be8d9acac Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 27 Apr 2018 16:33:00 -0230 Subject: Further refactors; includes refactor of send-v2.js and associated container --- ui/app/actions.js | 70 ++++++++-- ui/app/app.js | 4 +- ui/app/components/customize-gas-modal/index.js | 8 +- ui/app/components/pending-tx/confirm-send-ether.js | 6 +- ui/app/components/pending-tx/confirm-send-token.js | 6 +- ui/app/components/send/send-v2-container.js | 9 +- .../account-list-item.container.js | 2 +- .../amount-max-button.container.js | 6 +- .../send-amount-row/send-amount-row.component.js | 5 +- .../send-amount-row/send-amount-row.container.js | 14 +- .../send-amount-row/send-amount-row.selectors.js | 21 +-- .../send-amount-row/send-amount-row.utils.js | 61 --------- .../send-from-row/send-from-row.component.js | 6 +- .../send-from-row/send-from-row.container.js | 6 +- .../send-gas-row/send-gas-row.component.js | 15 --- .../send-row-error-message.component.js | 1 + .../send-to-row/send-to-row.container.js | 6 +- ui/app/components/send_/send.component.js | 143 +++++++++++++++++++++ ui/app/components/send_/send.container.js | 88 +++++++++++++ ui/app/components/send_/send.selectors.js | 18 ++- ui/app/components/send_/send.utils.js | 115 ++++++++++++++++- ui/app/ducks/send.js | 15 +++ ui/app/reducers/metamask.js | 11 -- ui/app/send-v2.js | 6 +- 24 files changed, 487 insertions(+), 155 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 674669eed..7b838940a 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1,6 +1,11 @@ const abi = require('human-standard-token-abi') const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url') const { getTokenAddressFromTokenObject } = require('./util') +const { + calcGasTotal, + getParamsForGasEstimate, + calcTokenBalance, +} = require('./components/send_/send.utils') const ethUtil = require('ethereumjs-util') const { fetchLocale } = require('../i18n-helper') const log = require('loglevel') @@ -173,14 +178,16 @@ var actions = { updateGasLimit, updateGasPrice, updateGasTotal, + setGasTotal, + setSendTokenBalance, updateSendTokenBalance, updateSendFrom, updateSendTo, updateSendAmount, updateSendMemo, - updateSendErrors, setMaxModeTo, updateSend, + updateSendErrors, clearSend, setSelectedAddress, // app messages @@ -716,14 +723,64 @@ function updateGasPrice (gasPrice) { } } -function updateGasTotal (gasTotal) { +function setGasTotal (gasTotal) { return { type: actions.UPDATE_GAS_TOTAL, value: gasTotal, } } -function updateSendTokenBalance (tokenBalance) { +function updateGasTotal ({ selectedAddress, selectedToken, data }) { + return (dispatch) => { + const { symbol } = selectedToken || {} + const estimateGasParams = getParamsForGasEstimate(selectedAddress, symbol, data) + return Promise.all([ + dispatch(actions.getGasPrice()), + dispatch(actions.estimateGas(estimateGasParams)), + ]) + .then(([gasPrice, gas]) => { + const newGasTotal = calcGasTotal(gas, gasPrice) + dispatch(actions.setGasTotal(newGasTotal)) + dispatch(updateSendErrors({ gasLoadingError: null })) + }) + .catch(err => { + log.error(err) + dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' })) + }) + } +} + +function updateSendTokenBalance ({ + selectedToken, + tokenContract, + address, +}) { + return (dispatch) => { + const tokenBalancePromise = tokenContract + ? tokenContract.balanceOf(address) + : Promise.resolve() + return tokenBalancePromise + .then(usersToken => { + if (usersToken) { + const newTokenBalance = calcTokenBalance({ selectedToken, usersToken }) + dispatch(setSendTokenBalance(newTokenBalance)) + } + }) + .catch(err => { + log.error(err) + updateSendErrors({ tokenBalance: 'tokenBalanceError' }) + }) + } +} + +function updateSendErrors (errorObject) { + return { + type: actions.UPDATE_SEND_ERRORS, + value: errorObject, + } +} + +function setSendTokenBalance (tokenBalance) { return { type: actions.UPDATE_SEND_TOKEN_BALANCE, value: tokenBalance, @@ -758,13 +815,6 @@ function updateSendMemo (memo) { } } -function updateSendErrors (error) { - return { - type: actions.UPDATE_SEND_ERRORS, - value: error, - } -} - function setMaxModeTo (bool) { return { type: actions.UPDATE_MAX_MODE, diff --git a/ui/app/app.js b/ui/app/app.js index 0b38b1326..6511979b2 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -11,7 +11,7 @@ const log = require('loglevel') // init const InitializeScreen = require('../../mascara/src/app/first-time').default // accounts -const SendTransactionScreen2 = require('./components/send/send-v2-container') +const SendTransactionScreen = require('./components/send_/send.container') const ConfirmTxScreen = require('./conf-tx') // slideout menu @@ -84,7 +84,7 @@ class App extends Component { h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }), h(Initialized, { path: NOTICE_ROUTE, exact, component: NoticeScreen }), h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }), - h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }), + h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen }), h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }), h(Authenticated, { path: DEFAULT_ROUTE, exact, component: Home }), diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 4c693d1c3..0761faa99 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -8,6 +8,10 @@ const GasModalCard = require('./gas-modal-card') const ethUtil = require('ethereumjs-util') +import { + updateSendErrors, +} from '../../ducks/send' + const { MIN_GAS_PRICE_DEC, MIN_GAS_LIMIT_DEC, @@ -63,9 +67,9 @@ function mapDispatchToProps (dispatch) { hideModal: () => dispatch(actions.hideModal()), updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)), updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), - updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)), + updateGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)), updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), - updateSendErrors: error => dispatch(actions.updateSendErrors(error)), + updateSendErrors: error => dispatch(updateSendErrors(error)), } } diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index 936db3bcb..ac1b15a4f 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -29,6 +29,10 @@ const currencies = require('currency-formatter/currencies') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') +import { + updateSendErrors, +} from '../../ducks/send' + ConfirmSendEther.contextTypes = { t: PropTypes.func, } @@ -105,7 +109,7 @@ function mapDispatchToProps (dispatch) { })) dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) }, - updateSendErrors: error => dispatch(actions.updateSendErrors(error)), + updateSendErrors: error => dispatch(updateSendErrors(error)), } } diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 281c42824..e1abc6d3d 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -39,6 +39,10 @@ const { } = require('../../selectors') const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') +import { + updateSendErrors, +} from '../../ducks/send' + ConfirmSendToken.contextTypes = { t: PropTypes.func, } @@ -141,7 +145,7 @@ function mapDispatchToProps (dispatch, ownProps) { })) dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) }, - updateSendErrors: error => dispatch(actions.updateSendErrors(error)), + updateSendErrors: error => dispatch(updateSendErrors(error)), } } diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js index adfc91240..6464439f2 100644 --- a/ui/app/components/send/send-v2-container.js +++ b/ui/app/components/send/send-v2-container.js @@ -18,6 +18,10 @@ const { getSelectedTokenContract, } = require('../../selectors') +import { + updateSendErrors, +} from '../../ducks/send' + module.exports = compose( withRouter, connect(mapStateToProps, mapDispatchToProps) @@ -74,7 +78,8 @@ function mapDispatchToProps (dispatch) { updateTx: txData => dispatch(actions.updateTransaction(txData)), setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)), addToAddressBook: (address, nickname) => dispatch(actions.addToAddressBook(address, nickname)), - updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)), + setGasTotal: newTotal => dispatch(actions.setGasTotal(newTotal)), + updateGasTotal: () => dispatch(actions.updateGasTotal()), updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)), updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), updateSendTokenBalance: tokenBalance => dispatch(actions.updateSendTokenBalance(tokenBalance)), @@ -82,7 +87,7 @@ function mapDispatchToProps (dispatch) { updateSendTo: (newTo, nickname) => dispatch(actions.updateSendTo(newTo, nickname)), updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)), - updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)), + updateSendErrors: newError => dispatch(updateSendErrors(newError)), clearSend: () => dispatch(actions.clearSend()), setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)), } diff --git a/ui/app/components/send_/account-list-item/account-list-item.container.js b/ui/app/components/send_/account-list-item/account-list-item.container.js index e1bc1dd79..3151b1f1d 100644 --- a/ui/app/components/send_/account-list-item/account-list-item.container.js +++ b/ui/app/components/send_/account-list-item/account-list-item.container.js @@ -12,4 +12,4 @@ function mapStateToProps (state) { conversionRate: getConversionRate(state), currentCurrency: getConvertedCurrency(state), } -} \ No newline at end of file +} diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js index 5bdb67995..4211f16ab 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js @@ -9,10 +9,12 @@ import { getMaxModeOn } from '../send-amount-row.selectors.js' import { calcMaxAmount } from './amount-max-button.utils.js' import { updateSendAmount, - updateSendErrors, setMaxModeTo, -} from '../../../actions' +} from '../../../../../actions' import AmountMaxButton from './amount-max-button.component' +import { + updateSendErrors, +} from '../../../../../ducks/send' export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton) diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index 5c3084365..dbebc0bdf 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -8,7 +8,10 @@ export default class SendAmountRow extends Component { static propTypes = { amount: PropTypes.string, - amountConversionRate: PropTypes.number, + amountConversionRate: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), balance: PropTypes.string, conversionRate: PropTypes.number, convertedCurrency: PropTypes.string, diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js index 16e88bede..13888ec53 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js @@ -1,24 +1,26 @@ import { connect } from 'react-redux' import { + getAmountConversionRate, getConversionRate, getConvertedCurrency, getGasTotal, + getPrimaryCurrency, getSelectedToken, getSendAmount, getSendFromBalance, getTokenBalance, -} from '../../send.selectors.js' +} from '../../send.selectors' import { - getAmountConversionRate, - getPrimaryCurrency, sendAmountIsInError, -} from './send-amount-row.selectors.js' -import { getAmountErrorObject } from './send-amount-row.utils.js' +} from './send-amount-row.selectors' +import { getAmountErrorObject } from '../../send.utils' import { setMaxModeTo, updateSendAmount, - updateSendErrors, } from '../../../../actions' +import { + updateSendErrors, +} from '../../../../ducks/send' import SendAmountRow from './send-amount-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow) diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js index 88dee0dcb..62e4f6b32 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js @@ -1,13 +1,5 @@ -import { - getSelectedToken, - getSelectedTokenToFiatRate, - getConversionRate, -} from '../../send.selectors.js' - const selectors = { - getAmountConversionRate, getMaxModeOn, - getPrimaryCurrency, sendAmountIsInError, } @@ -18,16 +10,5 @@ function getMaxModeOn (state) { } function sendAmountIsInError (state) { - return Boolean(state.metamask.send.errors.amount) -} - -function getPrimaryCurrency (state) { - const selectedToken = getSelectedToken(state) - return selectedToken && selectedToken.symbol -} - -function getAmountConversionRate (state) { - return getSelectedToken(state) - ? getSelectedTokenToFiatRate(state) - : getConversionRate(state) + return Boolean(state.send.errors.amount) } diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js index 6ec5463d3..e69de29bb 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js @@ -1,61 +0,0 @@ -const { - conversionGreaterThan, -} = require('../../../../conversion-util') -const { - isBalanceSufficient, - isTokenBalanceSufficient, -} = require('../../send.utils') - -function getAmountErrorObject ({ - amount, - amountConversionRate, - balance, - conversionRate, - gasTotal, - primaryCurrency, - selectedToken, - tokenBalance, -}) { - let insufficientFunds = false - if (gasTotal && conversionRate) { - insufficientFunds = !isBalanceSufficient({ - amount: selectedToken ? '0x0' : amount, - amountConversionRate, - balance, - conversionRate, - gasTotal, - primaryCurrency, - }) - } - - let inSufficientTokens = false - if (selectedToken && tokenBalance !== null) { - const { decimals } = selectedToken - inSufficientTokens = !isTokenBalanceSufficient({ - tokenBalance, - amount, - decimals, - }) - } - - const amountLessThanZero = conversionGreaterThan( - { value: 0, fromNumericBase: 'dec' }, - { value: amount, fromNumericBase: 'hex' }, - ) - - let amountError = null - - if (insufficientFunds) { - amountError = 'insufficientFunds' - } else if (inSufficientTokens) { - amountError = 'insufficientTokens' - } else if (amountLessThanZero) { - amountError = 'negativeETH' - } - - return { amount: amountError } -} - -module.exports = { - getAmountErrorObject, -} diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js index 193262fa3..17e7f8e46 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js @@ -14,19 +14,19 @@ export default class SendFromRow extends Component { openFromDropdown: PropTypes.func, tokenContract: PropTypes.object, updateSendFrom: PropTypes.func, - updateSendTokenBalance: PropTypes.func, + setSendTokenBalance: PropTypes.func, }; async handleFromChange (newFrom) { const { updateSendFrom, tokenContract, - updateSendTokenBalance, + setSendTokenBalance, } = this.props if (tokenContract) { const usersToken = await tokenContract.balanceOf(newFrom.address) - updateSendTokenBalance(usersToken) + setSendTokenBalance(usersToken) } updateSendFrom(newFrom) } diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js index d62782e9f..9e366445d 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js @@ -11,7 +11,7 @@ import { import { calcTokenUpdateAmount } from './send-from-row.utils.js' import { updateSendFrom, - updateSendTokenBalance, + setSendTokenBalance, } from '../../../../actions' import { closeFromDropdown, @@ -36,11 +36,11 @@ function mapDispatchToProps (dispatch) { closeFromDropdown: () => dispatch(closeFromDropdown()), openFromDropdown: () => dispatch(openFromDropdown()), updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)), - updateSendTokenBalance: (usersToken, selectedToken) => { + setSendTokenBalance: (usersToken, selectedToken) => { if (!usersToken) return const tokenBalance = calcTokenUpdateAmount(selectedToken, selectedToken) - dispatch(updateSendTokenBalance(tokenBalance)) + dispatch(setSendTokenBalance(tokenBalance)) }, } } diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js index a8441c92f..c62c110e0 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js @@ -18,23 +18,8 @@ export default class SendGasRow extends Component { showCustomizeGasModal: PropTypes.func, tokenContract: PropTypes.object, updateSendFrom: PropTypes.func, - updateSendTokenBalance: PropTypes.func, }; - async handleFromChange (newFrom) { - const { - tokenContract, - updateSendFrom, - updateSendTokenBalance, - } = this.props - - if (tokenContract) { - const usersToken = await tokenContract.balanceOf(newFrom.address) - updateSendTokenBalance(usersToken) - } - updateSendFrom(newFrom) - } - render () { const { conversionRate, diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js index c031248c7..0d314208b 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js @@ -10,6 +10,7 @@ export default class SendRowErrorMessage extends Component { render () { const { errors, errorType } = this.props + const errorMessage = errors[errorType] return ( diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js index b55455f8b..bffdda49c 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js @@ -10,12 +10,12 @@ import { } from './send-to-row.selectors.js' import { getToErrorObject } from './send-to-row.utils.js' import { - updateSendErrors, updateSendTo, } from '../../../../actions' import { - openToDropdown, - closeToDropdown, + updateSendErrors, + openToDropdown, + closeToDropdown, } from '../../../../ducks/send' import SendToRow from './send-to-row.component' diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index e69de29bb..e014e5bce 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -0,0 +1,143 @@ +import React from 'react' +import PropTypes from 'prop-types' +import PersistentForm from '../../../lib/persistent-form' +import { + getAmountErrorObject, + doesAmountErrorRequireUpdate, +} from './send.utils' + +import PageContainer from '..//page-container/page-container.component' +import SendHeader from './send-header/send-header.container' +import SendContent from './send-content/send-content.component' +import SendFooter from './send-footer/send-footer.container' + +export default class SendTransactionScreen extends PersistentForm { + + static propTypes = { + amount: PropTypes.string, + amountConversionRate: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), + conversionRate: PropTypes.number, + data: PropTypes.string, + editingTransactionId: PropTypes.string, + from: PropTypes.object, + gasLimit: PropTypes.string, + gasPrice: PropTypes.string, + gasTotal: PropTypes.string, + history: PropTypes.object, + network: PropTypes.string, + primaryCurrency: PropTypes.string, + selectedAddress: PropTypes.string, + selectedToken: PropTypes.object, + tokenBalance: PropTypes.string, + tokenContract: PropTypes.object, + updateAndSetGasTotal: PropTypes.func, + updateSendErrors: PropTypes.func, + updateSendTokenBalance: PropTypes.func, + }; + + updateGas () { + const { + data, + editingTransactionId, + gasLimit, + gasPrice, + selectedAddress, + selectedToken = {}, + updateAndSetGasTotal, + } = this.props + + updateAndSetGasTotal({ + data, + editingTransactionId, + gasLimit, + gasPrice, + selectedAddress, + selectedToken, + }) + } + + componentDidUpdate (prevProps) { + const { + amount, + amountConversionRate, + conversionRate, + from: { address, balance }, + gasTotal, + network, + primaryCurrency, + selectedToken, + tokenBalance, + updateSendErrors, + updateSendTokenBalance, + tokenContract, + } = this.props + + const { + from: { balance: prevBalance }, + gasTotal: prevGasTotal, + tokenBalance: prevTokenBalance, + network: prevNetwork, + } = prevProps + + const uninitialized = [prevBalance, prevGasTotal].every(n => n === null) + + if (!uninitialized) { + const amountErrorRequiresUpdate = doesAmountErrorRequireUpdate({ + balance, + gasTotal, + prevBalance, + prevGasTotal, + prevTokenBalance, + selectedToken, + tokenBalance, + }) + + if (amountErrorRequiresUpdate) { + const amountErrorObject = getAmountErrorObject({ + amount, + amountConversionRate, + balance, + conversionRate, + gasTotal, + primaryCurrency, + selectedToken, + tokenBalance, + }) + updateSendErrors(amountErrorObject) + } + + if (network !== prevNetwork && network !== 'loading') { + updateSendTokenBalance({ + selectedToken, + tokenContract, + address, + }) + this.updateGas() + } + } + } + + componentWillMount () { + this.updateGas() + } + + render () { + const { history } = this.props + + return ( + + + + + + ) + } + +} + +SendTransactionScreen.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js index e69de29bb..df605469a 100644 --- a/ui/app/components/send_/send.container.js +++ b/ui/app/components/send_/send.container.js @@ -0,0 +1,88 @@ +import { connect } from 'react-redux' +import abi from 'ethereumjs-abi' +import SendEther from './send.component' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import { + getAmountConversionRate, + getConversionRate, + getCurrentNetwork, + getGasLimit, + getGasPrice, + getGasTotal, + getPrimaryCurrency, + getSelectedAddress, + getSelectedToken, + getSelectedTokenContract, + getSelectedTokenToFiatRate, + getSendAmount, + getSendEditingTransactionId, + getSendFromObject, + getTokenBalance, +} from './send.selectors' +import { + updateSendTokenBalance, + updateGasTotal, + setGasTotal, +} from '../../actions' +import { + updateSendErrors, +} from '../../ducks/send' +import { + calcGasTotal, + generateTokenTransferData, +} from './send.utils.js' + +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(SendEther) + +function mapStateToProps (state) { + const selectedAddress = getSelectedAddress(state) + const selectedToken = getSelectedToken(state) + + return { + amount: getSendAmount(state), + amountConversionRate: getAmountConversionRate(state), + conversionRate: getConversionRate(state), + data: generateTokenTransferData(abi, selectedAddress, selectedToken), + editingTransactionId: getSendEditingTransactionId(state), + from: getSendFromObject(state), + gasLimit: getGasLimit(state), + gasPrice: getGasPrice(state), + gasTotal: getGasTotal(state), + network: getCurrentNetwork(state), + primaryCurrency: getPrimaryCurrency(state), + selectedAddress: getSelectedAddress(state), + selectedToken: getSelectedToken(state), + tokenBalance: getTokenBalance(state), + tokenContract: getSelectedTokenContract(state), + tokenToFiatRate: getSelectedTokenToFiatRate(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + updateAndSetGasTotal: ({ + data, + editingTransactionId, + gasLimit, + gasPrice, + selectedAddress, + selectedToken, + }) => { + !editingTransactionId + ? dispatch(updateGasTotal({ selectedAddress, selectedToken, data })) + : dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice))) + }, + updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => { + dispatch(updateSendTokenBalance({ + selectedToken, + tokenContract, + address, + })) + }, + updateSendErrors: newError => dispatch(updateSendErrors(newError)), + } +} diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js index acce4afb7..761b15182 100644 --- a/ui/app/components/send_/send.selectors.js +++ b/ui/app/components/send_/send.selectors.js @@ -8,6 +8,7 @@ const selectors = { accountsWithSendEtherInfoSelector, autoAddToBetaUI, getAddressBook, + getAmountConversionRate, getConversionRate, getConvertedCurrency, getCurrentAccountWithSendEtherInfo, @@ -18,6 +19,7 @@ const selectors = { getGasLimit, getGasPrice, getGasTotal, + getPrimaryCurrency, getSelectedAccount, getSelectedAddress, getSelectedIdentity, @@ -77,6 +79,12 @@ function getAddressBook (state) { return state.metamask.addressBook } +function getAmountConversionRate (state) { + return getSelectedToken(state) + ? getSelectedTokenToFiatRate(state) + : getConversionRate(state) +} + function getConversionRate (state) { return state.metamask.conversionRate } @@ -121,6 +129,11 @@ function getGasTotal (state) { return state.metamask.send.gasTotal } +function getPrimaryCurrency (state) { + const selectedToken = getSelectedToken(state) + return selectedToken && selectedToken.symbol +} + function getSelectedAccount (state) { const accounts = state.metamask.accounts const selectedAddress = getSelectedAddress(state) @@ -161,9 +174,8 @@ function getSelectedTokenExchangeRate (state) { const tokenExchangeRates = state.metamask.tokenExchangeRates const selectedToken = getSelectedToken(state) || {} const { symbol = '' } = selectedToken - const pair = `${symbol.toLowerCase()}_eth` - const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {} + const { rate: tokenExchangeRate = 0 } = tokenExchangeRates && tokenExchangeRates[pair] || {} return tokenExchangeRate } @@ -190,7 +202,7 @@ function getSendEditingTransactionId (state) { } function getSendErrors (state) { - return state.metamask.send.errors + return state.send.errors } function getSendFrom (state) { diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index f289ea989..2e17b04a3 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -7,8 +7,22 @@ const { const { calcTokenAmount, } = require('../../token-util') +const { + conversionGreaterThan, +} = require('../../conversion-util') + +module.exports = { + calcGasTotal, + doesAmountErrorRequireUpdate, + generateTokenTransferData, + getAmountErrorObject, + getParamsForGasEstimate, + calcTokenBalance, + isBalanceSufficient, + isTokenBalanceSufficient, +} -function getGasTotal (gasLimit, gasPrice) { +function calcGasTotal (gasLimit, gasPrice) { return multiplyCurrencies(gasLimit, gasPrice, { toNumericBase: 'hex', multiplicandBase: 16, @@ -71,8 +85,99 @@ function isTokenBalanceSufficient ({ return tokenBalanceIsSufficient } -module.exports = { - getGasTotal, - isBalanceSufficient, - isTokenBalanceSufficient, +function getAmountErrorObject ({ + amount, + amountConversionRate, + balance, + conversionRate, + gasTotal, + primaryCurrency, + selectedToken, + tokenBalance, +}) { + let insufficientFunds = false + if (gasTotal && conversionRate) { + insufficientFunds = !isBalanceSufficient({ + amount: selectedToken ? '0x0' : amount, + amountConversionRate, + balance, + conversionRate, + gasTotal, + primaryCurrency, + }) + } + + let inSufficientTokens = false + if (selectedToken && tokenBalance !== null) { + const { decimals } = selectedToken + inSufficientTokens = !isTokenBalanceSufficient({ + tokenBalance, + amount, + decimals, + }) + } + + const amountLessThanZero = conversionGreaterThan( + { value: 0, fromNumericBase: 'dec' }, + { value: amount, fromNumericBase: 'hex' }, + ) + + let amountError = null + + if (insufficientFunds) { + amountError = 'insufficientFunds' + } else if (inSufficientTokens) { + amountError = 'insufficientTokens' + } else if (amountLessThanZero) { + amountError = 'negativeETH' + } + + return { amount: amountError } +} + +function getParamsForGasEstimate (selectedAddress, symbol, data) { + const estimatedGasParams = { + from: selectedAddress, + gas: '746a528800', + } + + if (symbol) { + Object.assign(estimatedGasParams, { value: '0x0' }) + } + + if (data) { + Object.assign(estimatedGasParams, { data }) + } + + return estimatedGasParams +} + +function calcTokenBalance ({ selectedToken, usersToken }) { + const { decimals } = selectedToken || {} + return calcTokenAmount(usersToken.balance.toString(), decimals) +} + +function doesAmountErrorRequireUpdate ({ + balance, + gasTotal, + prevBalance, + prevGasTotal, + prevTokenBalance, + selectedToken, + tokenBalance, +}) { + const balanceHasChanged = balance !== prevBalance + const gasTotalHasChange = gasTotal !== prevGasTotal + const tokenBalanceHasChanged = selectedToken && tokenBalance !== prevTokenBalance + const amountErrorRequiresUpdate = balanceHasChanged || gasTotalHasChange || tokenBalanceHasChanged + + return amountErrorRequiresUpdate +} + +function generateTokenTransferData (abi, selectedAddress, selectedToken) { + if (!selectedToken) return + return Array.prototype.map.call( + abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']), + x => ('00' + x.toString(16)).slice(-2) + ).join('') } diff --git a/ui/app/ducks/send.js b/ui/app/ducks/send.js index e0feb9f06..aef493ea0 100644 --- a/ui/app/ducks/send.js +++ b/ui/app/ducks/send.js @@ -5,6 +5,7 @@ const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN' const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN' const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN' const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN' +const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS' // TODO: determine if this approach to initState is consistent with conventional ducks pattern const initState = { @@ -32,6 +33,13 @@ export default function reducer ({ send: sendState = initState }, action = {}) { return extend(sendState, { toDropdownOpen: false, }) + case UPDATE_SEND_ERRORS: + return extend(sendState, { + errors: { + ...sendState.errors, + ...action.value, + }, + }) default: return sendState } @@ -53,3 +61,10 @@ export function openToDropdown () { export function closeToDropdown () { return { type: CLOSE_TO_DROPDOWN } } + +export function updateSendErrors (errorObject) { + return { + type: UPDATE_SEND_ERRORS, + value: errorObject, + } +} diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index bb35cf990..5564c8080 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -254,17 +254,6 @@ function reduceMetamask (state, action) { }, }) - case actions.UPDATE_SEND_ERRORS: - return extend(metamaskState, { - send: { - ...metamaskState.send, - errors: { - ...metamaskState.send.errors, - ...action.value, - }, - }, - }) - case actions.UPDATE_MAX_MODE: return extend(metamaskState, { send: { diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index ef67ebf5e..16964b45d 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -84,7 +84,7 @@ SendTransactionScreen.prototype.updateGas = function () { estimateGas, selectedAddress, data, - updateGasTotal, + setGasTotal, from, tokenContract, editingTransactionId, @@ -110,7 +110,7 @@ SendTransactionScreen.prototype.updateGas = function () { ]) .then(([gasPrice, gas]) => { const newGasTotal = getGasTotal(gas, gasPrice) - updateGasTotal(newGasTotal) + setGasTotal(newGasTotal) this.setState({ gasLoadingError: false }) }) .catch(err => { @@ -118,7 +118,7 @@ SendTransactionScreen.prototype.updateGas = function () { }) } else { const newGasTotal = getGasTotal(gasLimit, gasPrice) - updateGasTotal(newGasTotal) + setGasTotal(newGasTotal) } } -- cgit From 5529ec6efd6fa659d790b4641e47cf247b7cfca7 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 30 Apr 2018 13:00:51 -0230 Subject: Fix close button on send screen --- ui/app/components/send_/send-header/send-header.component.js | 7 ++++--- ui/app/components/send_/send.component.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-header/send-header.component.js b/ui/app/components/send_/send-header/send-header.component.js index 30cb5c749..098fd89a4 100644 --- a/ui/app/components/send_/send-header/send-header.component.js +++ b/ui/app/components/send_/send-header/send-header.component.js @@ -1,23 +1,24 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import PageContainerHeader from '../../page-container/page-container-header.component' +import { DEFAULT_ROUTE } from '../../../routes' export default class SendHeader extends Component { static propTypes = { clearSend: PropTypes.func, - goHome: PropTypes.func, + history: PropTypes.object, isToken: PropTypes.bool, }; render () { - const { isToken, clearSend, goHome } = this.props + const { isToken, clearSend, history } = this.props return ( { clearSend() - goHome() + history.push(DEFAULT_ROUTE) }} subtitle={this.context.t('onlySendToEtherAddress')} title={isToken ? this.context.t('sendTokens') : this.context.t('sendETH')} diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index e014e5bce..969d76946 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -129,7 +129,7 @@ export default class SendTransactionScreen extends PersistentForm { return ( - + -- cgit From 41b609ab5ba185c8f5d7068aea8a638ae194ae66 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 30 Apr 2018 13:33:49 -0230 Subject: Fix amount max button. --- .../send-amount-row/amount-max-button/amount-max-button.component.js | 4 ++-- .../send-amount-row/amount-max-button/amount-max-button.container.js | 4 ++-- .../send-amount-row/amount-max-button/amount-max-button.utils.js | 2 +- .../send_/send-content/send-amount-row/send-amount-row.component.js | 2 +- .../send_/send-content/send-row-wrapper/send-row-wrapper.component.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js index c3b5b6ae4..bdf12b738 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js @@ -13,7 +13,7 @@ export default class AmountMaxButton extends Component { tokenBalance: PropTypes.string, }; - setAmountToMax = function () { + setMaxAmount () { const { balance, gasTotal, @@ -39,7 +39,7 @@ export default class AmountMaxButton extends Component { onClick={(event) => { event.preventDefault() setMaxModeTo(true) - this.setAmountToMax() + this.setMaxAmount() }} > {!maxModeOn ? this.context.t('max') : ''} diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js index 4211f16ab..b2ad1877a 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js @@ -32,8 +32,8 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return { setAmountToMax: maxAmountDataObject => { - updateSendErrors({ amount: null }) - updateSendAmount(calcMaxAmount(maxAmountDataObject)) + dispatch(updateSendErrors({ amount: null })) + dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject))) }, setMaxModeTo: bool => dispatch(setMaxModeTo(bool)), } diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js index d87f2424b..b490a7fd7 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js @@ -1,7 +1,7 @@ const { multiplyCurrencies, subtractCurrencies, -} = require('../../../../conversion-util') +} = require('../../../../../conversion-util') const ethUtil = require('ethereumjs-util') function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) { diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index dbebc0bdf..8e201ae41 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' -import AmountMaxButton from './amount-max-button/amount-max-button.component' +import AmountMaxButton from './amount-max-button/amount-max-button.container' import CurrencyDisplay from '../../../send/currency-display' export default class SendAmountRow extends Component { diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js index c6b75eccd..707b3ae80 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js @@ -20,7 +20,7 @@ export default class SendRowWrapper extends Component { } = this.props const formField = Array.isArray(children) ? children[1] || children[0] : children - const customLabelContent = children.length === 1 ? children[0] : null + const customLabelContent = children.length > 1 ? children[0] : null return (
-- cgit From bc145dc6a64ef5536bcbd263707b4cee071aea3b Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 30 Apr 2018 13:39:05 -0230 Subject: Fix send-v2__form class in send-content.component --- ui/app/components/send_/send-content/send-content.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js index 1f5a06fcc..e213cfcf3 100644 --- a/ui/app/components/send_/send-content/send-content.component.js +++ b/ui/app/components/send_/send-content/send-content.component.js @@ -10,7 +10,7 @@ export default class SendContent extends Component { render () { return ( -
+
-- cgit From 4a8c3194c2a4742d448d9fa9240c56a1e3319601 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 30 Apr 2018 14:00:01 -0230 Subject: Use constants for send screen errors messages. --- .../send-content/send-to-row/send-to-row.selectors.js | 2 +- .../send_/send-content/send-to-row/send-to-row.utils.js | 8 ++++++-- ui/app/components/send_/send.constants.js | 17 ++++++++++++++--- ui/app/components/send_/send.utils.js | 11 ++++++++--- 4 files changed, 29 insertions(+), 9 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js index c741aad84..8919014be 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js @@ -10,5 +10,5 @@ function getToDropdownOpen (state) { } function sendToIsInError (state) { - return Boolean(state.metamask.send.errors.to) + return Boolean(state.send.errors.to) } diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js index d290bf461..22e2e1f34 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js @@ -1,12 +1,16 @@ +const { + REQUIRED_ERROR, + INVALID_RECIPIENT_ADDRESS_ERROR, +} = require('../../send.constants') const { isValidAddress } = require('../../../../util') function getToErrorObject (to) { let toError = null if (!to) { - toError = 'required' + toError = REQUIRED_ERROR } else if (!isValidAddress(to)) { - toError = 'invalidAddressRecipient' + toError = INVALID_RECIPIENT_ADDRESS_ERROR } return { to: toError } diff --git a/ui/app/components/send_/send.constants.js b/ui/app/components/send_/send.constants.js index b3ee0899a..d047ed704 100644 --- a/ui/app/components/send_/send.constants.js +++ b/ui/app/components/send_/send.constants.js @@ -22,12 +22,23 @@ const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, { const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb' +const INSUFFICIENT_FUNDS_ERROR = 'insufficientFunds' +const INSUFFICIENT_TOKENS_ERROR = 'insufficientTokens' +const NEGATIVE_ETH_ERROR = 'negativeETH' +const INVALID_RECIPIENT_ADDRESS_ERROR = 'invalidAddressRecipient' +const REQUIRED_ERROR = 'required' + module.exports = { + INSUFFICIENT_FUNDS_ERROR, + INSUFFICIENT_TOKENS_ERROR, + INVALID_RECIPIENT_ADDRESS_ERROR, + MIN_GAS_LIMIT_DEC, + MIN_GAS_LIMIT_HEX, + MIN_GAS_PRICE_DEC, MIN_GAS_PRICE_GWEI, MIN_GAS_PRICE_HEX, - MIN_GAS_PRICE_DEC, - MIN_GAS_LIMIT_HEX, - MIN_GAS_LIMIT_DEC, MIN_GAS_TOTAL, + NEGATIVE_ETH_ERROR, + REQUIRED_ERROR, TOKEN_TRANSFER_FUNCTION_SIGNATURE, } diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 2e17b04a3..e537d5624 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -10,6 +10,11 @@ const { const { conversionGreaterThan, } = require('../../conversion-util') +const { + INSUFFICIENT_FUNDS_ERROR, + INSUFFICIENT_TOKENS_ERROR, + NEGATIVE_ETH_ERROR, +} = require('./send.constants') module.exports = { calcGasTotal, @@ -125,11 +130,11 @@ function getAmountErrorObject ({ let amountError = null if (insufficientFunds) { - amountError = 'insufficientFunds' + amountError = INSUFFICIENT_FUNDS_ERROR } else if (inSufficientTokens) { - amountError = 'insufficientTokens' + amountError = INSUFFICIENT_TOKENS_ERROR } else if (amountLessThanZero) { - amountError = 'negativeETH' + amountError = NEGATIVE_ETH_ERROR } return { amount: amountError } -- cgit From beb8d1cf5e2c15a57a72c85067f31c2e383ea08c Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 30 Apr 2018 14:07:01 -0230 Subject: Move getMaxModeOn selector to amount-max-button container. --- .../amount-max-button/amount-max-button.container.js | 2 +- .../amount-max-button/amount-max-button.selectors.js | 9 +++++++++ .../send-content/send-amount-row/send-amount-row.selectors.js | 5 ----- 3 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js index b2ad1877a..a72f41775 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js @@ -5,7 +5,7 @@ import { getSendFromBalance, getTokenBalance, } from '../../../send.selectors.js' -import { getMaxModeOn } from '../send-amount-row.selectors.js' +import { getMaxModeOn } from './amount-max-button.selectors.js' import { calcMaxAmount } from './amount-max-button.utils.js' import { updateSendAmount, diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js new file mode 100644 index 000000000..69fec1994 --- /dev/null +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js @@ -0,0 +1,9 @@ +const selectors = { + getMaxModeOn, +} + +module.exports = selectors + +function getMaxModeOn (state) { + return state.metamask.send.maxModeOn +} diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js index 62e4f6b32..fb08c7ed7 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js @@ -1,14 +1,9 @@ const selectors = { - getMaxModeOn, sendAmountIsInError, } module.exports = selectors -function getMaxModeOn (state) { - return state.metamask.send.maxModeOn -} - function sendAmountIsInError (state) { return Boolean(state.send.errors.amount) } -- cgit From 954394f81090b1a6a4afe55243caa3671b88addc Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 30 Apr 2018 14:08:03 -0230 Subject: Remove 'goHome' from send_ --- ui/app/components/send_/send-footer/send-footer.container.js | 2 -- ui/app/components/send_/send-header/send-header.container.js | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js index 288c4daf2..022b2db08 100644 --- a/ui/app/components/send_/send-footer/send-footer.container.js +++ b/ui/app/components/send_/send-footer/send-footer.container.js @@ -3,7 +3,6 @@ import ethUtil from 'ethereumjs-util' import { addToAddressBook, clearSend, - goHome, signTokenTx, signTx, updateTransaction, @@ -58,7 +57,6 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return { - goHome: () => dispatch(goHome()), clearSend: () => dispatch(clearSend()), sign: ({ selectedToken, to, amount, from, gas, gasPrice }) => { const txParams = constructTxParams({ diff --git a/ui/app/components/send_/send-header/send-header.container.js b/ui/app/components/send_/send-header/send-header.container.js index 8eb2bbe2e..0c92da3a6 100644 --- a/ui/app/components/send_/send-header/send-header.container.js +++ b/ui/app/components/send_/send-header/send-header.container.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux' -import { goHome, clearSend } from '../../../actions' +import { clearSend } from '../../../actions' import SendHeader from './send-header.component' import { getSelectedToken } from '../../../selectors' @@ -14,6 +14,5 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return { clearSend: () => dispatch(clearSend()), - goHome: () => dispatch(goHome()), } } -- cgit From e488c0eeeace80708285fa5e7d83f3fa219a86c8 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 1 May 2018 00:25:36 -0230 Subject: Delete dead send code. --- .../components/dropdowns/account-dropdown-mini.js | 2 +- ui/app/components/send/account-list-item.js | 74 ------- ui/app/components/send/from-dropdown.js | 72 ------- ui/app/components/send/gas-tooltip.js | 106 ---------- ui/app/components/send/memo-textarea.js | 33 --- ui/app/components/send/send-constants.js | 33 --- ui/app/components/send/send-utils.js | 78 ------- ui/app/components/send/send-v2-container.js | 94 --------- ui/app/components/send/to-autocomplete.js | 2 +- ui/app/send-v2.js | 231 --------------------- 10 files changed, 2 insertions(+), 723 deletions(-) delete mode 100644 ui/app/components/send/account-list-item.js delete mode 100644 ui/app/components/send/from-dropdown.js delete mode 100644 ui/app/components/send/gas-tooltip.js delete mode 100644 ui/app/components/send/memo-textarea.js delete mode 100644 ui/app/components/send/send-constants.js delete mode 100644 ui/app/components/send/send-utils.js delete mode 100644 ui/app/components/send/send-v2-container.js delete mode 100644 ui/app/send-v2.js (limited to 'ui') diff --git a/ui/app/components/dropdowns/account-dropdown-mini.js b/ui/app/components/dropdowns/account-dropdown-mini.js index a3d41af90..a7a908d3b 100644 --- a/ui/app/components/dropdowns/account-dropdown-mini.js +++ b/ui/app/components/dropdowns/account-dropdown-mini.js @@ -1,7 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const AccountListItem = require('../send/account-list-item') +const AccountListItem = require('../send_/account-list-item/account-list-item.component').default module.exports = AccountDropdownMini diff --git a/ui/app/components/send/account-list-item.js b/ui/app/components/send/account-list-item.js deleted file mode 100644 index b5e604a6e..000000000 --- a/ui/app/components/send/account-list-item.js +++ /dev/null @@ -1,74 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const connect = require('react-redux').connect -const { checksumAddress } = require('../../util') -const Identicon = require('../identicon') -const CurrencyDisplay = require('./currency-display') -const { conversionRateSelector, getCurrentCurrency } = require('../../selectors') - -inherits(AccountListItem, Component) -function AccountListItem () { - Component.call(this) -} - -function mapStateToProps (state) { - return { - conversionRate: conversionRateSelector(state), - currentCurrency: getCurrentCurrency(state), - } -} - -module.exports = connect(mapStateToProps)(AccountListItem) - -AccountListItem.prototype.render = function () { - const { - className, - account, - handleClick, - icon = null, - conversionRate, - currentCurrency, - displayBalance = true, - displayAddress = false, - } = this.props - - const { name, address, balance } = account || {} - - return h('div.account-list-item', { - className, - onClick: () => handleClick({ name, address, balance }), - }, [ - - h('div.account-list-item__top-row', {}, [ - - h( - Identicon, - { - address, - diameter: 18, - className: 'account-list-item__identicon', - }, - ), - - h('div.account-list-item__account-name', {}, name || address), - - icon && h('div.account-list-item__icon', [icon]), - - ]), - - displayAddress && name && h('div.account-list-item__account-address', checksumAddress(address)), - - displayBalance && h(CurrencyDisplay, { - primaryCurrency: 'ETH', - convertedCurrency: currentCurrency, - value: balance, - conversionRate, - readOnly: true, - className: 'account-list-item__account-balances', - primaryBalanceClassName: 'account-list-item__account-primary-balance', - convertedBalanceClassName: 'account-list-item__account-secondary-balance', - }, name), - - ]) -} diff --git a/ui/app/components/send/from-dropdown.js b/ui/app/components/send/from-dropdown.js deleted file mode 100644 index 0686fbe73..000000000 --- a/ui/app/components/send/from-dropdown.js +++ /dev/null @@ -1,72 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const AccountListItem = require('./account-list-item') - -module.exports = FromDropdown - -inherits(FromDropdown, Component) -function FromDropdown () { - Component.call(this) -} - -FromDropdown.prototype.getListItemIcon = function (currentAccount, selectedAccount) { - const listItemIcon = h(`i.fa.fa-check.fa-lg`, { style: { color: '#02c9b1' } }) - - return currentAccount.address === selectedAccount.address - ? listItemIcon - : null -} - -FromDropdown.prototype.renderDropdown = function () { - const { - accounts, - selectedAccount, - closeDropdown, - onSelect, - } = this.props - - return h('div', {}, [ - - h('div.send-v2__from-dropdown__close-area', { - onClick: closeDropdown, - }), - - h('div.send-v2__from-dropdown__list', {}, [ - - ...accounts.map(account => h(AccountListItem, { - className: 'account-list-item__dropdown', - account, - handleClick: () => { - onSelect(account) - closeDropdown() - }, - icon: this.getListItemIcon(account, selectedAccount), - })), - - ]), - - ]) -} - -FromDropdown.prototype.render = function () { - const { - selectedAccount, - openDropdown, - dropdownOpen, - } = this.props - - return h('div.send-v2__from-dropdown', {}, [ - - h(AccountListItem, { - account: selectedAccount, - handleClick: openDropdown, - icon: h(`i.fa.fa-caret-down.fa-lg`, { style: { color: '#dedede' } }), - }), - - dropdownOpen && this.renderDropdown(), - - ]) - -} - diff --git a/ui/app/components/send/gas-tooltip.js b/ui/app/components/send/gas-tooltip.js deleted file mode 100644 index 62cdc1cad..000000000 --- a/ui/app/components/send/gas-tooltip.js +++ /dev/null @@ -1,106 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const inherits = require('util').inherits -const InputNumber = require('../input-number.js') -const connect = require('react-redux').connect - -GasTooltip.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect()(GasTooltip) - - -inherits(GasTooltip, Component) -function GasTooltip () { - Component.call(this) - this.state = { - gasLimit: 0, - gasPrice: 0, - } - - this.updateGasPrice = this.updateGasPrice.bind(this) - this.updateGasLimit = this.updateGasLimit.bind(this) - this.onClose = this.onClose.bind(this) -} - -GasTooltip.prototype.componentWillMount = function () { - const { gasPrice = 0, gasLimit = 0} = this.props - - this.setState({ - gasPrice: parseInt(gasPrice, 16) / 1000000000, - gasLimit: parseInt(gasLimit, 16), - }) -} - -GasTooltip.prototype.updateGasPrice = function (newPrice) { - const { onFeeChange } = this.props - const { gasLimit } = this.state - - this.setState({ gasPrice: newPrice }) - onFeeChange({ - gasLimit: gasLimit.toString(16), - gasPrice: (newPrice * 1000000000).toString(16), - }) -} - -GasTooltip.prototype.updateGasLimit = function (newLimit) { - const { onFeeChange } = this.props - const { gasPrice } = this.state - - this.setState({ gasLimit: newLimit }) - onFeeChange({ - gasLimit: newLimit.toString(16), - gasPrice: (gasPrice * 1000000000).toString(16), - }) -} - -GasTooltip.prototype.onClose = function (e) { - e.stopPropagation() - this.props.onClose() -} - -GasTooltip.prototype.render = function () { - const { gasPrice, gasLimit } = this.state - - return h('div.gas-tooltip', {}, [ - h('div.gas-tooltip-close-area', { - onClick: this.onClose, - }), - h('div.customize-gas-tooltip-container', {}, [ - h('div.customize-gas-tooltip', {}, [ - h('div.gas-tooltip-header.gas-tooltip-label', {}, ['Customize Gas']), - h('div.gas-tooltip-input-label', {}, [ - h('span.gas-tooltip-label', {}, ['Gas Price']), - h('i.fa.fa-info-circle'), - ]), - h(InputNumber, { - unitLabel: 'GWEI', - step: 1, - min: 0, - placeholder: '0', - value: gasPrice, - onChange: (newPrice) => this.updateGasPrice(newPrice), - }), - h('div.gas-tooltip-input-label', { - style: { - 'marginTop': '81px', - }, - }, [ - h('span.gas-tooltip-label', {}, [this.context.t('gasLimit')]), - h('i.fa.fa-info-circle'), - ]), - h(InputNumber, { - unitLabel: 'UNITS', - step: 1, - min: 0, - placeholder: '0', - value: gasLimit, - onChange: (newLimit) => this.updateGasLimit(newLimit), - }), - ]), - h('div.gas-tooltip-arrow', {}), - ]), - ]) -} diff --git a/ui/app/components/send/memo-textarea.js b/ui/app/components/send/memo-textarea.js deleted file mode 100644 index f4bb24bf8..000000000 --- a/ui/app/components/send/memo-textarea.js +++ /dev/null @@ -1,33 +0,0 @@ -// const Component = require('react').Component -// const h = require('react-hyperscript') -// const inherits = require('util').inherits -// const Identicon = require('../identicon') - -// module.exports = MemoTextArea - -// inherits(MemoTextArea, Component) -// function MemoTextArea () { -// Component.call(this) -// } - -// MemoTextArea.prototype.render = function () { -// const { memo, identities, onChange } = this.props - -// return h('div.send-v2__memo-text-area', [ - -// h('textarea.send-v2__memo-text-area__input', { -// placeholder: 'Optional', -// value: memo, -// onChange, -// // onBlur: () => { -// // this.setErrorsFor('memo') -// // }, -// onFocus: event => { -// // this.clearErrorsFor('memo') -// }, -// }), - -// ]) - -// } - diff --git a/ui/app/components/send/send-constants.js b/ui/app/components/send/send-constants.js deleted file mode 100644 index b3ee0899a..000000000 --- a/ui/app/components/send/send-constants.js +++ /dev/null @@ -1,33 +0,0 @@ -const ethUtil = require('ethereumjs-util') -const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') - -const MIN_GAS_PRICE_HEX = (100000000).toString(16) -const MIN_GAS_PRICE_DEC = '100000000' -const MIN_GAS_LIMIT_DEC = '21000' -const MIN_GAS_LIMIT_HEX = (parseInt(MIN_GAS_LIMIT_DEC)).toString(16) - -const MIN_GAS_PRICE_GWEI = ethUtil.addHexPrefix(conversionUtil(MIN_GAS_PRICE_HEX, { - fromDenomination: 'WEI', - toDenomination: 'GWEI', - fromNumericBase: 'hex', - toNumericBase: 'hex', - numberOfDecimals: 1, -})) - -const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, { - toNumericBase: 'hex', - multiplicandBase: 16, - multiplierBase: 16, -}) - -const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb' - -module.exports = { - MIN_GAS_PRICE_GWEI, - MIN_GAS_PRICE_HEX, - MIN_GAS_PRICE_DEC, - MIN_GAS_LIMIT_HEX, - MIN_GAS_LIMIT_DEC, - MIN_GAS_TOTAL, - TOKEN_TRANSFER_FUNCTION_SIGNATURE, -} diff --git a/ui/app/components/send/send-utils.js b/ui/app/components/send/send-utils.js deleted file mode 100644 index 71bfb2668..000000000 --- a/ui/app/components/send/send-utils.js +++ /dev/null @@ -1,78 +0,0 @@ -const { - addCurrencies, - conversionUtil, - conversionGTE, - multiplyCurrencies, -} = require('../../conversion-util') -const { - calcTokenAmount, -} = require('../../token-util') - -function isBalanceSufficient ({ - amount = '0x0', - gasTotal = '0x0', - balance, - primaryCurrency, - amountConversionRate, - conversionRate, -}) { - const totalAmount = addCurrencies(amount, gasTotal, { - aBase: 16, - bBase: 16, - toNumericBase: 'hex', - }) - - const balanceIsSufficient = conversionGTE( - { - value: balance, - fromNumericBase: 'hex', - fromCurrency: primaryCurrency, - conversionRate, - }, - { - value: totalAmount, - fromNumericBase: 'hex', - conversionRate: amountConversionRate || conversionRate, - fromCurrency: primaryCurrency, - }, - ) - - return balanceIsSufficient -} - -function isTokenBalanceSufficient ({ - amount = '0x0', - tokenBalance, - decimals, -}) { - const amountInDec = conversionUtil(amount, { - fromNumericBase: 'hex', - }) - - const tokenBalanceIsSufficient = conversionGTE( - { - value: tokenBalance, - fromNumericBase: 'dec', - }, - { - value: calcTokenAmount(amountInDec, decimals), - fromNumericBase: 'dec', - }, - ) - - return tokenBalanceIsSufficient -} - -function getGasTotal (gasLimit, gasPrice) { - return multiplyCurrencies(gasLimit, gasPrice, { - toNumericBase: 'hex', - multiplicandBase: 16, - multiplierBase: 16, - }) -} - -module.exports = { - getGasTotal, - isBalanceSufficient, - isTokenBalanceSufficient, -} diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js deleted file mode 100644 index 6464439f2..000000000 --- a/ui/app/components/send/send-v2-container.js +++ /dev/null @@ -1,94 +0,0 @@ -const connect = require('react-redux').connect -const actions = require('../../actions') -const abi = require('ethereumjs-abi') -const SendEther = require('../../send-v2') -const { withRouter } = require('react-router-dom') -const { compose } = require('recompose') - -const { - accountsWithSendEtherInfoSelector, - getCurrentAccountWithSendEtherInfo, - conversionRateSelector, - getSelectedToken, - getSelectedAddress, - getAddressBook, - getSendFrom, - getCurrentCurrency, - getSelectedTokenToFiatRate, - getSelectedTokenContract, -} = require('../../selectors') - -import { - updateSendErrors, -} from '../../ducks/send' - -module.exports = compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(SendEther) - -function mapStateToProps (state) { - const fromAccounts = accountsWithSendEtherInfoSelector(state) - const selectedAddress = getSelectedAddress(state) - const selectedToken = getSelectedToken(state) - const conversionRate = conversionRateSelector(state) - - let data - let primaryCurrency - let tokenToFiatRate - if (selectedToken) { - data = Array.prototype.map.call( - abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']), - x => ('00' + x.toString(16)).slice(-2) - ).join('') - - primaryCurrency = selectedToken.symbol - - tokenToFiatRate = getSelectedTokenToFiatRate(state) - } - - return { - ...state.metamask.send, - from: getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state), - fromAccounts, - toAccounts: [...fromAccounts, ...getAddressBook(state)], - conversionRate, - selectedToken, - primaryCurrency, - convertedCurrency: getCurrentCurrency(state), - data, - selectedAddress, - amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate, - tokenContract: getSelectedTokenContract(state), - unapprovedTxs: state.metamask.unapprovedTxs, - network: state.metamask.network, - } -} - -function mapDispatchToProps (dispatch) { - return { - showCustomizeGasModal: () => dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })), - estimateGas: params => dispatch(actions.estimateGas(params)), - getGasPrice: () => dispatch(actions.getGasPrice()), - signTokenTx: (tokenAddress, toAddress, amount, txData) => ( - dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData)) - ), - signTx: txParams => dispatch(actions.signTx(txParams)), - updateAndApproveTx: txParams => dispatch(actions.updateAndApproveTx(txParams)), - updateTx: txData => dispatch(actions.updateTransaction(txData)), - setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)), - addToAddressBook: (address, nickname) => dispatch(actions.addToAddressBook(address, nickname)), - setGasTotal: newTotal => dispatch(actions.setGasTotal(newTotal)), - updateGasTotal: () => dispatch(actions.updateGasTotal()), - updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)), - updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), - updateSendTokenBalance: tokenBalance => dispatch(actions.updateSendTokenBalance(tokenBalance)), - updateSendFrom: newFrom => dispatch(actions.updateSendFrom(newFrom)), - updateSendTo: (newTo, nickname) => dispatch(actions.updateSendTo(newTo, nickname)), - updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), - updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)), - updateSendErrors: newError => dispatch(updateSendErrors(newError)), - clearSend: () => dispatch(actions.clearSend()), - setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)), - } -} diff --git a/ui/app/components/send/to-autocomplete.js b/ui/app/components/send/to-autocomplete.js index 5ea17f9a2..df74ef194 100644 --- a/ui/app/components/send/to-autocomplete.js +++ b/ui/app/components/send/to-autocomplete.js @@ -2,7 +2,7 @@ const Component = require('react').Component const PropTypes = require('prop-types') const h = require('react-hyperscript') const inherits = require('util').inherits -const AccountListItem = require('./account-list-item') +const AccountListItem = require('../send_/account-list-item/account-list-item.component').default const connect = require('react-redux').connect ToAutoComplete.contextTypes = { diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js deleted file mode 100644 index 16964b45d..000000000 --- a/ui/app/send-v2.js +++ /dev/null @@ -1,231 +0,0 @@ -const { inherits } = require('util') -const PropTypes = require('prop-types') -const PersistentForm = require('../lib/persistent-form') -const h = require('react-hyperscript') - -const { - conversionGreaterThan, -} = require('./conversion-util') -const { - calcTokenAmount, -} = require('./token-util') -const { - isBalanceSufficient, - isTokenBalanceSufficient, - getGasTotal, -} = require('./components/send/send-utils') - -import PageContainer from './components/page-container/page-container.component' -import SendHeader from './components/send_/send-header/send-header.container' -import SendContent from './components/send_/send-content/send-content.component' -import SendFooter from './components/send_/send-footer/send-footer.container' - -SendTransactionScreen.contextTypes = { - t: PropTypes.func, -} - -module.exports = SendTransactionScreen - -inherits(SendTransactionScreen, PersistentForm) -function SendTransactionScreen () { - PersistentForm.call(this) - - this.state = { - fromDropdownOpen: false, - toDropdownOpen: false, - errors: { - to: null, - amount: null, - }, - gasLoadingError: false, - } - - this.validateAmount = this.validateAmount.bind(this) -} - -const getParamsForGasEstimate = function (selectedAddress, symbol, data) { - const estimatedGasParams = { - from: selectedAddress, - gas: '746a528800', - } - - if (symbol) { - Object.assign(estimatedGasParams, { value: '0x0' }) - } - - if (data) { - Object.assign(estimatedGasParams, { data }) - } - - return estimatedGasParams -} - -SendTransactionScreen.prototype.updateSendTokenBalance = function (usersToken) { - if (!usersToken) return - - const { - selectedToken = {}, - updateSendTokenBalance, - } = this.props - const { decimals } = selectedToken || {} - const tokenBalance = calcTokenAmount(usersToken.balance.toString(), decimals) - - updateSendTokenBalance(tokenBalance) -} - -SendTransactionScreen.prototype.componentWillMount = function () { - this.updateGas() -} - -SendTransactionScreen.prototype.updateGas = function () { - const { - selectedToken = {}, - getGasPrice, - estimateGas, - selectedAddress, - data, - setGasTotal, - from, - tokenContract, - editingTransactionId, - gasPrice, - gasLimit, - } = this.props - - const { symbol } = selectedToken || {} - - const tokenBalancePromise = tokenContract - ? tokenContract.balanceOf(from.address) - : Promise.resolve() - tokenBalancePromise - .then(usersToken => this.updateSendTokenBalance(usersToken)) - - if (!editingTransactionId) { - const estimateGasParams = getParamsForGasEstimate(selectedAddress, symbol, data) - - Promise - .all([ - getGasPrice(), - estimateGas(estimateGasParams), - ]) - .then(([gasPrice, gas]) => { - const newGasTotal = getGasTotal(gas, gasPrice) - setGasTotal(newGasTotal) - this.setState({ gasLoadingError: false }) - }) - .catch(err => { - this.setState({ gasLoadingError: true }) - }) - } else { - const newGasTotal = getGasTotal(gasLimit, gasPrice) - setGasTotal(newGasTotal) - } -} - -SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { - const { - from: { balance }, - gasTotal, - tokenBalance, - amount, - selectedToken, - network, - } = this.props - - const { - from: { balance: prevBalance }, - gasTotal: prevGasTotal, - tokenBalance: prevTokenBalance, - network: prevNetwork, - } = prevProps - - const uninitialized = [prevBalance, prevGasTotal].every(n => n === null) - - const balanceHasChanged = balance !== prevBalance - const gasTotalHasChange = gasTotal !== prevGasTotal - const tokenBalanceHasChanged = selectedToken && tokenBalance !== prevTokenBalance - const amountValidationChange = balanceHasChanged || gasTotalHasChange || tokenBalanceHasChanged - - if (!uninitialized) { - if (amountValidationChange) { - this.validateAmount(amount) - } - - if (network !== prevNetwork && network !== 'loading') { - this.updateGas() - } - } -} - - -SendTransactionScreen.prototype.validateAmount = function (value) { - const { - from: { balance }, - updateSendErrors, - amountConversionRate, - conversionRate, - primaryCurrency, - selectedToken, - gasTotal, - tokenBalance, - } = this.props - const { decimals } = selectedToken || {} - const amount = value - - let amountError = null - - let sufficientBalance = true - - if (gasTotal) { - sufficientBalance = isBalanceSufficient({ - amount: selectedToken ? '0x0' : amount, - gasTotal, - balance, - primaryCurrency, - amountConversionRate, - conversionRate, - }) - } - - const verifyTokenBalance = selectedToken && tokenBalance !== null - let sufficientTokens - if (verifyTokenBalance) { - sufficientTokens = isTokenBalanceSufficient({ - tokenBalance, - amount, - decimals, - }) - } - - const amountLessThanZero = conversionGreaterThan( - { value: 0, fromNumericBase: 'dec' }, - { value: amount, fromNumericBase: 'hex' }, - ) - - if (conversionRate && !sufficientBalance) { - amountError = 'insufficientFunds' - } else if (verifyTokenBalance && !sufficientTokens) { - amountError = 'insufficientTokens' - } else if (amountLessThanZero) { - amountError = 'negativeETH' - } - - updateSendErrors({ amount: amountError }) -} - -SendTransactionScreen.prototype.render = function () { - const { history } = this.props - - return ( - - h(PageContainer, [ - - h(SendHeader), - - h(SendContent), - - h(SendFooter, { history }), - ]) - - ) -} -- cgit From 7c490098548522c16be1b1e84bce37f5bf87f1f4 Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 5 May 2018 11:11:53 -0400 Subject: Unit tests for containers, utils and selectors in send_/ --- ui/app/components/customize-gas-modal/index.js | 4 +- .../notification-modals/confirm-reset-account.js | 2 +- ui/app/components/pages/add-token.js | 8 +- .../pending-tx/confirm-deploy-contract.js | 2 +- ui/app/components/pending-tx/confirm-send-ether.js | 8 +- ui/app/components/pending-tx/confirm-send-token.js | 8 +- .../tests/account-list-item-container.test.js | 32 ++ .../tests/amount-max-button-container.test.js | 91 ++++ .../tests/amount-max-button-selectors.test.js | 22 + .../tests/amount-max-button-utils.test.js | 27 + .../send-amount-row/send-amount-row.utils.js | 0 .../tests/send-amount-row-container.test.js | 109 ++++ .../tests/send-amount-row-selectors.test.js | 34 ++ .../tests/send-amount-row-utils.test.js | 0 .../send-from-row/send-from-row.container.js | 4 +- .../send-from-row/send-from-row.utils.js | 12 - .../tests/send-from-row-container.test.js | 110 ++++ .../tests/send-from-row-selectors.test.js | 20 + .../send-gas-row/send-gas-row.selectors.js | 2 +- .../tests/send-gas-row-container.test.js | 66 +++ .../tests/send-gas-row-selectors.test.js | 22 + .../tests/send-row-error-message-container.test.js | 28 + .../tests/send-to-row-container.test.js | 114 ++++ .../tests/send-to-row-selectors.test.js | 47 ++ .../send-to-row/tests/send-to-row-utils.test.js | 45 ++ .../send_/send-footer/send-footer.container.js | 4 +- .../send_/send-footer/send-footer.selectors.js | 2 +- .../send_/send-footer/send-footer.utils.js | 10 +- .../tests/send-footer-container.test.js | 202 ++++++++ .../tests/send-footer-selectors.test.js | 0 .../send-footer/tests/send-footer-utils.test.js | 242 +++++++++ .../tests/send-header-container.test.js | 55 ++ ui/app/components/send_/send.container.js | 4 +- ui/app/components/send_/send.selectors.js | 44 +- ui/app/components/send_/send.utils.js | 8 +- .../components/send_/tests/send-container.test.js | 150 ++++++ .../send_/tests/send-selectors-test-data.js | 191 +++++++ .../components/send_/tests/send-selectors.test.js | 572 +++++++++++++++++++++ ui/app/components/send_/tests/send-utils.test.js | 264 ++++++++++ ui/app/components/tx-list-item.js | 4 +- ui/app/i18n-provider.js | 4 +- ui/app/main-container.js | 2 +- ui/app/metamask-connect.js | 2 +- ui/app/util.js | 2 +- 44 files changed, 2506 insertions(+), 73 deletions(-) create mode 100644 ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js delete mode 100644 ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js delete mode 100644 ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-utils.test.js delete mode 100644 ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js create mode 100644 ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js delete mode 100644 ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js create mode 100644 ui/app/components/send_/tests/send-selectors-test-data.js (limited to 'ui') diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 0761faa99..1da3f7f61 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -16,11 +16,11 @@ const { MIN_GAS_PRICE_DEC, MIN_GAS_LIMIT_DEC, MIN_GAS_PRICE_GWEI, -} = require('../send/send-constants') +} = require('../send_/send.constants') const { isBalanceSufficient, -} = require('../send/send-utils') +} = require('../send_/send.utils') const { conversionUtil, diff --git a/ui/app/components/modals/notification-modals/confirm-reset-account.js b/ui/app/components/modals/notification-modals/confirm-reset-account.js index 89fa9bef1..f1a605498 100644 --- a/ui/app/components/modals/notification-modals/confirm-reset-account.js +++ b/ui/app/components/modals/notification-modals/confirm-reset-account.js @@ -26,7 +26,7 @@ class ConfirmResetAccount extends Component { showCancelButton: true, showConfirmButton: true, onConfirm: resetAccount, - + }) } } diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js index 566e42450..1e7fc3a41 100644 --- a/ui/app/components/pages/add-token.js +++ b/ui/app/components/pages/add-token.js @@ -149,10 +149,10 @@ AddTokenScreen.prototype.validate = function () { errors.customAddress = this.context.t('invalidAddress') } - const validDecimals = customDecimals !== null - && customDecimals !== '' - && customDecimals >= 0 - && customDecimals < 36 + const validDecimals = customDecimals !== null && + customDecimals !== '' && + customDecimals >= 0 && + customDecimals < 36 if (!validDecimals) { errors.customDecimals = this.context.t('decimalsMustZerotoTen') } diff --git a/ui/app/components/pending-tx/confirm-deploy-contract.js b/ui/app/components/pending-tx/confirm-deploy-contract.js index aa68a9eb0..af3a14f57 100644 --- a/ui/app/components/pending-tx/confirm-deploy-contract.js +++ b/ui/app/components/pending-tx/confirm-deploy-contract.js @@ -11,7 +11,7 @@ const { conversionUtil } = require('../../conversion-util') const SenderToRecipient = require('../sender-to-recipient') const NetworkDisplay = require('../network-display') -const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') +const { MIN_GAS_PRICE_HEX } = require('../send_/send.constants') class ConfirmDeployContract extends Component { constructor (props) { diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index ac1b15a4f..a450f9081 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -17,16 +17,16 @@ const { multiplyCurrencies, } = require('../../conversion-util') const { - getGasTotal, + calcGasTotal, isBalanceSufficient, -} = require('../send/send-utils') +} = require('../send_/send.utils') const GasFeeDisplay = require('../send/gas-fee-display-v2') const SenderToRecipient = require('../sender-to-recipient') const NetworkDisplay = require('../network-display') const currencyFormatter = require('currency-formatter') const currencies = require('currency-formatter/currencies') -const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') +const { MIN_GAS_PRICE_HEX } = require('../send_/send.constants') const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') import { @@ -590,7 +590,7 @@ ConfirmSendEther.prototype.isBalanceSufficient = function (txMeta) { value: amount, }, } = txMeta - const gasTotal = getGasTotal(gas, gasPrice) + const gasTotal = calcGasTotal(gas, gasPrice) return isBalanceSufficient({ amount, diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index e1abc6d3d..135cb2275 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -20,9 +20,9 @@ const { addCurrencies, } = require('../../conversion-util') const { - getGasTotal, + calcGasTotal, isBalanceSufficient, -} = require('../send/send-utils') +} = require('../send_/send.utils') const { calcTokenAmount, } = require('../../token-util') @@ -30,7 +30,7 @@ const classnames = require('classnames') const currencyFormatter = require('currency-formatter') const currencies = require('currency-formatter/currencies') -const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') +const { MIN_GAS_PRICE_HEX } = require('../send_/send.constants') const { getTokenExchangeRate, @@ -580,7 +580,7 @@ ConfirmSendToken.prototype.isBalanceSufficient = function (txMeta) { gasPrice, }, } = txMeta - const gasTotal = getGasTotal(gas, gasPrice) + const gasTotal = calcGasTotal(gas, gasPrice) return isBalanceSufficient({ amount: '0', diff --git a/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js b/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js new file mode 100644 index 000000000..49da920e6 --- /dev/null +++ b/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js @@ -0,0 +1,32 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' + +let mapStateToProps + +proxyquire('../account-list-item.container.js', { + 'react-redux': { + connect: (ms, md) => { + mapStateToProps = ms + return () => ({}) + }, + }, + '../send.selectors.js': { + getConversionRate: (s) => `mockConversionRate:${s}`, + getConvertedCurrency: (s) => `mockCurrentCurrency:${s}`, + }, +}) + +describe('account-list-item container', () => { + + describe('mapStateToProps()', () => { + + it('should map the correct properties to props', () => { + assert.deepEqual(mapStateToProps('mockState'), { + conversionRate: 'mockConversionRate:mockState', + currentCurrency: 'mockCurrentCurrency:mockState', + }) + }) + + }) + +}) diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js index e69de29bb..1aa0ad8fb 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js @@ -0,0 +1,91 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +let mapStateToProps +let mapDispatchToProps + +const actionSpies = { + setMaxModeTo: sinon.spy(), + updateSendAmount: sinon.spy(), +} +const duckActionSpies = { + updateSendErrors: sinon.spy(), +} + +proxyquire('../amount-max-button.container.js', { + 'react-redux': { + connect: (ms, md) => { + mapStateToProps = ms + mapDispatchToProps = md + return () => ({}) + }, + }, + '../../../send.selectors.js': { + getGasTotal: (s) => `mockGasTotal:${s}`, + getSelectedToken: (s) => `mockSelectedToken:${s}`, + getSendFromBalance: (s) => `mockBalance:${s}`, + getTokenBalance: (s) => `mockTokenBalance:${s}`, + }, + './amount-max-button.selectors.js': { getMaxModeOn: (s) => `mockMaxModeOn:${s}` }, + './amount-max-button.utils.js': { calcMaxAmount: (mockObj) => mockObj.val + 1 }, + '../../../../../actions': actionSpies, + '../../../../../ducks/send': duckActionSpies, +}) + +describe('amount-max-button container', () => { + + describe('mapStateToProps()', () => { + + it('should map the correct properties to props', () => { + assert.deepEqual(mapStateToProps('mockState'), { + balance: 'mockBalance:mockState', + gasTotal: 'mockGasTotal:mockState', + maxModeOn: 'mockMaxModeOn:mockState', + selectedToken: 'mockSelectedToken:mockState', + tokenBalance: 'mockTokenBalance:mockState', + }) + }) + + }) + + describe('mapDispatchToProps()', () => { + let dispatchSpy + let mapDispatchToPropsObject + + beforeEach(() => { + dispatchSpy = sinon.spy() + mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) + }) + + describe('setAmountToMax()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.setAmountToMax({ val: 11, foo: 'bar' }) + assert(dispatchSpy.calledTwice) + assert(duckActionSpies.updateSendErrors.calledOnce) + assert.deepEqual( + duckActionSpies.updateSendErrors.getCall(0).args[0], + { amount: null } + ) + assert(actionSpies.updateSendAmount.calledOnce) + assert.equal( + actionSpies.updateSendAmount.getCall(0).args[0], + 12 + ) + }) + }) + + describe('setMaxModeTo()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.setMaxModeTo('mockVal') + assert(dispatchSpy.calledOnce) + assert.equal( + actionSpies.setMaxModeTo.getCall(0).args[0], + 'mockVal' + ) + }) + }) + + }) + +}) diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js index e69de29bb..655fe1969 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js @@ -0,0 +1,22 @@ +import assert from 'assert' +import { + getMaxModeOn, +} from '../amount-max-button.selectors.js' + +describe('amount-max-button selectors', () => { + + describe('getMaxModeOn()', () => { + it('should', () => { + const state = { + metamask: { + send: { + maxModeOn: null, + }, + }, + } + + assert.equal(getMaxModeOn(state), null) + }) + }) + +}) diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js index e69de29bb..816df6a12 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js @@ -0,0 +1,27 @@ +import assert from 'assert' +import { + calcMaxAmount, +} from '../amount-max-button.utils.js' + +describe('amount-max-button utils', () => { + + describe('calcMaxAmount()', () => { + it('should calculate the correct amount when no selectedToken defined', () => { + assert.deepEqual(calcMaxAmount({ + balance: 'ffffff', + gasTotal: 'ff', + selectedToken: false, + }), 'ffff00') + }) + + it('should calculate the correct amount when a selectedToken is defined', () => { + assert.deepEqual(calcMaxAmount({ + selectedToken: { + decimals: 10, + }, + tokenBalance: 100, + }), 'e8d4a51000') + }) + }) + +}) diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js index e69de29bb..0678ec38f 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js @@ -0,0 +1,109 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +let mapStateToProps +let mapDispatchToProps + +const actionSpies = { + setMaxModeTo: sinon.spy(), + updateSendAmount: sinon.spy(), +} +const duckActionSpies = { + updateSendErrors: sinon.spy(), +} + +proxyquire('../send-amount-row.container.js', { + 'react-redux': { + connect: (ms, md) => { + mapStateToProps = ms + mapDispatchToProps = md + return () => ({}) + }, + }, + '../../send.selectors': { + getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`, + getConversionRate: (s) => `mockConversionRate:${s}`, + getConvertedCurrency: (s) => `mockConvertedCurrency:${s}`, + getGasTotal: (s) => `mockGasTotal:${s}`, + getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`, + getSelectedToken: (s) => `mockSelectedToken:${s}`, + getSendAmount: (s) => `mockAmount:${s}`, + getSendFromBalance: (s) => `mockBalance:${s}`, + getTokenBalance: (s) => `mockTokenBalance:${s}`, + }, + './send-amount-row.selectors': { sendAmountIsInError: (s) => `mockInError:${s}` }, + '../../send.utils': { getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }) }, + '../../../../actions': actionSpies, + '../../../../ducks/send': duckActionSpies, +}) + +describe('send-amount-row container', () => { + + describe('mapStateToProps()', () => { + + it('should map the correct properties to props', () => { + assert.deepEqual(mapStateToProps('mockState'), { + amount: 'mockAmount:mockState', + amountConversionRate: 'mockAmountConversionRate:mockState', + balance: 'mockBalance:mockState', + conversionRate: 'mockConversionRate:mockState', + convertedCurrency: 'mockConvertedCurrency:mockState', + gasTotal: 'mockGasTotal:mockState', + inError: 'mockInError:mockState', + primaryCurrency: 'mockPrimaryCurrency:mockState', + selectedToken: 'mockSelectedToken:mockState', + tokenBalance: 'mockTokenBalance:mockState', + }) + }) + + }) + + describe('mapDispatchToProps()', () => { + let dispatchSpy + let mapDispatchToPropsObject + + beforeEach(() => { + dispatchSpy = sinon.spy() + mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) + }) + + describe('setMaxModeTo()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.setMaxModeTo('mockBool') + assert(dispatchSpy.calledOnce) + assert(actionSpies.setMaxModeTo.calledOnce) + assert.equal( + actionSpies.setMaxModeTo.getCall(0).args[0], + 'mockBool' + ) + }) + }) + + describe('updateSendAmount()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.updateSendAmount('mockAmount') + assert(dispatchSpy.calledOnce) + assert(actionSpies.updateSendAmount.calledOnce) + assert.equal( + actionSpies.updateSendAmount.getCall(0).args[0], + 'mockAmount' + ) + }) + }) + + describe('updateSendAmountError()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.updateSendAmountError({ some: 'data' }) + assert(dispatchSpy.calledOnce) + assert(duckActionSpies.updateSendErrors.calledOnce) + assert.deepEqual( + duckActionSpies.updateSendErrors.getCall(0).args[0], + { some: 'data', mockChange: true } + ) + }) + }) + + }) + +}) diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-selectors.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-selectors.test.js index e69de29bb..4672cb8a7 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-selectors.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-selectors.test.js @@ -0,0 +1,34 @@ +import assert from 'assert' +import { + sendAmountIsInError, +} from '../send-amount-row.selectors.js' + +describe('send-amount-row selectors', () => { + + describe('sendAmountIsInError()', () => { + it('should return true if send.errors.amount is truthy', () => { + const state = { + send: { + errors: { + amount: 'abc', + }, + }, + } + + assert.equal(sendAmountIsInError(state), true) + }) + + it('should return false if send.errors.amount is falsy', () => { + const state = { + send: { + errors: { + amount: null, + }, + }, + } + + assert.equal(sendAmountIsInError(state), false) + }) + }) + +}) diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-utils.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-utils.test.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js index 9e366445d..377aead0a 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js @@ -8,7 +8,7 @@ import { import { getFromDropdownOpen, } from './send-from-row.selectors.js' -import { calcTokenUpdateAmount } from './send-from-row.utils.js' +import { calcTokenBalance } from '../../send.utils.js' import { updateSendFrom, setSendTokenBalance, @@ -39,7 +39,7 @@ function mapDispatchToProps (dispatch) { setSendTokenBalance: (usersToken, selectedToken) => { if (!usersToken) return - const tokenBalance = calcTokenUpdateAmount(selectedToken, selectedToken) + const tokenBalance = calcTokenBalance(usersToken, selectedToken) dispatch(setSendTokenBalance(tokenBalance)) }, } diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js deleted file mode 100644 index 0aaaef793..000000000 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js +++ /dev/null @@ -1,12 +0,0 @@ -const { - calcTokenAmount, -} = require('../../../../token-util') - -function calcTokenUpdateAmount (usersToken, selectedToken) { - const { decimals } = selectedToken || {} - return calcTokenAmount(usersToken.balance.toString(), decimals) -} - -module.exports = { - calcTokenUpdateAmount, -} diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js index e69de29bb..70ab963ab 100644 --- a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js +++ b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js @@ -0,0 +1,110 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +let mapStateToProps +let mapDispatchToProps + +const actionSpies = { + updateSendFrom: sinon.spy(), + setSendTokenBalance: sinon.spy(), +} +const duckActionSpies = { + closeFromDropdown: sinon.spy(), + openFromDropdown: sinon.spy(), +} + +proxyquire('../send-from-row.container.js', { + 'react-redux': { + connect: (ms, md) => { + mapStateToProps = ms + mapDispatchToProps = md + return () => ({}) + }, + }, + '../../send.selectors.js': { + accountsWithSendEtherInfoSelector: (s) => `mockFromAccounts:${s}`, + getConversionRate: (s) => `mockConversionRate:${s}`, + getSelectedTokenContract: (s) => `mockTokenContract:${s}`, + getSendFromObject: (s) => `mockFrom:${s}`, + }, + './send-from-row.selectors.js': { getFromDropdownOpen: (s) => `mockFromDropdownOpen:${s}` }, + '../../send.utils.js': { calcTokenBalance: (a, b) => a + b }, + '../../../../actions': actionSpies, + '../../../../ducks/send': duckActionSpies, +}) + +describe('send-from-row container', () => { + + describe('mapStateToProps()', () => { + + it('should map the correct properties to props', () => { + assert.deepEqual(mapStateToProps('mockState'), { + conversionRate: 'mockConversionRate:mockState', + from: 'mockFrom:mockState', + fromAccounts: 'mockFromAccounts:mockState', + fromDropdownOpen: 'mockFromDropdownOpen:mockState', + tokenContract: 'mockTokenContract:mockState', + }) + }) + + }) + + describe('mapDispatchToProps()', () => { + let dispatchSpy + let mapDispatchToPropsObject + + beforeEach(() => { + dispatchSpy = sinon.spy() + mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) + }) + + describe('closeFromDropdown()', () => { + it('should dispatch a closeFromDropdown action', () => { + mapDispatchToPropsObject.closeFromDropdown() + assert(dispatchSpy.calledOnce) + assert(duckActionSpies.closeFromDropdown.calledOnce) + assert.equal( + duckActionSpies.closeFromDropdown.getCall(0).args[0], + undefined + ) + }) + }) + + describe('openFromDropdown()', () => { + it('should dispatch a openFromDropdown action', () => { + mapDispatchToPropsObject.openFromDropdown() + assert(dispatchSpy.calledOnce) + assert(duckActionSpies.openFromDropdown.calledOnce) + assert.equal( + duckActionSpies.openFromDropdown.getCall(0).args[0], + undefined + ) + }) + }) + + describe('updateSendFrom()', () => { + it('should dispatch an updateSendFrom action', () => { + mapDispatchToPropsObject.updateSendFrom('mockFrom') + assert(dispatchSpy.calledOnce) + assert.equal( + actionSpies.updateSendFrom.getCall(0).args[0], + 'mockFrom' + ) + }) + }) + + describe('setSendTokenBalance()', () => { + it('should dispatch an setSendTokenBalance action', () => { + mapDispatchToPropsObject.setSendTokenBalance('mockUsersToken', 'mockSelectedToken') + assert(dispatchSpy.calledOnce) + assert.equal( + actionSpies.setSendTokenBalance.getCall(0).args[0], + 'mockUsersTokenmockSelectedToken' + ) + }) + }) + + }) + +}) diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-selectors.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-selectors.test.js index e69de29bb..ecb57bbc3 100644 --- a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-selectors.test.js +++ b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-selectors.test.js @@ -0,0 +1,20 @@ +import assert from 'assert' +import { + getFromDropdownOpen, +} from '../send-from-row.selectors.js' + +describe('send-from-row selectors', () => { + + describe('getFromDropdownOpen()', () => { + it('should get send.fromDropdownOpen', () => { + const state = { + send: { + fromDropdownOpen: null, + }, + } + + assert.equal(getFromDropdownOpen(state), null) + }) + }) + +}) diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js index ad4ef4877..d069ae8c6 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js @@ -5,5 +5,5 @@ const selectors = { module.exports = selectors function sendGasIsInError (state) { - return state.metamask.send.errors.gasLoading + return state.send.errors.gasLoading } diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js index e69de29bb..9135524d1 100644 --- a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js +++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js @@ -0,0 +1,66 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +let mapStateToProps +let mapDispatchToProps + +const actionSpies = { + showModal: sinon.spy(), +} + +proxyquire('../send-gas-row.container.js', { + 'react-redux': { + connect: (ms, md) => { + mapStateToProps = ms + mapDispatchToProps = md + return () => ({}) + }, + }, + '../../send.selectors.js': { + getConversionRate: (s) => `mockConversionRate:${s}`, + getConvertedCurrency: (s) => `mockConvertedCurrency:${s}`, + getGasTotal: (s) => `mockGasTotal:${s}`, + }, + './send-gas-row.selectors.js': { sendGasIsInError: (s) => `mockGasLoadingError:${s}` }, + '../../../../actions': actionSpies, +}) + +describe('send-gas-row container', () => { + + describe('mapStateToProps()', () => { + + it('should map the correct properties to props', () => { + assert.deepEqual(mapStateToProps('mockState'), { + conversionRate: 'mockConversionRate:mockState', + convertedCurrency: 'mockConvertedCurrency:mockState', + gasTotal: 'mockGasTotal:mockState', + gasLoadingError: 'mockGasLoadingError:mockState', + }) + }) + + }) + + describe('mapDispatchToProps()', () => { + let dispatchSpy + let mapDispatchToPropsObject + + beforeEach(() => { + dispatchSpy = sinon.spy() + mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) + }) + + describe('showCustomizeGasModal()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.showCustomizeGasModal() + assert(dispatchSpy.calledOnce) + assert.deepEqual( + actionSpies.showModal.getCall(0).args[0], + { name: 'CUSTOMIZE_GAS' } + ) + }) + }) + + }) + +}) diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js index e69de29bb..a5196334e 100644 --- a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js +++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js @@ -0,0 +1,22 @@ +import assert from 'assert' +import { + sendGasIsInError, +} from '../send-gas-row.selectors.js' + +describe('send-gas-row selectors', () => { + + describe('sendGasIsInError()', () => { + it('should return send.errors.gasLoading', () => { + const state = { + send: { + errors: { + gasLoading: 'abc', + }, + }, + } + + assert.equal(sendGasIsInError(state), 'abc') + }) + }) + +}) diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js new file mode 100644 index 000000000..eecff165d --- /dev/null +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js @@ -0,0 +1,28 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' + +let mapStateToProps + +proxyquire('../send-row-error-message.container.js', { + 'react-redux': { + connect: (ms, md) => { + mapStateToProps = ms + return () => ({}) + }, + }, + '../../../send.selectors': { getSendErrors: (s) => `mockErrors:${s}` }, +}) + +describe('send-row-error-message container', () => { + + describe('mapStateToProps()', () => { + + it('should map the correct properties to props', () => { + assert.deepEqual(mapStateToProps('mockState', { errorType: 'someType' }), { + errors: 'mockErrors:mockState', + errorType: 'someType' }) + }) + + }) + +}) diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js index e69de29bb..3415e7afa 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js @@ -0,0 +1,114 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +let mapStateToProps +let mapDispatchToProps + +const actionSpies = { + updateSendTo: sinon.spy(), +} +const duckActionSpies = { + closeToDropdown: sinon.spy(), + openToDropdown: sinon.spy(), + updateSendErrors: sinon.spy(), +} + +proxyquire('../send-to-row.container.js', { + 'react-redux': { + connect: (ms, md) => { + mapStateToProps = ms + mapDispatchToProps = md + return () => ({}) + }, + }, + '../../send.selectors.js': { + getCurrentNetwork: (s) => `mockNetwork:${s}`, + getSendTo: (s) => `mockTo:${s}`, + getSendToAccounts: (s) => `mockToAccounts:${s}`, + }, + './send-to-row.selectors.js': { + getToDropdownOpen: (s) => `mockToDropdownOpen:${s}`, + sendToIsInError: (s) => `mockInError:${s}`, + }, + './send-to-row.utils.js': { getToErrorObject: (t) => `mockError:${t}` }, + '../../../../actions': actionSpies, + '../../../../ducks/send': duckActionSpies, +}) + +describe('send-to-row container', () => { + + describe('mapStateToProps()', () => { + + it('should map the correct properties to props', () => { + assert.deepEqual(mapStateToProps('mockState'), { + inError: 'mockInError:mockState', + network: 'mockNetwork:mockState', + to: 'mockTo:mockState', + toAccounts: 'mockToAccounts:mockState', + toDropdownOpen: 'mockToDropdownOpen:mockState', + }) + }) + + }) + + describe('mapDispatchToProps()', () => { + let dispatchSpy + let mapDispatchToPropsObject + + beforeEach(() => { + dispatchSpy = sinon.spy() + mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) + }) + + describe('closeToDropdown()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.closeToDropdown() + assert(dispatchSpy.calledOnce) + assert(duckActionSpies.closeToDropdown.calledOnce) + assert.equal( + duckActionSpies.closeToDropdown.getCall(0).args[0], + undefined + ) + }) + }) + + describe('openToDropdown()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.openToDropdown() + assert(dispatchSpy.calledOnce) + assert(duckActionSpies.openToDropdown.calledOnce) + assert.equal( + duckActionSpies.openToDropdown.getCall(0).args[0], + undefined + ) + }) + }) + + describe('updateSendTo()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.updateSendTo('mockTo', 'mockNickname') + assert(dispatchSpy.calledOnce) + assert(actionSpies.updateSendTo.calledOnce) + assert.deepEqual( + actionSpies.updateSendTo.getCall(0).args, + ['mockTo', 'mockNickname'] + ) + }) + }) + + describe('updateSendToError()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.updateSendToError('mockTo') + assert(dispatchSpy.calledOnce) + assert(duckActionSpies.updateSendErrors.calledOnce) + assert.equal( + duckActionSpies.updateSendErrors.getCall(0).args[0], + 'mockError:mockTo' + ) + }) + }) + + }) + +}) diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-selectors.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-selectors.test.js index e69de29bb..122ad3265 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-selectors.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-selectors.test.js @@ -0,0 +1,47 @@ +import assert from 'assert' +import { + getToDropdownOpen, + sendToIsInError, +} from '../send-to-row.selectors.js' + +describe('send-to-row selectors', () => { + + describe('getToDropdownOpen()', () => { + it('should return send.getToDropdownOpen', () => { + const state = { + send: { + toDropdownOpen: false, + }, + } + + assert.equal(getToDropdownOpen(state), false) + }) + }) + + describe('sendToIsInError()', () => { + it('should return true if send.errors.to is truthy', () => { + const state = { + send: { + errors: { + to: 'abc', + }, + }, + } + + assert.equal(sendToIsInError(state), true) + }) + + it('should return false if send.errors.to is falsy', () => { + const state = { + send: { + errors: { + to: null, + }, + }, + } + + assert.equal(sendToIsInError(state), false) + }) + }) + +}) diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js new file mode 100644 index 000000000..615c9581b --- /dev/null +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js @@ -0,0 +1,45 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +import { + REQUIRED_ERROR, + INVALID_RECIPIENT_ADDRESS_ERROR, +} from '../../../send.constants' + +const stubs = { + isValidAddress: sinon.stub().callsFake(to => Boolean(to.match(/^[0xabcdef123456798]+$/))), +} + +const toRowUtils = proxyquire('../send-to-row.utils.js', { + '../../../../util': { + isValidAddress: stubs.isValidAddress, + }, +}) +const { + getToErrorObject, +} = toRowUtils + +describe('send-to-row utils', () => { + + describe('getToErrorObject()', () => { + it('should return a required error if to is falsy', () => { + assert.deepEqual(getToErrorObject(null), { + to: REQUIRED_ERROR, + }) + }) + + it('should return an invalid recipient error if to is truthy but invalid', () => { + assert.deepEqual(getToErrorObject('mockInvalidTo'), { + to: INVALID_RECIPIENT_ADDRESS_ERROR, + }) + }) + + it('should return null if to is truthy and valid', () => { + assert.deepEqual(getToErrorObject('0xabc123'), { + to: null, + }) + }) + }) + +}) diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js index 022b2db08..0242dfa54 100644 --- a/ui/app/components/send_/send-footer/send-footer.container.js +++ b/ui/app/components/send_/send-footer/send-footer.container.js @@ -20,10 +20,8 @@ import { getSendToAccounts, getTokenBalance, getUnapprovedTxs, -} from '../send.selectors' -import { isSendFormInError, -} from './send-footer.selectors' +} from '../send.selectors' import { addressIsNew, constructTxParams, diff --git a/ui/app/components/send_/send-footer/send-footer.selectors.js b/ui/app/components/send_/send-footer/send-footer.selectors.js index e8fef6be6..15a053ae0 100644 --- a/ui/app/components/send_/send-footer/send-footer.selectors.js +++ b/ui/app/components/send_/send-footer/send-footer.selectors.js @@ -1,4 +1,4 @@ -import { getSendErrors } from '../send.selectors' +const { getSendErrors } = require('../send.selectors') const selectors = { isSendFormInError, diff --git a/ui/app/components/send_/send-footer/send-footer.utils.js b/ui/app/components/send_/send-footer/send-footer.utils.js index 353c0e347..149d9e357 100644 --- a/ui/app/components/send_/send-footer/send-footer.utils.js +++ b/ui/app/components/send_/send-footer/send-footer.utils.js @@ -1,6 +1,6 @@ -import ethAbi from 'ethereumjs-abi' -import ethUtil from 'ethereumjs-util' -import { TOKEN_TRANSFER_FUNCTION_SIGNATURE } from '../send.constants' +const ethAbi = require('ethereumjs-abi') +const ethUtil = require('ethereumjs-util') +const { TOKEN_TRANSFER_FUNCTION_SIGNATURE } = require('../send.constants') function formShouldBeDisabled ({ inError, selectedToken, tokenBalance, gasTotal }) { const missingTokenBalance = selectedToken && !tokenBalance @@ -47,6 +47,7 @@ function constructUpdatedTx ({ } if (selectedToken) { + console.log(`ethAbi.rawEncode`, ethAbi.rawEncode) const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call( ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]), x => ('00' + x.toString(16)).slice(-2) @@ -70,6 +71,8 @@ function constructUpdatedTx ({ delete editingTx.txParams.data } } + + return editingTx } function addressIsNew (toAccounts, newAddress) { @@ -81,4 +84,5 @@ module.exports = { formShouldBeDisabled, constructTxParams, constructUpdatedTx, + addHexPrefixToObjectValues, } diff --git a/ui/app/components/send_/send-footer/tests/send-footer-container.test.js b/ui/app/components/send_/send-footer/tests/send-footer-container.test.js index e69de29bb..e9d4eb04d 100644 --- a/ui/app/components/send_/send-footer/tests/send-footer-container.test.js +++ b/ui/app/components/send_/send-footer/tests/send-footer-container.test.js @@ -0,0 +1,202 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +let mapStateToProps +let mapDispatchToProps + +const actionSpies = { + addToAddressBook: sinon.spy(), + clearSend: sinon.spy(), + signTokenTx: sinon.spy(), + signTx: sinon.spy(), + updateTransaction: sinon.spy(), +} +const utilsStubs = { + addressIsNew: sinon.stub().returns(true), + constructTxParams: sinon.stub().returns('mockConstructedTxParams'), + constructUpdatedTx: sinon.stub().returns('mockConstructedUpdatedTxParams'), + formShouldBeDisabled: sinon.stub().returns('mockFormShouldBeDisabled'), +} + +proxyquire('../send-footer.container.js', { + 'react-redux': { + connect: (ms, md) => { + mapStateToProps = ms + mapDispatchToProps = md + return () => ({}) + }, + }, + '../../../actions': actionSpies, + '../send.selectors': { + getGasLimit: (s) => `mockGasLimit:${s}`, + getGasPrice: (s) => `mockGasPrice:${s}`, + getGasTotal: (s) => `mockGasTotal:${s}`, + getSelectedToken: (s) => `mockSelectedToken:${s}`, + getSendAmount: (s) => `mockAmount:${s}`, + getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`, + getSendFromObject: (s) => `mockFromObject:${s}`, + getSendTo: (s) => `mockTo:${s}`, + getSendToAccounts: (s) => `mockToAccounts:${s}`, + getTokenBalance: (s) => `mockTokenBalance:${s}`, + getUnapprovedTxs: (s) => `mockUnapprovedTxs:${s}`, + isSendFormInError: (s) => `mockInError:${s}`, + }, + './send-footer.selectors': { isSendFormInError: () => {} }, + './send-footer.utils': utilsStubs, +}) + +describe('send-footer container', () => { + + describe('mapStateToProps()', () => { + + it('should map the correct properties to props', () => { + assert.deepEqual(mapStateToProps('mockState'), { + amount: 'mockAmount:mockState', + disabled: 'mockFormShouldBeDisabled', + selectedToken: 'mockSelectedToken:mockState', + editingTransactionId: 'mockEditingTransactionId:mockState', + from: 'mockFromObject:mockState', + gasLimit: 'mockGasLimit:mockState', + gasPrice: 'mockGasPrice:mockState', + inError: 'mockInError:mockState', + isToken: true, + to: 'mockTo:mockState', + toAccounts: 'mockToAccounts:mockState', + unapprovedTxs: 'mockUnapprovedTxs:mockState', + }) + assert.deepEqual( + utilsStubs.formShouldBeDisabled.getCall(0).args[0], + { + inError: 'mockInError:mockState', + selectedToken: 'mockSelectedToken:mockState', + tokenBalance: 'mockTokenBalance:mockState', + gasTotal: 'mockGasTotal:mockState', + } + ) + }) + + }) + + describe('mapDispatchToProps()', () => { + let dispatchSpy + let mapDispatchToPropsObject + + beforeEach(() => { + dispatchSpy = sinon.spy() + mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) + }) + + describe('clearSend()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.clearSend() + assert(dispatchSpy.calledOnce) + assert(actionSpies.clearSend.calledOnce) + }) + }) + + describe('sign()', () => { + it('should dispatch a signTokenTx action if selectedToken is defined', () => { + mapDispatchToPropsObject.sign({ + selectedToken: { + address: '0xabc', + }, + to: 'mockTo', + amount: 'mockAmount', + from: 'mockFrom', + gas: 'mockGas', + gasPrice: 'mockGasPrice', + }) + assert(dispatchSpy.calledOnce) + assert.deepEqual( + utilsStubs.constructTxParams.getCall(0).args[0], + { + selectedToken: { + address: '0xabc', + }, + to: 'mockTo', + amount: 'mockAmount', + from: 'mockFrom', + gas: 'mockGas', + gasPrice: 'mockGasPrice', + } + ) + assert.deepEqual( + actionSpies.signTokenTx.getCall(0).args, + [ '0xabc', 'mockTo', 'mockAmount', 'mockConstructedTxParams' ] + ) + }) + + it('should dispatch a sign action if selectedToken is not defined', () => { + utilsStubs.constructTxParams.resetHistory() + mapDispatchToPropsObject.sign({ + to: 'mockTo', + amount: 'mockAmount', + from: 'mockFrom', + gas: 'mockGas', + gasPrice: 'mockGasPrice', + }) + assert(dispatchSpy.calledOnce) + assert.deepEqual( + utilsStubs.constructTxParams.getCall(0).args[0], + { + selectedToken: undefined, + to: 'mockTo', + amount: 'mockAmount', + from: 'mockFrom', + gas: 'mockGas', + gasPrice: 'mockGasPrice', + } + ) + assert.deepEqual( + actionSpies.signTx.getCall(0).args, + [ 'mockConstructedTxParams' ] + ) + }) + }) + + describe('update()', () => { + it('should dispatch an updateTransaction action', () => { + mapDispatchToPropsObject.update({ + to: 'mockTo', + amount: 'mockAmount', + from: 'mockFrom', + gas: 'mockGas', + gasPrice: 'mockGasPrice', + editingTransactionId: 'mockEditingTransactionId', + selectedToken: 'mockSelectedToken', + unapprovedTxs: 'mockUnapprovedTxs', + }) + assert(dispatchSpy.calledOnce) + assert.deepEqual( + utilsStubs.constructUpdatedTx.getCall(0).args[0], + { + to: 'mockTo', + amount: 'mockAmount', + from: 'mockFrom', + gas: 'mockGas', + gasPrice: 'mockGasPrice', + editingTransactionId: 'mockEditingTransactionId', + selectedToken: 'mockSelectedToken', + unapprovedTxs: 'mockUnapprovedTxs', + } + ) + assert.equal(actionSpies.updateTransaction.getCall(0).args[0], 'mockConstructedUpdatedTxParams') + }) + }) + + describe('addToAddressBookIfNew()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.addToAddressBookIfNew('mockNewAddress', 'mockToAccounts', 'mockNickname') + assert(dispatchSpy.calledOnce) + assert.equal(utilsStubs.addressIsNew.getCall(0).args[0], 'mockToAccounts') + assert.deepEqual( + actionSpies.addToAddressBook.getCall(0).args, + [ '0xmockNewAddress', 'mockNickname' ] + ) + }) + }) + + }) + +}) diff --git a/ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js b/ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js b/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js index e69de29bb..b235ea5e5 100644 --- a/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js +++ b/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js @@ -0,0 +1,242 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' +const { TOKEN_TRANSFER_FUNCTION_SIGNATURE } = require('../../send.constants') + +const stubs = { + rawEncode: sinon.stub().callsFake((arr1, arr2) => { + return [ ...arr1, ...arr2 ] + }), +} + +const sendUtils = proxyquire('../send-footer.utils.js', { + 'ethereumjs-abi': { + rawEncode: stubs.rawEncode, + }, +}) +const { + addressIsNew, + formShouldBeDisabled, + constructTxParams, + constructUpdatedTx, + addHexPrefixToObjectValues, +} = sendUtils + +describe('send-footer utils', () => { + + describe('addHexPrefixToObjectValues()', () => { + it('should return a new object with the same properties with a 0x prefix', () => { + assert.deepEqual( + addHexPrefixToObjectValues({ + prop1: '0x123', + prop2: '456', + prop3: 'x', + }), + { + prop1: '0x123', + prop2: '0x456', + prop3: '0xx', + } + ) + }) + }) + + describe('addressIsNew()', () => { + it('should return false if the address exists in toAccounts', () => { + assert.equal( + addressIsNew([ + { address: '0xabc' }, + { address: '0xdef' }, + { address: '0xghi' }, + ], '0xdef'), + false + ) + }) + + it('should return true if the address does not exists in toAccounts', () => { + assert.equal( + addressIsNew([ + { address: '0xabc' }, + { address: '0xdef' }, + { address: '0xghi' }, + ], '0xxyz'), + true + ) + }) + }) + + describe('formShouldBeDisabled()', () => { + const config = { + 'should return true if inError is truthy': { + inError: true, + expectedResult: true, + }, + 'should return true if gasTotal is falsy': { + inError: false, + gasTotal: false, + expectedResult: true, + }, + 'should return true if selectedToken is truthy and tokenBalance is falsy': { + selectedToken: true, + tokenBalance: null, + expectedResult: true, + }, + 'should return false if inError is false and all other params are truthy': { + inError: false, + gasTotal: '0x123', + selectedToken: true, + tokenBalance: 123, + expectedResult: false, + }, + } + Object.entries(config).map(([description, obj]) => { + it(description, () => { + assert.equal(formShouldBeDisabled(obj), obj.expectedResult) + }) + }) + }) + + describe('constructTxParams()', () => { + it('should return a new txParams object with value and to properties if there is no selectedToken', () => { + assert.deepEqual( + constructTxParams({ + selectedToken: false, + to: 'mockTo', + amount: 'mockAmount', + from: 'mockFrom', + gas: 'mockGas', + gasPrice: 'mockGasPrice', + }), + { + to: '0xmockTo', + value: '0xmockAmount', + from: '0xmockFrom', + gas: '0xmockGas', + gasPrice: '0xmockGasPrice', + } + ) + }) + + it('should return a new txParams object without a to property and a 0 value if there is a selectedToken', () => { + assert.deepEqual( + constructTxParams({ + selectedToken: true, + to: 'mockTo', + amount: 'mockAmount', + from: 'mockFrom', + gas: 'mockGas', + gasPrice: 'mockGasPrice', + }), + { + value: '0x0', + from: '0xmockFrom', + gas: '0xmockGas', + gasPrice: '0xmockGasPrice', + } + ) + }) + }) + + describe('constructUpdatedTx()', () => { + it('should return a new object with an updated txParams', () => { + const result = constructUpdatedTx({ + amount: 'mockAmount', + editingTransactionId: '0x456', + from: 'mockFrom', + gas: 'mockGas', + gasPrice: 'mockGasPrice', + selectedToken: false, + to: 'mockTo', + unapprovedTxs: { + '0x123': {}, + '0x456': { + unapprovedTxParam: 'someOtherParam', + txParams: { + data: 'someData', + }, + }, + }, + }) + + assert.deepEqual(result, { + unapprovedTxParam: 'someOtherParam', + txParams: { + from: '0xmockFrom', + gas: '0xmockGas', + gasPrice: '0xmockGasPrice', + value: '0xmockAmount', + to: '0xmockTo', + data: '0xsomeData', + }, + }) + }) + + it('should not have data property if there is non in the original tx', () => { + const result = constructUpdatedTx({ + amount: 'mockAmount', + editingTransactionId: '0x456', + from: 'mockFrom', + gas: 'mockGas', + gasPrice: 'mockGasPrice', + selectedToken: false, + to: 'mockTo', + unapprovedTxs: { + '0x123': {}, + '0x456': { + unapprovedTxParam: 'someOtherParam', + txParams: { + from: 'oldFrom', + gas: 'oldGas', + gasPrice: 'oldGasPrice', + }, + }, + }, + }) + + assert.deepEqual(result, { + unapprovedTxParam: 'someOtherParam', + txParams: { + from: '0xmockFrom', + gas: '0xmockGas', + gasPrice: '0xmockGasPrice', + value: '0xmockAmount', + to: '0xmockTo', + }, + }) + }) + + it('should have token property values if selectedToken is truthy', () => { + const result = constructUpdatedTx({ + amount: 'mockAmount', + editingTransactionId: '0x456', + from: 'mockFrom', + gas: 'mockGas', + gasPrice: 'mockGasPrice', + selectedToken: { + address: 'mockTokenAddress', + }, + to: 'mockTo', + unapprovedTxs: { + '0x123': {}, + '0x456': { + unapprovedTxParam: 'someOtherParam', + txParams: {}, + }, + }, + }) + + assert.deepEqual(result, { + unapprovedTxParam: 'someOtherParam', + txParams: { + from: '0xmockFrom', + gas: '0xmockGas', + gasPrice: '0xmockGasPrice', + value: '0x0', + to: '0xmockTokenAddress', + data: `${TOKEN_TRANSFER_FUNCTION_SIGNATURE}ss56Tont`, + }, + }) + }) + }) + +}) diff --git a/ui/app/components/send_/send-header/tests/send-header-container.test.js b/ui/app/components/send_/send-header/tests/send-header-container.test.js index e69de29bb..abce9af6b 100644 --- a/ui/app/components/send_/send-header/tests/send-header-container.test.js +++ b/ui/app/components/send_/send-header/tests/send-header-container.test.js @@ -0,0 +1,55 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +let mapStateToProps +let mapDispatchToProps + +const actionSpies = { + clearSend: sinon.spy(), +} + +proxyquire('../send-header.container.js', { + 'react-redux': { + connect: (ms, md) => { + mapStateToProps = ms + mapDispatchToProps = md + return () => ({}) + }, + }, + '../../../actions': actionSpies, + '../../../selectors': { getSelectedToken: (s) => `mockSelectedToken:${s}` }, +}) + +describe('send-header container', () => { + + describe('mapStateToProps()', () => { + + it('should map the correct properties to props', () => { + assert.deepEqual(mapStateToProps('mockState'), { + isToken: true, + }) + }) + + }) + + describe('mapDispatchToProps()', () => { + let dispatchSpy + let mapDispatchToPropsObject + + beforeEach(() => { + dispatchSpy = sinon.spy() + mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) + }) + + describe('clearSend()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.clearSend() + assert(dispatchSpy.calledOnce) + assert(actionSpies.clearSend.calledOnce) + }) + }) + + }) + +}) diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js index df605469a..d966fd808 100644 --- a/ui/app/components/send_/send.container.js +++ b/ui/app/components/send_/send.container.js @@ -1,5 +1,4 @@ import { connect } from 'react-redux' -import abi from 'ethereumjs-abi' import SendEther from './send.component' import { withRouter } from 'react-router-dom' import { compose } from 'recompose' @@ -46,7 +45,7 @@ function mapStateToProps (state) { amount: getSendAmount(state), amountConversionRate: getAmountConversionRate(state), conversionRate: getConversionRate(state), - data: generateTokenTransferData(abi, selectedAddress, selectedToken), + data: generateTokenTransferData(selectedAddress, selectedToken), editingTransactionId: getSendEditingTransactionId(state), from: getSendFromObject(state), gasLimit: getGasLimit(state), @@ -72,6 +71,7 @@ function mapDispatchToProps (dispatch) { selectedAddress, selectedToken, }) => { + console.log(`editingTransactionId`, editingTransactionId) !editingTransactionId ? dispatch(updateGasTotal({ selectedAddress, selectedToken, data })) : dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice))) diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js index 761b15182..4fadf442c 100644 --- a/ui/app/components/send_/send.selectors.js +++ b/ui/app/components/send_/send.selectors.js @@ -1,12 +1,12 @@ -import { valuesFor } from '../../util' -import abi from 'human-standard-token-abi' -import { +const { valuesFor } = require('../../util') +const abi = require('human-standard-token-abi') +const { multiplyCurrencies, -} from '../../conversion-util' +} = require('../../conversion-util') const selectors = { accountsWithSendEtherInfoSelector, - autoAddToBetaUI, + // autoAddToBetaUI, getAddressBook, getAmountConversionRate, getConversionRate, @@ -58,22 +58,22 @@ function accountsWithSendEtherInfoSelector (state) { return accountsWithSendEtherInfo } -function autoAddToBetaUI (state) { - const autoAddTransactionThreshold = 12 - const autoAddAccountsThreshold = 2 - const autoAddTokensThreshold = 1 +// function autoAddToBetaUI (state) { +// const autoAddTransactionThreshold = 12 +// const autoAddAccountsThreshold = 2 +// const autoAddTokensThreshold = 1 - const numberOfTransactions = state.metamask.selectedAddressTxList.length - const numberOfAccounts = Object.keys(state.metamask.accounts).length - const numberOfTokensAdded = state.metamask.tokens.length +// const numberOfTransactions = state.metamask.selectedAddressTxList.length +// const numberOfAccounts = Object.keys(state.metamask.accounts).length +// const numberOfTokensAdded = state.metamask.tokens.length - const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) && - (numberOfAccounts > autoAddAccountsThreshold) && - (numberOfTokensAdded > autoAddTokensThreshold) - const userIsNotInBeta = !state.metamask.featureFlags.betaUI +// const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) && +// (numberOfAccounts > autoAddAccountsThreshold) && +// (numberOfTokensAdded > autoAddTokensThreshold) +// const userIsNotInBeta = !state.metamask.featureFlags.betaUI - return userIsNotInBeta && userPassesThreshold -} +// return userIsNotInBeta && userPassesThreshold +// } function getAddressBook (state) { return state.metamask.addressBook @@ -117,14 +117,14 @@ function getForceGasMin (state) { return state.metamask.send.forceGasMin } -function getGasPrice (state) { - return state.metamask.send.gasPrice -} - function getGasLimit (state) { return state.metamask.send.gasLimit } +function getGasPrice (state) { + return state.metamask.send.gasPrice +} + function getGasTotal (state) { return state.metamask.send.gasTotal } diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index e537d5624..f09a02a6a 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -3,18 +3,17 @@ const { conversionUtil, conversionGTE, multiplyCurrencies, + conversionGreaterThan, } = require('../../conversion-util') const { calcTokenAmount, } = require('../../token-util') -const { - conversionGreaterThan, -} = require('../../conversion-util') const { INSUFFICIENT_FUNDS_ERROR, INSUFFICIENT_TOKENS_ERROR, NEGATIVE_ETH_ERROR, } = require('./send.constants') +const abi = require('ethereumjs-abi') module.exports = { calcGasTotal, @@ -179,8 +178,9 @@ function doesAmountErrorRequireUpdate ({ return amountErrorRequiresUpdate } -function generateTokenTransferData (abi, selectedAddress, selectedToken) { +function generateTokenTransferData (selectedAddress, selectedToken) { if (!selectedToken) return + console.log(`abi.rawEncode`, abi.rawEncode) return Array.prototype.map.call( abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']), x => ('00' + x.toString(16)).slice(-2) diff --git a/ui/app/components/send_/tests/send-container.test.js b/ui/app/components/send_/tests/send-container.test.js index e69de29bb..edd5e38ab 100644 --- a/ui/app/components/send_/tests/send-container.test.js +++ b/ui/app/components/send_/tests/send-container.test.js @@ -0,0 +1,150 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +let mapStateToProps +let mapDispatchToProps + +const actionSpies = { + updateSendTokenBalance: sinon.spy(), + updateGasTotal: sinon.spy(), + setGasTotal: sinon.spy(), +} +const duckActionSpies = { + updateSendErrors: sinon.spy(), +} + +proxyquire('../send.container.js', { + 'react-redux': { + connect: (ms, md) => { + mapStateToProps = ms + mapDispatchToProps = md + return () => ({}) + }, + }, + 'react-router-dom': { withRouter: () => {} }, + 'recompose': { compose: (arg1, arg2) => () => arg2() }, + './send.selectors': { + getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`, + getConversionRate: (s) => `mockConversionRate:${s}`, + getCurrentNetwork: (s) => `mockNetwork:${s}`, + getGasLimit: (s) => `mockGasLimit:${s}`, + getGasPrice: (s) => `mockGasPrice:${s}`, + getGasTotal: (s) => `mockGasTotal:${s}`, + getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`, + getSelectedAddress: (s) => `mockSelectedAddress:${s}`, + getSelectedToken: (s) => `mockSelectedToken:${s}`, + getSelectedTokenContract: (s) => `mockTokenContract:${s}`, + getSelectedTokenToFiatRate: (s) => `mockTokenToFiatRate:${s}`, + getSendAmount: (s) => `mockAmount:${s}`, + getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`, + getSendFromObject: (s) => `mockFrom:${s}`, + getTokenBalance: (s) => `mockTokenBalance:${s}`, + }, + '../../actions': actionSpies, + '../../ducks/send': duckActionSpies, + './send.utils.js': { + calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice, + generateTokenTransferData: (a, b) => `mockData:${a + b}`, + }, +}) + +describe('send container', () => { + + describe('mapStateToProps()', () => { + + it('should map the correct properties to props', () => { + assert.deepEqual(mapStateToProps('mockState'), { + amount: 'mockAmount:mockState', + amountConversionRate: 'mockAmountConversionRate:mockState', + conversionRate: 'mockConversionRate:mockState', + data: 'mockData:mockSelectedAddress:mockStatemockSelectedToken:mockState', + editingTransactionId: 'mockEditingTransactionId:mockState', + from: 'mockFrom:mockState', + gasLimit: 'mockGasLimit:mockState', + gasPrice: 'mockGasPrice:mockState', + gasTotal: 'mockGasTotal:mockState', + network: 'mockNetwork:mockState', + primaryCurrency: 'mockPrimaryCurrency:mockState', + selectedAddress: 'mockSelectedAddress:mockState', + selectedToken: 'mockSelectedToken:mockState', + tokenBalance: 'mockTokenBalance:mockState', + tokenContract: 'mockTokenContract:mockState', + tokenToFiatRate: 'mockTokenToFiatRate:mockState', + }) + }) + + }) + + describe('mapDispatchToProps()', () => { + let dispatchSpy + let mapDispatchToPropsObject + + beforeEach(() => { + dispatchSpy = sinon.spy() + mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) + }) + + describe('updateAndSetGasTotal()', () => { + const mockProps = { + data: '0x1', + editingTransactionId: '0x2', + gasLimit: '0x3', + gasPrice: '0x4', + selectedAddress: '0x4', + selectedToken: { address: '0x1' }, + } + + it('should dispatch a setGasTotal action when editingTransactionId is truthy', () => { + mapDispatchToPropsObject.updateAndSetGasTotal(mockProps) + assert(dispatchSpy.calledOnce) + assert.equal( + actionSpies.setGasTotal.getCall(0).args[0], + '0x30x4' + ) + }) + + it('should dispatch an updateGasTotal action when editingTransactionId is falsy', () => { + const { selectedAddress, selectedToken, data } = mockProps + mapDispatchToPropsObject.updateAndSetGasTotal( + Object.assign(mockProps, {editingTransactionId: false}) + ) + assert(dispatchSpy.calledOnce) + assert.deepEqual( + actionSpies.updateGasTotal.getCall(0).args[0], + { selectedAddress, selectedToken, data } + ) + }) + }) + + describe('updateSendTokenBalance()', () => { + const mockProps = { + address: '0x10', + tokenContract: '0x00a', + selectedToken: {address: '0x1'}, + } + + it('should dispatch an action', () => { + mapDispatchToPropsObject.updateSendTokenBalance(Object.assign({}, mockProps)) + assert(dispatchSpy.calledOnce) + assert.deepEqual( + actionSpies.updateSendTokenBalance.getCall(0).args[0], + mockProps + ) + }) + }) + + describe('updateSendErrors()', () => { + it('should dispatch an action', () => { + mapDispatchToPropsObject.updateSendErrors('mockError') + assert(dispatchSpy.calledOnce) + assert.equal( + duckActionSpies.updateSendErrors.getCall(0).args[0], + 'mockError' + ) + }) + }) + + }) + +}) diff --git a/ui/app/components/send_/tests/send-selectors-test-data.js b/ui/app/components/send_/tests/send-selectors-test-data.js new file mode 100644 index 000000000..bdea759fb --- /dev/null +++ b/ui/app/components/send_/tests/send-selectors-test-data.js @@ -0,0 +1,191 @@ +module.exports = { + 'metamask': { + 'isInitialized': true, + 'isUnlocked': true, + 'featureFlags': {'betaUI': true}, + 'rpcTarget': 'https://rawtestrpc.metamask.io/', + 'identities': { + '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': { + 'address': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', + 'name': 'Send Account 1', + }, + '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb': { + 'address': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', + 'name': 'Send Account 2', + }, + '0x2f8d4a878cfa04a6e60d46362f5644deab66572d': { + 'address': '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', + 'name': 'Send Account 3', + }, + '0xd85a4b6a394794842887b8284293d69163007bbb': { + 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', + 'name': 'Send Account 4', + }, + }, + 'currentCurrency': 'USD', + 'conversionRate': 1200.88200327, + 'conversionDate': 1489013762, + 'noActiveNotices': true, + 'frequentRpcList': [], + 'network': '3', + 'accounts': { + '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': { + 'code': '0x', + 'balance': '0x47c9d71831c76efe', + 'nonce': '0x1b', + 'address': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', + }, + '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb': { + 'code': '0x', + 'balance': '0x37452b1315889f80', + 'nonce': '0xa', + 'address': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', + }, + '0x2f8d4a878cfa04a6e60d46362f5644deab66572d': { + 'code': '0x', + 'balance': '0x30c9d71831c76efe', + 'nonce': '0x1c', + 'address': '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', + }, + '0xd85a4b6a394794842887b8284293d69163007bbb': { + 'code': '0x', + 'balance': '0x0', + 'nonce': '0x0', + 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', + }, + }, + 'addressBook': [ + { + 'address': '0x06195827297c7a80a443b6894d3bdb8824b43896', + 'name': 'Address Book Account 1', + }, + ], + 'tokens': [ + { + 'address': '0x1a195821297c7a80a433b6894d3bdb8824b43896', + 'decimals': 18, + 'symbol': 'ABC', + }, + { + 'address': '0x8d6b81208414189a58339873ab429b6c47ab92d3', + 'decimals': 4, + 'symbol': 'DEF', + }, + { + 'address': '0xa42084c8d1d9a2198631988579bb36b48433a72b', + 'decimals': 18, + 'symbol': 'GHI', + }, + ], + 'tokenExchangeRates': { + 'def_eth': { + rate: 2.0, + }, + 'ghi_eth': { + rate: 31.01, + }, + }, + 'transactions': {}, + 'selectedAddressTxList': [], + 'selectedTokenAddress': '0x8d6b81208414189a58339873ab429b6c47ab92d3', + 'unapprovedMsgs': {}, + 'unapprovedMsgCount': 0, + 'unapprovedPersonalMsgs': {}, + 'unapprovedPersonalMsgCount': 0, + 'keyringTypes': [ + 'Simple Key Pair', + 'HD Key Tree', + ], + 'keyrings': [ + { + 'type': 'HD Key Tree', + 'accounts': [ + 'fdea65c8e26263f6d9a1b5de9555d2931a33b825', + 'c5b8dbac4c1d3f152cdeb400e2313f309c410acb', + '2f8d4a878cfa04a6e60d46362f5644deab66572d', + ], + }, + { + 'type': 'Simple Key Pair', + 'accounts': [ + '0xd85a4b6a394794842887b8284293d69163007bbb', + ], + }, + ], + 'selectedAddress': '0xd85a4b6a394794842887b8284293d69163007bbb', + 'provider': { + 'type': 'testnet', + }, + 'shapeShiftTxList': [], + 'lostAccounts': [], + 'send': { + 'gasLimit': '0xFFFF', + 'gasPrice': '0xaa', + 'gasTotal': '0xb451dc41b578', + 'tokenBalance': 3434, + 'from': { + 'address': '0xabcdefg', + 'balance': '0x5f4e3d2c1', + }, + 'to': '0x987fedabc', + 'amount': '0x080', + 'memo': '', + 'errors': { + 'someError': null, + }, + 'maxModeOn': false, + 'editingTransactionId': 97531, + 'forceGasMin': true, + }, + 'unapprovedTxs': { + '4768706228115573': { + 'id': 4768706228115573, + 'time': 1487363153561, + 'status': 'unapproved', + 'gasMultiplier': 1, + 'metamaskNetworkId': '3', + 'txParams': { + 'from': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', + 'to': '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761', + 'value': '0xde0b6b3a7640000', + 'metamaskId': 4768706228115573, + 'metamaskNetworkId': '3', + 'gas': '0x5209', + }, + 'gasLimitSpecified': false, + 'estimatedGas': '0x5209', + 'txFee': '17e0186e60800', + 'txValue': 'de0b6b3a7640000', + 'maxCost': 'de234b52e4a0800', + 'gasPrice': '4a817c800', + }, + }, + 'currentLocale': 'en', + }, + 'appState': { + 'menuOpen': false, + 'currentView': { + 'name': 'accountDetail', + 'detailView': null, + 'context': '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + }, + 'accountDetail': { + 'subview': 'transactions', + }, + 'modal': { + 'modalState': {}, + 'previousModalState': {}, + }, + 'transForward': true, + 'isLoading': false, + 'warning': null, + 'scrollToBottom': false, + 'forgottenPassword': null, + }, + 'identities': {}, + 'send': { + 'fromDropdownOpen': false, + 'toDropdownOpen': false, + 'errors': { 'someError': null }, + }, +} diff --git a/ui/app/components/send_/tests/send-selectors.test.js b/ui/app/components/send_/tests/send-selectors.test.js index e69de29bb..43e7792a0 100644 --- a/ui/app/components/send_/tests/send-selectors.test.js +++ b/ui/app/components/send_/tests/send-selectors.test.js @@ -0,0 +1,572 @@ +import assert from 'assert' +import selectors from '../send.selectors.js' +const { + accountsWithSendEtherInfoSelector, + // autoAddToBetaUI, + getAddressBook, + getAmountConversionRate, + getConversionRate, + getConvertedCurrency, + getCurrentAccountWithSendEtherInfo, + getCurrentCurrency, + getCurrentNetwork, + getCurrentViewContext, + getForceGasMin, + getGasLimit, + getGasPrice, + getGasTotal, + getPrimaryCurrency, + getSelectedAccount, + getSelectedAddress, + getSelectedIdentity, + getSelectedToken, + // getSelectedTokenContract, + getSelectedTokenExchangeRate, + getSelectedTokenToFiatRate, + getSendAmount, + getSendEditingTransactionId, + getSendErrors, + getSendFrom, + getSendFromBalance, + getSendFromObject, + getSendMaxModeState, + getSendTo, + getSendToAccounts, + getTokenBalance, + getTokenExchangeRate, + getUnapprovedTxs, + isSendFormInError, + // transactionsSelector, +} = selectors +import mockState from './send-selectors-test-data' + +describe('send selectors', () => { + + describe('accountsWithSendEtherInfoSelector()', () => { + it('should return an array of account objects with name info from identities', () => { + assert.deepEqual( + accountsWithSendEtherInfoSelector(mockState), + [ + { + 'code': '0x', + 'balance': '0x47c9d71831c76efe', + 'nonce': '0x1b', + 'address': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', + 'name': 'Send Account 1', + }, + { + 'code': '0x', + 'balance': '0x37452b1315889f80', + 'nonce': '0xa', + 'address': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', + 'name': 'Send Account 2', + }, + { + 'code': '0x', + 'balance': '0x30c9d71831c76efe', + 'nonce': '0x1c', + 'address': '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', + 'name': 'Send Account 3', + }, + { + 'code': '0x', + 'balance': '0x0', + 'nonce': '0x0', + 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', + 'name': 'Send Account 4', + }, + ] + ) + }) + }) + + // describe('autoAddToBetaUI()', () => { + // it('should', () => { + // assert.deepEqual( + // autoAddToBetaUI(mockState), + + // ) + // }) + // }) + + describe('getAddressBook()', () => { + it('should return the address book', () => { + assert.deepEqual( + getAddressBook(mockState), + [ + { + 'address': '0x06195827297c7a80a443b6894d3bdb8824b43896', + 'name': 'Address Book Account 1', + }, + ], + ) + }) + }) + + describe('getAmountConversionRate()', () => { + it('should return the token conversion rate if a token is selected', () => { + assert.equal( + getAmountConversionRate(mockState), + 2401.76400654 + ) + }) + + it('should return the eth conversion rate if no token is selected', () => { + const editedMockState = { + metamask: Object.assign({}, mockState.metamask, { selectedTokenAddress: null }), + } + assert.equal( + getAmountConversionRate(editedMockState), + 1200.88200327 + ) + }) + }) + + describe('getConversionRate()', () => { + it('should return the eth conversion rate', () => { + assert.deepEqual( + getConversionRate(mockState), + 1200.88200327 + ) + }) + }) + + describe('getConvertedCurrency()', () => { + it('should return the currently selected currency', () => { + assert.equal( + getConvertedCurrency(mockState), + 'USD' + ) + }) + }) + + describe('getCurrentAccountWithSendEtherInfo()', () => { + it('should return the currently selected account with identity info', () => { + assert.deepEqual( + getCurrentAccountWithSendEtherInfo(mockState), + { + 'code': '0x', + 'balance': '0x0', + 'nonce': '0x0', + 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', + 'name': 'Send Account 4', + } + ) + }) + }) + + describe('getCurrentCurrency()', () => { + it('should return the currently selected currency', () => { + assert.equal( + getCurrentCurrency(mockState), + 'USD' + ) + }) + }) + + describe('getCurrentNetwork()', () => { + it('should return the id of the currently selected network', () => { + assert.equal( + getCurrentNetwork(mockState), + '3' + ) + }) + }) + + describe('getCurrentViewContext()', () => { + it('should return the context of the current view', () => { + assert.equal( + getCurrentViewContext(mockState), + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' + ) + }) + }) + + describe('getForceGasMin()', () => { + it('should get the send.forceGasMin property', () => { + assert.equal( + getForceGasMin(mockState), + true + ) + }) + }) + + describe('getGasLimit()', () => { + it('should return the send.gasLimit', () => { + assert.equal( + getGasLimit(mockState), + '0xFFFF' + ) + }) + }) + + describe('getGasPrice()', () => { + it('should return the send.gasPrice', () => { + assert.equal( + getGasPrice(mockState), + '0xaa' + ) + }) + }) + + describe('getGasTotal()', () => { + it('should return the send.gasTotal', () => { + assert.equal( + getGasTotal(mockState), + '0xb451dc41b578' + ) + }) + }) + + describe('getPrimaryCurrency()', () => { + it('should return the symbol of the selected token', () => { + assert.equal( + getPrimaryCurrency(mockState), + 'DEF' + ) + }) + }) + + describe('getSelectedAccount()', () => { + it('should return the currently selected account', () => { + assert.deepEqual( + getSelectedAccount(mockState), + { + 'code': '0x', + 'balance': '0x0', + 'nonce': '0x0', + 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', + } + ) + }) + }) + + describe('getSelectedAddress()', () => { + it('should', () => { + assert.equal( + getSelectedAddress(mockState), + '0xd85a4b6a394794842887b8284293d69163007bbb' + ) + }) + }) + + describe('getSelectedIdentity()', () => { + it('should return the identity object of the currently selected address', () => { + assert.deepEqual( + getSelectedIdentity(mockState), + { + 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', + 'name': 'Send Account 4', + } + ) + }) + }) + + describe('getSelectedToken()', () => { + it('should return the currently selected token if selected', () => { + assert.deepEqual( + getSelectedToken(mockState), + { + 'address': '0x8d6b81208414189a58339873ab429b6c47ab92d3', + 'decimals': 4, + 'symbol': 'DEF', + } + ) + }) + + it('should return the send token if none is currently selected, but a send token exists', () => { + const mockSendToken = { + 'address': '0x123456708414189a58339873ab429b6c47ab92d3', + 'decimals': 4, + 'symbol': 'JKL', + } + const editedMockState = { + metamask: Object.assign({}, mockState.metamask, { + selectedTokenAddress: null, + send: { + token: mockSendToken, + }, + }), + } + assert.deepEqual( + getSelectedToken(editedMockState), + Object.assign({}, mockSendToken) + ) + }) + }) + + // TODO + // describe('getSelectedTokenContract()', () => { + // it('should', () => { + // assert.deepEqual( + // getSelectedTokenContract(mockState), + + // ) + // }) + // }) + + describe('getSelectedTokenExchangeRate()', () => { + it('should return the exchange rate for the selected token', () => { + assert.equal( + getSelectedTokenExchangeRate(mockState), + 2.0 + ) + }) + }) + + describe('getSelectedTokenToFiatRate()', () => { + it('should return rate for converting the selected token to fiat', () => { + assert.equal( + getSelectedTokenToFiatRate(mockState), + 2401.76400654 + ) + }) + }) + + describe('getSendAmount()', () => { + it('should return the send.amount', () => { + assert.equal( + getSendAmount(mockState), + '0x080' + ) + }) + }) + + describe('getSendEditingTransactionId()', () => { + it('should return the send.editingTransactionId', () => { + assert.equal( + getSendEditingTransactionId(mockState), + 97531 + ) + }) + }) + + describe('getSendErrors()', () => { + it('should return the send.errors', () => { + assert.deepEqual( + getSendErrors(mockState), + { 'someError': null } + ) + }) + }) + + describe('getSendFrom()', () => { + it('should return the send.from', () => { + assert.deepEqual( + getSendFrom(mockState), + { + 'address': '0xabcdefg', + 'balance': '0x5f4e3d2c1', + } + ) + }) + }) + + describe('getSendFromBalance()', () => { + it('should get the send.from balance if it exists', () => { + assert.equal( + getSendFromBalance(mockState), + '0x5f4e3d2c1' + ) + }) + + it('should get the selected account balance if the send.from does not exist', () => { + const editedMockState = { + metamask: Object.assign({}, mockState.metamask, { + send: { + from: null, + }, + }), + } + assert.equal( + getSendFromBalance(editedMockState), + '0x0' + ) + }) + }) + + describe('getSendFromObject()', () => { + it('should return send.from if it exists', () => { + assert.deepEqual( + getSendFromObject(mockState), + { + 'address': '0xabcdefg', + 'balance': '0x5f4e3d2c1', + } + ) + }) + + it('should return the current account with send ether info if send.from does not exist', () => { + const editedMockState = { + metamask: Object.assign({}, mockState.metamask, { + send: { + from: null, + }, + }), + } + assert.deepEqual( + getSendFromObject(editedMockState), + { + 'code': '0x', + 'balance': '0x0', + 'nonce': '0x0', + 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', + 'name': 'Send Account 4', + } + ) + }) + }) + + describe('getSendMaxModeState()', () => { + it('should return send.maxModeOn', () => { + assert.equal( + getSendMaxModeState(mockState), + false + ) + }) + }) + + describe('getSendTo()', () => { + it('should return send.to', () => { + assert.equal( + getSendTo(mockState), + '0x987fedabc' + ) + }) + }) + + describe('getSendToAccounts()', () => { + it('should return an array including all the users accounts and the address book', () => { + assert.deepEqual( + getSendToAccounts(mockState), + [ + { + 'code': '0x', + 'balance': '0x47c9d71831c76efe', + 'nonce': '0x1b', + 'address': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', + 'name': 'Send Account 1', + }, + { + 'code': '0x', + 'balance': '0x37452b1315889f80', + 'nonce': '0xa', + 'address': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', + 'name': 'Send Account 2', + }, + { + 'code': '0x', + 'balance': '0x30c9d71831c76efe', + 'nonce': '0x1c', + 'address': '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', + 'name': 'Send Account 3', + }, + { + 'code': '0x', + 'balance': '0x0', + 'nonce': '0x0', + 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', + 'name': 'Send Account 4', + }, + { + 'address': '0x06195827297c7a80a443b6894d3bdb8824b43896', + 'name': 'Address Book Account 1', + }, + ] + ) + }) + }) + + describe('getTokenBalance()', () => { + it('should', () => { + assert.equal( + getTokenBalance(mockState), + 3434 + ) + }) + }) + + describe('getTokenExchangeRate()', () => { + it('should return the passed tokens exchange rates', () => { + assert.equal( + getTokenExchangeRate(mockState, 'GHI'), + 31.01 + ) + }) + }) + + describe('getUnapprovedTxs()', () => { + it('should return the unapproved txs', () => { + assert.deepEqual( + getUnapprovedTxs(mockState), + { + '4768706228115573': { + 'id': 4768706228115573, + 'time': 1487363153561, + 'status': 'unapproved', + 'gasMultiplier': 1, + 'metamaskNetworkId': '3', + 'txParams': { + 'from': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', + 'to': '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761', + 'value': '0xde0b6b3a7640000', + 'metamaskId': 4768706228115573, + 'metamaskNetworkId': '3', + 'gas': '0x5209', + }, + 'gasLimitSpecified': false, + 'estimatedGas': '0x5209', + 'txFee': '17e0186e60800', + 'txValue': 'de0b6b3a7640000', + 'maxCost': 'de234b52e4a0800', + 'gasPrice': '4a817c800', + }, + } + ) + }) + }) + + describe('isSendFormInError()', () => { + it('should return true if amount or to errors are truthy', () => { + const editedMockState1 = { + send: Object.assign({}, mockState.send, { + errors: { amount: true }, + }), + } + assert.deepEqual( + isSendFormInError(editedMockState1), + true + ) + const editedMockState2 = { + send: Object.assign({}, mockState.send, { + errors: { to: true }, + }), + } + assert.deepEqual( + isSendFormInError(editedMockState2), + true + ) + }) + + it('should return false if amount is falsy and to is null', () => { + const editedMockState = { + send: Object.assign({}, mockState.send, { errors: { amount: false, to: null } }), + } + assert.deepEqual( + isSendFormInError(editedMockState), + false + ) + }) + }) + + // TODO + // describe('transactionsSelector()', () => { + // it('should', () => { + // assert.deepEqual( + // transactionsSelector(mockState), + + // ) + // }) + // }) + +}) diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index e69de29bb..4d471bcc1 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -0,0 +1,264 @@ +import assert from 'assert' +import sinon from 'sinon' +import proxyquire from 'proxyquire' + +const { + INSUFFICIENT_FUNDS_ERROR, + INSUFFICIENT_TOKENS_ERROR, +} = require('../send.constants') + +const stubs = { + addCurrencies: sinon.stub().callsFake((a, b, obj) => a + b), + conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)), + conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value > obj2.value), + multiplyCurrencies: sinon.stub().callsFake((a, b) => a * b), + calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d), + rawEncode: sinon.stub().returns([16, 1100]), +} + +const sendUtils = proxyquire('../send.utils.js', { + '../../conversion-util': { + addCurrencies: stubs.addCurrencies, + conversionUtil: stubs.conversionUtil, + conversionGTE: stubs.conversionGTE, + multiplyCurrencies: stubs.multiplyCurrencies, + }, + '../../token-util': { calcTokenAmount: stubs.calcTokenAmount }, + 'ethereumjs-abi': { + rawEncode: stubs.rawEncode, + }, +}) + +const { + calcGasTotal, + doesAmountErrorRequireUpdate, + generateTokenTransferData, + getAmountErrorObject, + getParamsForGasEstimate, + calcTokenBalance, + isBalanceSufficient, + isTokenBalanceSufficient, +} = sendUtils + +describe('send utils', () => { + + describe('calcGasTotal()', () => { + it('should call multiplyCurrencies with the correct params and return the multiplyCurrencies return', () => { + const result = calcGasTotal(12, 15) + assert.equal(result, 180) + const call_ = stubs.multiplyCurrencies.getCall(0).args + assert.deepEqual( + call_, + [12, 15, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 16, + } ] + ) + }) + }) + + describe('doesAmountErrorRequireUpdate()', () => { + const config = { + 'should return true if balances are different': { + balance: 0, + prevBalance: 1, + expectedResult: true, + }, + 'should return true if gasTotals are different': { + gasTotal: 0, + prevGasTotal: 1, + expectedResult: true, + }, + 'should return true if token balances are different': { + tokenBalance: 0, + prevTokenBalance: 1, + selectedToken: 'someToken', + expectedResult: true, + }, + 'should return false if they are all the same': { + balance: 1, + prevBalance: 1, + gasTotal: 1, + prevGasTotal: 1, + tokenBalance: 1, + prevTokenBalance: 1, + selectedToken: 'someToken', + expectedResult: false, + }, + } + Object.entries(config).map(([description, obj]) => { + it(description, () => { + assert.equal(doesAmountErrorRequireUpdate(obj), obj.expectedResult) + }) + }) + + }) + + describe('generateTokenTransferData()', () => { + it('should return undefined if not passed a selected token', () => { + assert.equal(generateTokenTransferData('mockAddress', false), undefined) + }) + + it('should return encoded token transfer data', () => { + assert.equal(generateTokenTransferData('mockAddress', true), '104c') + }) + }) + + describe('getAmountErrorObject()', () => { + const config = { + 'should return insufficientFunds error if isBalanceSufficient returns false': { + amount: 15, + amountConversionRate: 2, + balance: 1, + conversionRate: 3, + gasTotal: 17, + primaryCurrency: 'ABC', + expectedResult: { amount: INSUFFICIENT_FUNDS_ERROR }, + }, + 'should return insufficientTokens error if token is selected and isTokenBalanceSufficient returns false': { + amount: '0x10', + amountConversionRate: 2, + balance: 100, + conversionRate: 3, + decimals: 10, + gasTotal: 17, + primaryCurrency: 'ABC', + selectedToken: 'someToken', + tokenBalance: 123, + expectedResult: { amount: INSUFFICIENT_TOKENS_ERROR }, + }, + } + Object.entries(config).map(([description, obj]) => { + it(description, () => { + assert.deepEqual(getAmountErrorObject(obj), obj.expectedResult) + }) + }) + }) + + describe('getParamsForGasEstimate()', () => { + it('should return from and gas properties if no symbol or data', () => { + assert.deepEqual( + getParamsForGasEstimate('mockAddress'), + { + from: 'mockAddress', + gas: '746a528800', + } + ) + }) + + it('should return value property if symbol provided', () => { + assert.deepEqual( + getParamsForGasEstimate('mockAddress', 'ABC'), + { + from: 'mockAddress', + gas: '746a528800', + value: '0x0', + } + ) + }) + + it('should return data property if data provided', () => { + assert.deepEqual( + getParamsForGasEstimate('mockAddress', 'ABC', 'somedata'), + { + from: 'mockAddress', + gas: '746a528800', + value: '0x0', + data: 'somedata', + } + ) + }) + }) + + describe('calcTokenBalance()', () => { + it('should return the calculated token blance', () => { + assert.equal(calcTokenBalance({ + selectedToken: { + decimals: 11, + }, + usersToken: { + balance: 20, + }, + }), 'calc:2011') + }) + }) + + describe('isBalanceSufficient()', () => { + it('should correctly call addCurrencies and return the result of calling conversionGTE', () => { + stubs.conversionGTE.resetHistory() + const result = isBalanceSufficient({ + amount: 15, + amountConversionRate: 2, + balance: 100, + conversionRate: 3, + gasTotal: 17, + primaryCurrency: 'ABC', + }) + assert.deepEqual( + stubs.addCurrencies.getCall(0).args, + [ + 15, 17, { + aBase: 16, + bBase: 16, + toNumericBase: 'hex', + }, + ] + ) + assert.deepEqual( + stubs.conversionGTE.getCall(0).args, + [ + { + value: 100, + fromNumericBase: 'hex', + fromCurrency: 'ABC', + conversionRate: 3, + }, + { + value: 32, + fromNumericBase: 'hex', + conversionRate: 2, + fromCurrency: 'ABC', + }, + ] + ) + + assert.equal(result, true) + }) + }) + + describe('isTokenBalanceSufficient()', () => { + it('should correctly call conversionUtil and return the result of calling conversionGTE', () => { + stubs.conversionGTE.resetHistory() + const result = isTokenBalanceSufficient({ + amount: '0x10', + tokenBalance: 123, + decimals: 10, + }) + assert.deepEqual( + stubs.conversionUtil.getCall(0).args, + [ + '0x10', { + fromNumericBase: 'hex', + }, + ] + ) + assert.deepEqual( + stubs.conversionGTE.getCall(0).args, + [ + { + value: 123, + fromNumericBase: 'dec', + }, + { + value: 'calc:1610', + fromNumericBase: 'dec', + }, + ] + ) + + assert.equal(result, false) + }) + }) + +}) diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index bd4ea80a6..1eed06c3b 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -203,8 +203,8 @@ TxListItem.prototype.showRetryButton = function () { const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce) const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1] - const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce - && lastSubmittedTxWithCurrentNonce.id === transactionId + const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce && + lastSubmittedTxWithCurrentNonce.id === transactionId return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 } diff --git a/ui/app/i18n-provider.js b/ui/app/i18n-provider.js index 4ef618018..2856e0ed6 100644 --- a/ui/app/i18n-provider.js +++ b/ui/app/i18n-provider.js @@ -6,14 +6,14 @@ const { compose } = require('recompose') const t = require('../i18n-helper').getMessage class I18nProvider extends Component { - getChildContext() { + getChildContext () { const { localeMessages } = this.props return { t: t.bind(null, localeMessages), } } - render() { + render () { return this.props.children } } diff --git a/ui/app/main-container.js b/ui/app/main-container.js index c305687ea..68d1d45e7 100644 --- a/ui/app/main-container.js +++ b/ui/app/main-container.js @@ -20,7 +20,7 @@ MainContainer.prototype.render = function () { // - pass resulting h() to MainContainer // - error checking in separate func // - router in separate func - let contents = { + const contents = { component: AccountAndTransactionDetails, key: 'account-detail', style: {}, diff --git a/ui/app/metamask-connect.js b/ui/app/metamask-connect.js index 8da594635..81fa7e403 100644 --- a/ui/app/metamask-connect.js +++ b/ui/app/metamask-connect.js @@ -24,4 +24,4 @@ const _higherOrderMapStateToProps = (mapStateToProps) => { } } -module.exports = metamaskConnect \ No newline at end of file +module.exports = metamaskConnect diff --git a/ui/app/util.js b/ui/app/util.js index 8e9390dfb..1ccd17ba7 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -291,7 +291,7 @@ function getTokenAddressFromTokenObject (token) { /** * Safely checksumms a potentially-null address - * + * * @param {String} [address] - address to checksum * @returns {String} - checksummed address */ -- cgit From 9ccc609e567b373b5f02bddc4d1095b2ce2d6c44 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 7 May 2018 08:03:20 -0400 Subject: Adds test for send, send-content, send-footer and send-header components. --- .../tests/send-content-component.test.js | 38 ++++ .../send_/send-footer/send-footer.component.js | 17 +- .../tests/send-footer-component.test.js | 159 ++++++++++++++ .../send_/send-header/send-header.component.js | 14 +- .../tests/send-header-component.test.js | 70 ++++++ .../components/send_/tests/send-component.test.js | 234 +++++++++++++++++++++ 6 files changed, 516 insertions(+), 16 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/tests/send-content-component.test.js b/ui/app/components/send_/send-content/tests/send-content-component.test.js index e69de29bb..785545861 100644 --- a/ui/app/components/send_/send-content/tests/send-content-component.test.js +++ b/ui/app/components/send_/send-content/tests/send-content-component.test.js @@ -0,0 +1,38 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import SendContent from '../send-content.component.js' + +import PageContainerContent from '../../../page-container/page-container-content.component' +import SendAmountRow from '../send-amount-row/send-amount-row.container' +import SendFromRow from '../send-from-row/send-from-row.container' +import SendGasRow from '../send-gas-row/send-gas-row.container' +import SendToRow from '../send-to-row/send-to-row.container' + +describe('Send Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow() + }) + + describe('render', () => { + it('should render a PageContainerContent component', () => { + assert.equal(wrapper.find(PageContainerContent).length, 1) + }) + + it('should render a div with a .send-v2__form class as a child of PageContainerContent', () => { + const PageContainerContentChild = wrapper.find(PageContainerContent).children() + PageContainerContentChild.is('div') + PageContainerContentChild.is('.send-v2__form') + }) + + it('should render the correct row components as grandchildren of the PageContainerContent component', () => { + const PageContainerContentChild = wrapper.find(PageContainerContent).children() + PageContainerContentChild.childAt(0).is(SendFromRow) + PageContainerContentChild.childAt(1).is(SendToRow) + PageContainerContentChild.childAt(2).is(SendAmountRow) + PageContainerContentChild.childAt(3).is(SendGasRow) + }) + }) +}) diff --git a/ui/app/components/send_/send-footer/send-footer.component.js b/ui/app/components/send_/send-footer/send-footer.component.js index fc7a78a94..de2a885f0 100644 --- a/ui/app/components/send_/send-footer/send-footer.component.js +++ b/ui/app/components/send_/send-footer/send-footer.component.js @@ -26,6 +26,11 @@ export default class SendFooter extends Component { update: PropTypes.func, }; + onCancel () { + this.props.clearSend() + this.props.history.push(DEFAULT_ROUTE) + } + onSubmit (event) { event.preventDefault() const { @@ -44,7 +49,7 @@ export default class SendFooter extends Component { toAccounts, } = this.props - // Should not be needed because submit should be disabled if there are no errors. + // Should not be needed because submit should be disabled if there are errors. // const noErrors = !amountError && toError === null // if (!noErrors) { @@ -70,18 +75,12 @@ export default class SendFooter extends Component { this.props.history.push(CONFIRM_TRANSACTION_ROUTE) } - render () { - const { clearSend, disabled, history } = this.props - return ( { - clearSend() - history.push(DEFAULT_ROUTE) - }} + onCancel={() => this.onCancel()} onSubmit={e => this.onSubmit(e)} - disabled={disabled} + disabled={this.props.disabled} /> ) } diff --git a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js index e69de29bb..f8cbd41f3 100644 --- a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js +++ b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js @@ -0,0 +1,159 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../../routes' +import SendFooter from '../send-footer.component.js' + +import PageContainerFooter from '../../../page-container/page-container-footer' + +const propsMethodSpies = { + addToAddressBookIfNew: sinon.spy(), + clearSend: sinon.spy(), + sign: sinon.spy(), + update: sinon.spy(), +} +const historySpies = { + push: sinon.spy(), +} +const MOCK_EVENT = { preventDefault: () => {} } + +sinon.spy(SendFooter.prototype, 'onCancel') +sinon.spy(SendFooter.prototype, 'onSubmit') + +describe('Send Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow(, { context: { t: str => str } }) + instance = wrapper.instance() + }) + + afterEach(() => { + propsMethodSpies.clearSend.resetHistory() + propsMethodSpies.addToAddressBookIfNew.resetHistory() + propsMethodSpies.clearSend.resetHistory() + propsMethodSpies.sign.resetHistory() + propsMethodSpies.update.resetHistory() + historySpies.push.resetHistory() + SendFooter.prototype.onCancel.resetHistory() + SendFooter.prototype.onSubmit.resetHistory() + }) + + describe('onCancel', () => { + it('should call clearSend', () => { + assert.equal(propsMethodSpies.clearSend.callCount, 0) + wrapper.instance().onCancel() + assert.equal(propsMethodSpies.clearSend.callCount, 1) + }) + + it('should call history.push', () => { + assert.equal(historySpies.push.callCount, 0) + wrapper.instance().onCancel() + assert.equal(historySpies.push.callCount, 1) + assert.equal(historySpies.push.getCall(0).args[0], DEFAULT_ROUTE) + }) + }) + + describe('onSubmit', () => { + it('should call addToAddressBookIfNew with the correct params', () => { + wrapper.instance().onSubmit(MOCK_EVENT) + assert(propsMethodSpies.addToAddressBookIfNew.calledOnce) + assert.deepEqual( + propsMethodSpies.addToAddressBookIfNew.getCall(0).args, + ['mockTo', ['mockAccount']] + ) + }) + + it('should call props.update if editingTransactionId is truthy', () => { + wrapper.instance().onSubmit(MOCK_EVENT) + assert(propsMethodSpies.update.calledOnce) + assert.deepEqual( + propsMethodSpies.update.getCall(0).args[0], + { + amount: 'mockAmount', + editingTransactionId: 'mockEditingTransactionId', + from: 'mockAddress', + gas: 'mockGasLimit', + gasPrice: 'mockGasPrice', + selectedToken: { mockProp: 'mockSelectedTokenProp' }, + to: 'mockTo', + unapprovedTxs: ['mockTx'], + } + ) + }) + + it('should not call props.sign if editingTransactionId is truthy', () => { + assert.equal(propsMethodSpies.sign.callCount, 0) + }) + + it('should call props.sign if editingTransactionId is falsy', () => { + wrapper.setProps({ editingTransactionId: null }) + wrapper.instance().onSubmit(MOCK_EVENT) + assert(propsMethodSpies.sign.calledOnce) + assert.deepEqual( + propsMethodSpies.sign.getCall(0).args[0], + { + amount: 'mockAmount', + from: 'mockAddress', + gas: 'mockGasLimit', + gasPrice: 'mockGasPrice', + selectedToken: { mockProp: 'mockSelectedTokenProp' }, + to: 'mockTo', + } + ) + }) + + it('should not call props.update if editingTransactionId is falsy', () => { + assert.equal(propsMethodSpies.update.callCount, 0) + }) + + it('should call history.push', () => { + wrapper.instance().onSubmit(MOCK_EVENT) + assert.equal(historySpies.push.callCount, 1) + assert.equal(historySpies.push.getCall(0).args[0], CONFIRM_TRANSACTION_ROUTE) + }) + }) + + describe('render', () => { + it('should render a PageContainerFooter component', () => { + assert.equal(wrapper.find(PageContainerFooter).length, 1) + }) + + it('should pass the correct props to PageContainerFooter', () => { + const { + onCancel, + onSubmit, + disabled, + } = wrapper.find(PageContainerFooter).props() + assert.equal(disabled, true) + + assert.equal(SendFooter.prototype.onSubmit.callCount, 0) + onSubmit(MOCK_EVENT) + assert.equal(SendFooter.prototype.onSubmit.callCount, 1) + + assert.equal(SendFooter.prototype.onCancel.callCount, 0) + onCancel() + assert.equal(SendFooter.prototype.onCancel.callCount, 1) + }) + }) +}) diff --git a/ui/app/components/send_/send-header/send-header.component.js b/ui/app/components/send_/send-header/send-header.component.js index dc4190b93..0d75dbdae 100644 --- a/ui/app/components/send_/send-header/send-header.component.js +++ b/ui/app/components/send_/send-header/send-header.component.js @@ -11,17 +11,17 @@ export default class SendHeader extends Component { isToken: PropTypes.bool, }; - render () { - const { isToken, clearSend, history } = this.props + onClose () { + this.props.clearSend() + this.props.history.push(DEFAULT_ROUTE) + } + render () { return ( { - clearSend() - history.push(DEFAULT_ROUTE) - }} + onClose={() => this.onClose()} subtitle={this.context.t('onlySendToEtherAddress')} - title={isToken ? this.context.t('sendTokens') : this.context.t('sendETH')} + title={this.props.isToken ? this.context.t('sendTokens') : this.context.t('sendETH')} /> ) } diff --git a/ui/app/components/send_/send-header/tests/send-header-component.test.js b/ui/app/components/send_/send-header/tests/send-header-component.test.js index e69de29bb..f1e387b72 100644 --- a/ui/app/components/send_/send-header/tests/send-header-component.test.js +++ b/ui/app/components/send_/send-header/tests/send-header-component.test.js @@ -0,0 +1,70 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import { DEFAULT_ROUTE } from '../../../../routes' +import SendHeader from '../send-header.component.js' + +import PageContainerHeader from '../../../page-container/page-container-header' + +const propsMethodSpies = { + clearSend: sinon.spy(), +} +const historySpies = { + push: sinon.spy(), +} + +sinon.spy(SendHeader.prototype, 'onClose') + +describe('Send Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow(, { context: { t: str => str } }) + instance = wrapper.instance() + }) + + afterEach(() => { + propsMethodSpies.clearSend.resetHistory() + historySpies.push.resetHistory() + SendHeader.prototype.onClose.resetHistory() + }) + + describe('onClose', () => { + it('should call clearSend', () => { + assert.equal(propsMethodSpies.clearSend.callCount, 0) + wrapper.instance().onClose() + assert.equal(propsMethodSpies.clearSend.callCount, 1) + }) + + it('should call history.push', () => { + assert.equal(historySpies.push.callCount, 0) + wrapper.instance().onClose() + assert.equal(historySpies.push.callCount, 1) + assert.equal(historySpies.push.getCall(0).args[0], DEFAULT_ROUTE) + }) + }) + + describe('render', () => { + it('should render a PageContainerHeader compenent', () => { + assert.equal(wrapper.find(PageContainerHeader).length, 1) + }) + + it('should pass the correct props to PageContainerHeader', () => { + const { + onClose, + subtitle, + title, + } = wrapper.find(PageContainerHeader).props() + assert.equal(subtitle, 'onlySendToEtherAddress') + assert.equal(title, 'sendETH') + assert.equal(SendHeader.prototype.onClose.callCount, 0) + onClose() + assert.equal(SendHeader.prototype.onClose.callCount, 1) + }) + }) +}) diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js index e69de29bb..eee0b23b8 100644 --- a/ui/app/components/send_/tests/send-component.test.js +++ b/ui/app/components/send_/tests/send-component.test.js @@ -0,0 +1,234 @@ +import React from 'react' +import assert from 'assert' +import proxyquire from 'proxyquire' +import { shallow } from 'enzyme' +import sinon from 'sinon' + +import SendHeader from '../send-header/send-header.container' +import SendContent from '../send-content/send-content.component' +import SendFooter from '../send-footer/send-footer.container' + +const propsMethodSpies = { + updateAndSetGasTotal: sinon.spy(), + updateSendErrors: sinon.spy(), + updateSendTokenBalance: sinon.spy(), +} +const utilsMethodStubs = { + getAmountErrorObject: sinon.stub().returns({ amount: 'mockAmountError' }), + doesAmountErrorRequireUpdate: sinon.stub().callsFake(obj => obj.balance !== obj.prevBalance), +} + +const SendTransactionScreen = proxyquire('../send.component.js', { + './send.utils': utilsMethodStubs, +}).default + +sinon.spy(SendTransactionScreen.prototype, 'componentDidMount') +sinon.spy(SendTransactionScreen.prototype, 'updateGas') + +describe('Send Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow() + instance = wrapper.instance() + }) + + afterEach(() => { + SendTransactionScreen.prototype.componentDidMount.resetHistory() + SendTransactionScreen.prototype.updateGas.resetHistory() + utilsMethodStubs.doesAmountErrorRequireUpdate.resetHistory() + utilsMethodStubs.getAmountErrorObject.resetHistory() + propsMethodSpies.updateAndSetGasTotal.resetHistory() + propsMethodSpies.updateSendErrors.resetHistory() + propsMethodSpies.updateSendTokenBalance.resetHistory() + }) + + it('should call componentDidMount', () => { + assert(SendTransactionScreen.prototype.componentDidMount.calledOnce) + }) + + describe('componentWillMount', () => { + it('should call this.updateGas', () => { + assert(SendTransactionScreen.prototype.updateGas.calledOnce) + wrapper.instance().componentWillMount() + assert(SendTransactionScreen.prototype.updateGas.calledTwice) + }) + }) + + describe('componentDidUpdate', () => { + it('should call doesAmountErrorRequireUpdate with the expected params', () => { + wrapper.instance().componentDidUpdate({ + from: { + balance: '', + }, + }) + assert(utilsMethodStubs.doesAmountErrorRequireUpdate.calledOnce) + assert.deepEqual( + utilsMethodStubs.doesAmountErrorRequireUpdate.getCall(0).args[0], + { + balance: 'mockBalance', + gasTotal: 'mockGasTotal', + prevBalance: '', + prevGasTotal: undefined, + prevTokenBalance: undefined, + selectedToken: 'mockSelectedToken', + tokenBalance: 'mockTokenBalance', + } + ) + }) + + it('should not call getAmountErrorObject if doesAmountErrorRequireUpdate returns false', () => { + wrapper.instance().componentDidUpdate({ + from: { + balance: 'mockBalance', + }, + }) + assert.equal(utilsMethodStubs.getAmountErrorObject.callCount, 0) + }) + + it('should call getAmountErrorObject if doesAmountErrorRequireUpdate returns true', () => { + wrapper.instance().componentDidUpdate({ + from: { + balance: 'balanceChanged', + }, + }) + assert.equal(utilsMethodStubs.getAmountErrorObject.callCount, 1) + assert.deepEqual( + utilsMethodStubs.getAmountErrorObject.getCall(0).args[0], + { + amount: 'mockAmount', + amountConversionRate: 'mockAmountConversionRate', + balance: 'mockBalance', + conversionRate: 10, + gasTotal: 'mockGasTotal', + primaryCurrency: 'mockPrimaryCurrency', + selectedToken: 'mockSelectedToken', + tokenBalance: 'mockTokenBalance', + } + ) + }) + + it('should call updateSendErrors with the expected params', () => { + wrapper.instance().componentDidUpdate({ + from: { + balance: 'balanceChanged', + }, + }) + assert.equal(propsMethodSpies.updateSendErrors.callCount, 1) + assert.deepEqual( + propsMethodSpies.updateSendErrors.getCall(0).args[0], + { amount: 'mockAmountError'} + ) + }) + + it('should not call updateSendTokenBalance or this.updateGas if network === prevNetwork', () => { + SendTransactionScreen.prototype.updateGas.resetHistory() + wrapper.instance().componentDidUpdate({ + from: { + balance: 'balanceChanged', + }, + network: '3', + }) + assert.equal(propsMethodSpies.updateSendTokenBalance.callCount, 0) + assert.equal(SendTransactionScreen.prototype.updateGas.callCount, 0) + }) + + it('should not call updateSendTokenBalance or this.updateGas if network === loading', () => { + wrapper.setProps({ network: 'loading' }) + SendTransactionScreen.prototype.updateGas.resetHistory() + propsMethodSpies.updateSendTokenBalance.resetHistory() + wrapper.instance().componentDidUpdate({ + from: { + balance: 'balanceChanged', + }, + network: '3', + }) + assert.equal(propsMethodSpies.updateSendTokenBalance.callCount, 0) + assert.equal(SendTransactionScreen.prototype.updateGas.callCount, 0) + }) + + it('should call updateSendTokenBalance and this.updateGas with the correct params', () => { + SendTransactionScreen.prototype.updateGas.resetHistory() + wrapper.instance().componentDidUpdate({ + from: { + balance: 'balanceChanged', + }, + network: '2', + }) + assert.equal(propsMethodSpies.updateSendTokenBalance.callCount, 1) + assert.deepEqual( + propsMethodSpies.updateSendTokenBalance.getCall(0).args[0], + { + selectedToken: 'mockSelectedToken', + tokenContract: 'mockTokenContract', + address: 'mockAddress', + } + ) + assert.equal(SendTransactionScreen.prototype.updateGas.callCount, 1) + assert.deepEqual( + SendTransactionScreen.prototype.updateGas.getCall(0).args, + [] + ) + }) + }) + + describe('updateGas', () => { + it('should call updateAndSetGasTotal with the correct params', () => { + propsMethodSpies.updateAndSetGasTotal.resetHistory() + wrapper.instance().updateGas() + assert.equal(propsMethodSpies.updateAndSetGasTotal.callCount, 1) + assert.deepEqual( + propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0], + { + data: 'mockData', + editingTransactionId: 'mockEditingTransactionId', + gasLimit: 'mockGasLimit', + gasPrice: 'mockGasPrice', + selectedAddress: 'mockSelectedAddress', + selectedToken: 'mockSelectedToken', + } + ) + }) + }) + + describe('render', () => { + it('should render a page-container class', () => { + assert.equal(wrapper.find('.page-container').length, 1) + }) + + it('should render SendHeader, SendContent and SendFooter', () => { + assert.equal(wrapper.find(SendHeader).length, 1) + assert.equal(wrapper.find(SendContent).length, 1) + assert.equal(wrapper.find(SendFooter).length, 1) + }) + + it('should pass the history prop to SendHeader and SendFooter', () => { + assert.deepEqual( + wrapper.find(SendFooter).props(), + { + history: { mockProp: 'history-abc' }, + } + ) + }) + }) +}) -- cgit From f94ffa022c9f76a8d7acbf331f49a363d5e7362d Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 10 May 2018 07:37:33 -0400 Subject: Fix test descriptions and remove unnecessary proptypes. --- .../send_/send-content/send-gas-row/send-gas-row.component.js | 7 ------- .../send_/send-content/tests/send-content-component.test.js | 10 +++++----- .../send_/send-footer/tests/send-footer-component.test.js | 3 ++- .../send_/send-header/tests/send-header-component.test.js | 3 ++- ui/app/components/send_/tests/send-component.test.js | 1 + 5 files changed, 10 insertions(+), 14 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js index c62c110e0..581f012b6 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js @@ -6,18 +6,11 @@ import GasFeeDisplay from '../../../send/gas-fee-display-v2' export default class SendGasRow extends Component { static propTypes = { - closeFromDropdown: PropTypes.func, conversionRate: PropTypes.number, convertedCurrency: PropTypes.string, - from: PropTypes.string, - fromAccounts: PropTypes.array, - fromDropdownOpen: PropTypes.bool, gasLoadingError: PropTypes.bool, gasTotal: PropTypes.string, - openFromDropdown: PropTypes.func, showCustomizeGasModal: PropTypes.func, - tokenContract: PropTypes.object, - updateSendFrom: PropTypes.func, }; render () { diff --git a/ui/app/components/send_/send-content/tests/send-content-component.test.js b/ui/app/components/send_/send-content/tests/send-content-component.test.js index 785545861..d5bb6693c 100644 --- a/ui/app/components/send_/send-content/tests/send-content-component.test.js +++ b/ui/app/components/send_/send-content/tests/send-content-component.test.js @@ -9,7 +9,7 @@ import SendFromRow from '../send-from-row/send-from-row.container' import SendGasRow from '../send-gas-row/send-gas-row.container' import SendToRow from '../send-to-row/send-to-row.container' -describe('Send Component', function () { +describe('SendContent Component', function () { let wrapper beforeEach(() => { @@ -29,10 +29,10 @@ describe('Send Component', function () { it('should render the correct row components as grandchildren of the PageContainerContent component', () => { const PageContainerContentChild = wrapper.find(PageContainerContent).children() - PageContainerContentChild.childAt(0).is(SendFromRow) - PageContainerContentChild.childAt(1).is(SendToRow) - PageContainerContentChild.childAt(2).is(SendAmountRow) - PageContainerContentChild.childAt(3).is(SendGasRow) + assert(PageContainerContentChild.childAt(0).is(SendFromRow)) + assert(PageContainerContentChild.childAt(1).is(SendToRow)) + assert(PageContainerContentChild.childAt(2).is(SendAmountRow)) + assert(PageContainerContentChild.childAt(3).is(SendGasRow)) }) }) }) diff --git a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js index f8cbd41f3..4689434d4 100644 --- a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js +++ b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js @@ -21,8 +21,9 @@ const MOCK_EVENT = { preventDefault: () => {} } sinon.spy(SendFooter.prototype, 'onCancel') sinon.spy(SendFooter.prototype, 'onSubmit') -describe('Send Component', function () { +describe('SendFooter Component', function () { let wrapper + let instance beforeEach(() => { wrapper = shallow( { wrapper = shallow( { wrapper = shallow( Date: Thu, 10 May 2018 12:17:05 -0400 Subject: Unit tests for send from, gas, to and wrapper row components. --- .../tests/send-from-row-component.test.js | 125 ++++++++++++++++++++ .../tests/send-gas-row-component.test.js | 69 +++++++++++ .../tests/send-row-wrapper-component.test.js | 79 +++++++++++++ .../tests/send-to-row-component.test.js | 130 +++++++++++++++++++++ 4 files changed, 403 insertions(+) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js index e69de29bb..16463abf3 100644 --- a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js +++ b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js @@ -0,0 +1,125 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import SendFromRow from '../send-from-row.component.js' + +import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' +import FromDropdown from '../from-dropdown/from-dropdown.component' + +const propsMethodSpies = { + closeFromDropdown: sinon.spy(), + openFromDropdown: sinon.spy(), + updateSendFrom: sinon.spy(), + setSendTokenBalance: sinon.spy(), +} + +const MOCK_EVENT = { preventDefault: () => {} } + +sinon.spy(SendFromRow.prototype, 'handleFromChange') + +describe('SendFromRow Component', function () { + let wrapper + let instance + + beforeEach(() => { + wrapper = shallow(, { context: { t: str => str + '_t' } }) + instance = wrapper.instance() + }) + + afterEach(() => { + propsMethodSpies.closeFromDropdown.resetHistory() + propsMethodSpies.openFromDropdown.resetHistory() + propsMethodSpies.updateSendFrom.resetHistory() + propsMethodSpies.setSendTokenBalance.resetHistory() + SendFromRow.prototype.handleFromChange.resetHistory() + }) + + describe('handleFromChange', () => { + + it('should call updateSendFrom', () => { + assert.equal(propsMethodSpies.updateSendFrom.callCount, 0) + instance.handleFromChange('mockFrom') + assert.equal(propsMethodSpies.updateSendFrom.callCount, 1) + assert.deepEqual( + propsMethodSpies.updateSendFrom.getCall(0).args, + ['mockFrom'] + ) + }) + + it('should call tokenContract.balanceOf and setSendTokenBalance if tokenContract is defined', async () => { + wrapper.setProps({ + tokenContract: { + balanceOf: () => new Promise((resolve) => resolve('mockUsersToken')) + } + }) + assert.equal(propsMethodSpies.setSendTokenBalance.callCount, 0) + await instance.handleFromChange('mockFrom') + assert.equal(propsMethodSpies.setSendTokenBalance.callCount, 1) + assert.deepEqual( + propsMethodSpies.setSendTokenBalance.getCall(0).args, + ['mockUsersToken'] + ) + }) + + }) + + describe('render', () => { + it('should render a SendRowWrapper component', () => { + assert.equal(wrapper.find(SendRowWrapper).length, 1) + }) + + it('should pass the correct props to SendRowWrapper', () => { + const { + errorType, + label, + showError, + } = wrapper.find(SendRowWrapper).props() + + assert.equal(label, 'from_t:') + }) + + it('should render an FromDropdown as a child of the SendRowWrapper', () => { + assert(wrapper.find(SendRowWrapper).childAt(0).is(FromDropdown)) + }) + + it('should render the FromDropdown with the correct props', () => { + const { + accounts, + closeDropdown, + conversionRate, + dropdownOpen, + onSelect, + openDropdown, + selectedAccount, + } = wrapper.find(SendRowWrapper).childAt(0).props() + assert.deepEqual(accounts, ['mockAccount']) + assert.equal(dropdownOpen, false) + assert.equal(conversionRate, 15) + assert.deepEqual(selectedAccount, { address: 'mockAddress' }) + assert.equal(propsMethodSpies.closeFromDropdown.callCount, 0) + closeDropdown() + assert.equal(propsMethodSpies.closeFromDropdown.callCount, 1) + assert.equal(propsMethodSpies.openFromDropdown.callCount, 0) + openDropdown() + assert.equal(propsMethodSpies.openFromDropdown.callCount, 1) + assert.equal(SendFromRow.prototype.handleFromChange.callCount, 0) + onSelect('mockNewFrom') + assert.equal(SendFromRow.prototype.handleFromChange.callCount, 1) + assert.deepEqual( + SendFromRow.prototype.handleFromChange.getCall(0).args, + ['mockNewFrom'] + ) + }) + }) +}) diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js index e69de29bb..a96e8c8bb 100644 --- a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js +++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js @@ -0,0 +1,69 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import SendGasRow from '../send-gas-row.component.js' + +import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' +import GasFeeDisplay from '../../../../send/gas-fee-display-v2' + +const propsMethodSpies = { + showCustomizeGasModal: sinon.spy(), +} + +const MOCK_EVENT = { preventDefault: () => {} } + +describe('SendGasRow Component', function () { + let wrapper + let instance + + beforeEach(() => { + wrapper = shallow(, { context: { t: str => str + '_t' } }) + instance = wrapper.instance() + }) + + afterEach(() => { + propsMethodSpies.showCustomizeGasModal.resetHistory() + }) + + describe('render', () => { + it('should render a SendRowWrapper component', () => { + assert.equal(wrapper.find(SendRowWrapper).length, 1) + }) + + it('should pass the correct props to SendRowWrapper', () => { + const { + label, + } = wrapper.find(SendRowWrapper).props() + + assert.equal(label, 'gasFee_t:') + }) + + it('should render a GasFeeDisplay as a child of the SendRowWrapper', () => { + assert(wrapper.find(SendRowWrapper).childAt(0).is(GasFeeDisplay)) + }) + + it('should render the GasFeeDisplay with the correct props', () => { + const { + conversionRate, + convertedCurrency, + gasLoadingError, + gasTotal, + onClick, + } = wrapper.find(SendRowWrapper).childAt(0).props() + assert.equal(conversionRate,20) + assert.equal(convertedCurrency,'mockConvertedCurrency') + assert.equal(gasLoadingError, false) + assert.equal(gasTotal,'mockGasTotal') + assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 0) + onClick() + assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 1) + }) + }) +}) diff --git a/ui/app/components/send_/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js b/ui/app/components/send_/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js index e69de29bb..30280e1d0 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js @@ -0,0 +1,79 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import SendRowWrapper from '../send-row-wrapper.component.js' + +import SendRowErrorMessage from '../send-row-error-message/send-row-error-message.container' + +describe('SendContent Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow( + Mock Form Field + ) + }) + + describe('render', () => { + it('should render a div with a send-v2__form-row class', () => { + assert.equal(wrapper.find('div.send-v2__form-row').length, 1) + }) + + it('should render two children of the root div, with send-v2_form label and field classes', () => { + assert.equal(wrapper.find('.send-v2__form-row > .send-v2__form-label').length, 1) + assert.equal(wrapper.find('.send-v2__form-row > .send-v2__form-field').length, 1) + }) + + it('should render the label as a child of the send-v2__form-label', () => { + assert.equal(wrapper.find('.send-v2__form-row > .send-v2__form-label').childAt(0).text(), 'mockLabel') + }) + + it('should render its first child as a child of the send-v2__form-field', () => { + assert.equal(wrapper.find('.send-v2__form-row > .send-v2__form-field').childAt(0).text(), 'Mock Form Field') + }) + + it('should not render a SendRowErrorMessage if showError is false', () => { + assert.equal(wrapper.find(SendRowErrorMessage).length, 0) + }) + + it('should render a SendRowErrorMessage with and errorType props if showError is true', () => { + wrapper.setProps({showError: true}) + assert.equal(wrapper.find(SendRowErrorMessage).length, 1) + + const expectedSendRowErrorMessage = wrapper.find('.send-v2__form-row > .send-v2__form-label').childAt(1) + assert(expectedSendRowErrorMessage.is(SendRowErrorMessage)) + assert.deepEqual( + expectedSendRowErrorMessage.props(), + { errorType: 'mockErrorType' } + ) + }) + + it('should render its second child as a child of the send-v2__form-field, if it has two children', () => { + wrapper = shallow( + Mock Custom Label Content + Mock Form Field + ) + assert.equal(wrapper.find('.send-v2__form-row > .send-v2__form-field').childAt(0).text(), 'Mock Form Field') + }) + + it('should render its first child as the last child of the send-v2__form-label, if it has two children', () => { + wrapper = shallow( + Mock Custom Label Content + Mock Form Field + ) + assert.equal(wrapper.find('.send-v2__form-row > .send-v2__form-label').childAt(1).text(), 'Mock Custom Label Content') + }) + }) +}) diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js index e69de29bb..a4084a360 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js @@ -0,0 +1,130 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import SendToRow from '../send-to-row.component.js' + +import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' +import EnsInput from '../../../../ens-input' + +const propsMethodSpies = { + closeToDropdown: sinon.spy(), + openToDropdown: sinon.spy(), + updateSendTo: sinon.spy(), + updateSendToError: sinon.spy(), +} + +const MOCK_EVENT = { preventDefault: () => {} } + +sinon.spy(SendToRow.prototype, 'handleToChange') + +describe('SendToRow Component', function () { + let wrapper + let instance + + beforeEach(() => { + wrapper = shallow(, { context: { t: str => str + '_t' } }) + instance = wrapper.instance() + }) + + afterEach(() => { + propsMethodSpies.closeToDropdown.resetHistory() + propsMethodSpies.openToDropdown.resetHistory() + propsMethodSpies.updateSendTo.resetHistory() + propsMethodSpies.updateSendToError.resetHistory() + SendToRow.prototype.handleToChange.resetHistory() + }) + + describe('handleToChange', () => { + + it('should call updateSendTo', () => { + assert.equal(propsMethodSpies.updateSendTo.callCount, 0) + instance.handleToChange('mockTo2', 'mockNickname') + assert.equal(propsMethodSpies.updateSendTo.callCount, 1) + assert.deepEqual( + propsMethodSpies.updateSendTo.getCall(0).args, + ['mockTo2', 'mockNickname'] + ) + }) + + it('should call updateSendToError', () => { + assert.equal(propsMethodSpies.updateSendToError.callCount, 0) + instance.handleToChange('mockTo2') + assert.equal(propsMethodSpies.updateSendToError.callCount, 1) + assert.deepEqual( + propsMethodSpies.updateSendToError.getCall(0).args, + ['mockTo2'] + ) + }) + + }) + + describe('render', () => { + it('should render a SendRowWrapper component', () => { + assert.equal(wrapper.find(SendRowWrapper).length, 1) + }) + + it('should pass the correct props to SendRowWrapper', () => { + const { + errorType, + label, + showError, + } = wrapper.find(SendRowWrapper).props() + + assert.equal(errorType, 'to') + + assert.equal(label, 'to_t:') + + assert.equal(showError, false) + }) + + it('should render an EnsInput as a child of the SendRowWrapper', () => { + assert(wrapper.find(SendRowWrapper).childAt(0).is(EnsInput)) + }) + + it('should render the EnsInput with the correct props', () => { + const { + accounts, + closeDropdown, + dropdownOpen, + inError, + name, + network, + onChange, + openDropdown, + placeholder, + to, + } = wrapper.find(SendRowWrapper).childAt(0).props() + assert.deepEqual(accounts, ['mockAccount']) + assert.equal(dropdownOpen, false) + assert.equal(inError, false) + assert.equal(name, 'address') + assert.equal(network, 'mockNetwork') + assert.equal(placeholder, 'recipientAddress_t') + assert.equal(to, 'mockTo') + assert.equal(propsMethodSpies.closeToDropdown.callCount, 0) + closeDropdown() + assert.equal(propsMethodSpies.closeToDropdown.callCount, 1) + assert.equal(propsMethodSpies.openToDropdown.callCount, 0) + openDropdown() + assert.equal(propsMethodSpies.openToDropdown.callCount, 1) + assert.equal(SendToRow.prototype.handleToChange.callCount, 0) + onChange('mockNewTo', 'mockNewNickname') + assert.equal(SendToRow.prototype.handleToChange.callCount, 1) + assert.deepEqual( + SendToRow.prototype.handleToChange.getCall(0).args, + ['mockNewTo', 'mockNewNickname'] + ) + }) + }) +}) -- cgit From 5d79d126484a25ed6ad7f07c779ba91900abae7c Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 11 May 2018 04:07:33 -0230 Subject: SendRowErrorMessage component test. --- .../tests/send-row-error-message-component.test.js | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js new file mode 100644 index 000000000..2304a43d2 --- /dev/null +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js @@ -0,0 +1,28 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import SendRowErrorMessage from '../send-row-error-message.component.js' + +describe('SendRowErrorMessage Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow(, { context: { t: str => str + '_t' } }) + }) + + describe('render', () => { + it('should render null if the passed errors do not contain an error of errorType', () => { + assert.equal(wrapper.find('.send-v2__error').length, 0) + assert.equal(wrapper.html(), null) + }) + + it('should render an error message if the passed errors contain an error of errorType', () => { + wrapper.setProps({ errors: { error1: 'abc', error2: 'def', error3: 'xyz' } }) + assert.equal(wrapper.find('.send-v2__error').length, 1) + assert.equal(wrapper.find('.send-v2__error').text(), 'xyz_t') + }) + }) +}) -- cgit From 61d35e7abec6ef138ae08f1ec013da773083609a Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 11 May 2018 13:50:43 -0230 Subject: Unit tests for from-dropdown; split out send-dropdown-list from from-dropdown --- .../send-dropdown-list.component.js | 52 ++++++++++ .../tests/send-dropdown-list-component.test.js | 108 +++++++++++++++++++++ .../from-dropdown/from-dropdown.component.js | 49 ++-------- .../tests/from-dropdown-component.test.js | 90 +++++++++++++++++ 4 files changed, 260 insertions(+), 39 deletions(-) create mode 100644 ui/app/components/send_/send-content/send-dropdown-list/send-dropdown-list.component.js create mode 100644 ui/app/components/send_/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-dropdown-list/send-dropdown-list.component.js b/ui/app/components/send_/send-content/send-dropdown-list/send-dropdown-list.component.js new file mode 100644 index 000000000..7bcc06c3e --- /dev/null +++ b/ui/app/components/send_/send-content/send-dropdown-list/send-dropdown-list.component.js @@ -0,0 +1,52 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import AccountListItem from '../../account-list-item/account-list-item.container' + +export default class SendDropdownList extends Component { + + static propTypes = { + accounts: PropTypes.array, + closeDropdown: PropTypes.func, + onSelect: PropTypes.func, + activeAddress: PropTypes.string, + }; + + getListItemIcon (accountAddress, activeAddress) { + return accountAddress === activeAddress + ? + : null + } + + render () { + const { + accounts, + closeDropdown, + onSelect, + activeAddress, + } = this.props + + return (
+
closeDropdown()} + /> +
+ {accounts.map((account, index) => { + onSelect(account) + closeDropdown() + }} + icon={this.getListItemIcon(account.address, activeAddress)} + key={`send-dropdown-account-#${index}`} + />)} +
+
) + } + +} + +SendDropdownList.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js b/ui/app/components/send_/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js new file mode 100644 index 000000000..44de529d4 --- /dev/null +++ b/ui/app/components/send_/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js @@ -0,0 +1,108 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import SendDropdownList from '../send-dropdown-list.component.js' + +import AccountListItem from '../../../account-list-item/account-list-item.container' + +const propsMethodSpies = { + closeDropdown: sinon.spy(), + onSelect: sinon.spy(), +} + +sinon.spy(SendDropdownList.prototype, 'getListItemIcon') + +describe('SendDropdownList Component', function () { + let wrapper + let instance + + beforeEach(() => { + wrapper = shallow(, { context: { t: str => str + '_t' } }) + instance = wrapper.instance() + }) + + afterEach(() => { + propsMethodSpies.closeDropdown.resetHistory() + propsMethodSpies.onSelect.resetHistory() + SendDropdownList.prototype.getListItemIcon.resetHistory() + }) + + describe('getListItemIcon', () => { + it('should return check icon if the passed addresses are the same', () => { + assert.deepEqual( + wrapper.instance().getListItemIcon('mockAccount0', 'mockAccount0'), + + ) + }) + + it('should return null if the passed addresses are different', () => { + assert.equal( + wrapper.instance().getListItemIcon('mockAccount0', 'mockAccount1'), + null + ) + }) + }) + + describe('render', () => { + it('should render a single div with two children', () => { + assert(wrapper.is('div')) + assert.equal(wrapper.children().length, 2) + }) + + it('should render the children with the correct classes', () => { + assert(wrapper.childAt(0).hasClass('send-v2__from-dropdown__close-area')) + assert(wrapper.childAt(1).hasClass('send-v2__from-dropdown__list')) + }) + + it('should call closeDropdown onClick of the send-v2__from-dropdown__close-area', () => { + assert.equal(propsMethodSpies.closeDropdown.callCount, 0) + wrapper.childAt(0).props().onClick() + assert.equal(propsMethodSpies.closeDropdown.callCount, 1) + }) + + it('should render an AccountListItem for each item in accounts', () => { + assert.equal(wrapper.childAt(1).children().length, 3) + assert(wrapper.childAt(1).children().every(AccountListItem)) + }) + + it('should pass the correct props to the AccountListItem', () => { + wrapper.childAt(1).children().forEach((accountListItem, index) => { + const { + account, + className, + handleClick, + icon, + } = accountListItem.props() + assert.deepEqual(account, { address: 'mockAccount' + index }) + assert.equal(className, 'account-list-item__dropdown') + assert.equal(propsMethodSpies.onSelect.callCount, 0) + handleClick() + assert.equal(propsMethodSpies.onSelect.callCount, 1) + assert.deepEqual(propsMethodSpies.onSelect.getCall(0).args[0], { address: 'mockAccount' + index }) + propsMethodSpies.onSelect.resetHistory() + propsMethodSpies.closeDropdown.resetHistory() + assert.equal(propsMethodSpies.closeDropdown.callCount, 0) + handleClick() + assert.equal(propsMethodSpies.closeDropdown.callCount, 1) + propsMethodSpies.onSelect.resetHistory() + propsMethodSpies.closeDropdown.resetHistory() + }) + }) + + it('should call this.getListItemIcon for each AccountListItem', () => { + assert.equal(SendDropdownList.prototype.getListItemIcon.callCount, 3) + const getListItemIconCalls = SendDropdownList.prototype.getListItemIcon.getCalls() + assert(getListItemIconCalls.every(({ args }, index) => args[0] === 'mockAccount' + index)) + }) + }) +}) diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js index 337228122..7815887a5 100644 --- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js +++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js @@ -1,6 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import AccountListItem from '../../../account-list-item/account-list-item.container' +import SendDropdownList from '../../send-dropdown-list/send-dropdown-list.component' export default class FromDropdown extends Component { @@ -13,58 +14,28 @@ export default class FromDropdown extends Component { selectedAccount: PropTypes.object, }; - renderListItemIcon (icon, color) { - return - } - - getListItemIcon (currentAccount, selectedAccount) { - return currentAccount.address === selectedAccount.address - ? this.renderListItemIcon('fa-check', '#02c9b1') - : null - } - - renderDropdown () { + render () { const { accounts, closeDropdown, - onSelect, - selectedAccount, - } = this.props - - return (
-
closeDropdown} - /> -
- {accounts.map((account, index) => { - onSelect(account) - closeDropdown() - }} - icon={this.getListItemIcon(account, selectedAccount.address)} - key={`from-dropdown-account-#${index}`} - />)} -
-
) - } - - render () { - const { dropdownOpen, openDropdown, selectedAccount, + onSelect, } = this.props return
} /> - {dropdownOpen && this.renderDropdown()}, + {dropdownOpen && },
} diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js index e69de29bb..333775341 100644 --- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js +++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js @@ -0,0 +1,90 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import FromDropdown from '../from-dropdown.component.js' + +import AccountListItem from '../../../../account-list-item/account-list-item.container' +import SendDropdownList from '../../../send-dropdown-list/send-dropdown-list.component' + +const propsMethodSpies = { + closeDropdown: sinon.spy(), + openDropdown: sinon.spy(), + onSelect: sinon.spy(), +} + +describe('FromDropdown Component', function () { + let wrapper + let instance + + beforeEach(() => { + wrapper = shallow(, { context: { t: str => str + '_t' } }) + instance = wrapper.instance() + }) + + afterEach(() => { + propsMethodSpies.closeDropdown.resetHistory() + propsMethodSpies.openDropdown.resetHistory() + propsMethodSpies.onSelect.resetHistory() + }) + + describe('render', () => { + it('should render a div with a .send-v2__from-dropdown class', () => { + assert.equal(wrapper.find('.send-v2__from-dropdown').length, 1) + }) + + it('should render an AccountListItem as the first child of the .send-v2__from-dropdown div', () => { + assert(wrapper.find('.send-v2__from-dropdown').childAt(0).is(AccountListItem)) + }) + + it('should pass the correct props to AccountListItem', () => { + const { + account, + handleClick, + icon, + } = wrapper.find('.send-v2__from-dropdown').childAt(0).props() + assert.deepEqual(account, { address: 'mockAddress' }) + assert.deepEqual( + icon, + + ) + assert.equal(propsMethodSpies.openDropdown.callCount, 0) + handleClick() + assert.equal(propsMethodSpies.openDropdown.callCount, 1) + }) + + it('should not render a SendDropdownList when dropdownOpen is false', () => { + assert.equal(wrapper.find(SendDropdownList).length, 0) + }) + + it('should render a SendDropdownList when dropdownOpen is true', () => { + wrapper.setProps({ dropdownOpen: true }) + assert(wrapper.find(SendDropdownList).length, 1) + }) + + it('should pass the correct props to the SendDropdownList]', () => { + wrapper.setProps({ dropdownOpen: true }) + const { + accounts, + closeDropdown, + onSelect, + activeAddress, + } = wrapper.find(SendDropdownList).props() + assert.deepEqual(accounts, ['mockAccount']) + assert.equal(activeAddress, 'mockAddress') + assert.equal(propsMethodSpies.closeDropdown.callCount, 0) + closeDropdown() + assert.equal(propsMethodSpies.closeDropdown.callCount, 1) + assert.equal(propsMethodSpies.onSelect.callCount, 0) + onSelect() + assert.equal(propsMethodSpies.onSelect.callCount, 1) + }) + }) +}) -- cgit From 145e53b404af6adb49fba2636474455aba86ff81 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 14 May 2018 06:55:03 -0230 Subject: Unit tests for account-list-item, amount-max-button and send-amount-row components. --- .../tests/account-list-item-component.test.js | 129 ++++++++++++++++ .../tests/amount-max-button-component.test.js | 90 ++++++++++++ .../tests/send-amount-row-component.test.js | 162 +++++++++++++++++++++ 3 files changed, 381 insertions(+) create mode 100644 ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js (limited to 'ui') diff --git a/ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js b/ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js new file mode 100644 index 000000000..1a98934f5 --- /dev/null +++ b/ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js @@ -0,0 +1,129 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import proxyquire from 'proxyquire' +import Identicon from '../../../identicon' +import CurrencyDisplay from '../../../send/currency-display' + +const utilsMethodStubs = { + checksumAddress: sinon.stub().returns('mockCheckSumAddress') +} + +const AccountListItem = proxyquire('../account-list-item.component.js', { + '../../../util': utilsMethodStubs, +}).default + + +const propsMethodSpies = { + handleClick: sinon.spy(), +} + +describe('AccountListItem Component', function () { + let wrapper + let instance + + beforeEach(() => { + wrapper = shallow(} + />, { context: { t: str => str + '_t' } }) + instance = wrapper.instance() + }) + + afterEach(() => { + propsMethodSpies.handleClick.resetHistory() + }) + + describe('render', () => { + it('should render a div with the passed className', () => { + assert.equal(wrapper.find('.mockClassName').length, 1) + assert(wrapper.find('.mockClassName').is('div')) + assert(wrapper.find('.mockClassName').hasClass('account-list-item')) + }) + + it('should have a top row div', () => { + assert.equal(wrapper.find('.mockClassName > .account-list-item__top-row').length, 1) + assert(wrapper.find('.mockClassName > .account-list-item__top-row').is('div')) + }) + + it('should have an identicon, name and icon in the top row', () => { + const topRow = wrapper.find('.mockClassName > .account-list-item__top-row') + assert.equal(topRow.find(Identicon).length, 1) + assert.equal(topRow.find('.account-list-item__account-name').length, 1) + assert.equal(topRow.find('.account-list-item__icon').length, 1) + }) + + it('should show the account name if it exists', () => { + const topRow = wrapper.find('.mockClassName > .account-list-item__top-row') + assert.equal(topRow.find('.account-list-item__account-name').text(), 'mockName') + }) + + it('should show the account address if there is no name', () => { + wrapper.setProps({ account: { address: 'addressButNoName' } }) + const topRow = wrapper.find('.mockClassName > .account-list-item__top-row') + assert.equal(topRow.find('.account-list-item__account-name').text(), 'addressButNoName') + }) + + it('should render the passed icon', () => { + const topRow = wrapper.find('.mockClassName > .account-list-item__top-row') + assert(topRow.find('.account-list-item__icon').childAt(0).is('i')) + assert(topRow.find('.account-list-item__icon').childAt(0).hasClass('mockIcon')) + }) + + it('should not render an icon if none is passed', () => { + wrapper.setProps({ icon: null }) + const topRow = wrapper.find('.mockClassName > .account-list-item__top-row') + assert.equal(topRow.find('.account-list-item__icon').length, 0) + }) + + it('should render the account address as a checksumAddress if displayAddress is true and name is provided', () => { + wrapper.setProps({ displayAddress: true }) + assert.equal(wrapper.find('.account-list-item__account-address').length, 1) + assert.equal(wrapper.find('.account-list-item__account-address').text(), 'mockCheckSumAddress') + assert.deepEqual( + utilsMethodStubs.checksumAddress.getCall(0).args, + ['mockAddress'] + ) + }) + + it('should not render the account address as a checksumAddress if displayAddress is false', () => { + wrapper.setProps({ displayAddress: false }) + assert.equal(wrapper.find('.account-list-item__account-address').length, 0) + }) + + it('should not render the account address as a checksumAddress if name is not provided', () => { + wrapper.setProps({ account: { address: 'someAddressButNoName' } }) + assert.equal(wrapper.find('.account-list-item__account-address').length, 0) + }) + + it('should render a CurrencyDisplay with the correct props if displayBalance is true', () => { + wrapper.setProps({ displayBalance: true }) + assert.equal(wrapper.find(CurrencyDisplay).length, 1) + assert.deepEqual( + wrapper.find(CurrencyDisplay).props(), + { + className: 'account-list-item__account-balances', + conversionRate: 4, + convertedBalanceClassName: 'account-list-item__account-secondary-balance', + convertedCurrency: 'mockCurrentyCurrency', + primaryBalanceClassName: 'account-list-item__account-primary-balance', + primaryCurrency: 'ETH', + readOnly: true, + value: 'mockBalance', + } + ) + }) + + it('should not render a CurrencyDisplay if displayBalance is false', () => { + wrapper.setProps({ displayBalance: false }) + assert.equal(wrapper.find(CurrencyDisplay).length, 0) + }) + }) +}) diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js index e69de29bb..86a05ff21 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js @@ -0,0 +1,90 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import AmountMaxButton from '../amount-max-button.component.js' + +const propsMethodSpies = { + setAmountToMax: sinon.spy(), + setMaxModeTo: sinon.spy(), +} + +const MOCK_EVENT = { preventDefault: () => {} } + +sinon.spy(AmountMaxButton.prototype, 'setMaxAmount') + +describe('AmountMaxButton Component', function () { + let wrapper + let instance + + beforeEach(() => { + wrapper = shallow(, { context: { t: str => str + '_t' } }) + instance = wrapper.instance() + }) + + afterEach(() => { + propsMethodSpies.setAmountToMax.resetHistory() + propsMethodSpies.setMaxModeTo.resetHistory() + AmountMaxButton.prototype.setMaxAmount.resetHistory() + }) + + describe('setMaxAmount', () => { + + it('should call setAmountToMax with the correct params', () => { + assert.equal(propsMethodSpies.setAmountToMax.callCount, 0) + instance.setMaxAmount() + assert.equal(propsMethodSpies.setAmountToMax.callCount, 1) + assert.deepEqual( + propsMethodSpies.setAmountToMax.getCall(0).args, + [{ + balance: 'mockBalance', + gasTotal: 'mockGasTotal', + selectedToken: { address: 'mockTokenAddress' }, + tokenBalance: 'mockTokenBalance', + }] + ) + }) + + }) + + describe('render', () => { + it('should render a div with a send-v2__amount-max class', () => { + assert.equal(wrapper.find('.send-v2__amount-max').length, 1) + assert(wrapper.find('.send-v2__amount-max').is('div')) + }) + + it('should call setMaxModeTo and setMaxAmount when the send-v2__amount-max div is clicked', () => { + const { + onClick, + } = wrapper.find('.send-v2__amount-max').props() + + assert.equal(AmountMaxButton.prototype.setMaxAmount.callCount, 0) + assert.equal(propsMethodSpies.setMaxModeTo.callCount, 0) + onClick(MOCK_EVENT) + assert.equal(AmountMaxButton.prototype.setMaxAmount.callCount, 1) + assert.equal(propsMethodSpies.setMaxModeTo.callCount, 1) + assert.deepEqual( + propsMethodSpies.setMaxModeTo.getCall(0).args, + [true] + ) + }) + + it('should not render text when maxModeOn is true', () => { + wrapper.setProps({ maxModeOn: true }) + assert.equal(wrapper.find('.send-v2__amount-max').text(), '') + }) + + it('should render the expected text when maxModeOn is false', () => { + wrapper.setProps({ maxModeOn: false }) + assert.equal(wrapper.find('.send-v2__amount-max').text(), 'max_t') + }) + }) +}) diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js index e69de29bb..4f884eb69 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js @@ -0,0 +1,162 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import SendAmountRow from '../send-amount-row.component.js' + +import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' +import AmountMaxButton from '../amount-max-button/amount-max-button.container' +import CurrencyDisplay from '../../../../send/currency-display' + +const propsMethodSpies = { + setMaxModeTo: sinon.spy(), + updateSendAmount: sinon.spy(), + updateSendAmountError: sinon.spy(), +} + +const MOCK_EVENT = { preventDefault: () => {} } + +sinon.spy(SendAmountRow.prototype, 'handleAmountChange') +sinon.spy(SendAmountRow.prototype, 'validateAmount') + +describe('SendAmountRow Component', function () { + let wrapper + let instance + + beforeEach(() => { + wrapper = shallow(, { context: { t: str => str + '_t' } }) + instance = wrapper.instance() + }) + + afterEach(() => { + propsMethodSpies.setMaxModeTo.resetHistory() + propsMethodSpies.updateSendAmount.resetHistory() + propsMethodSpies.updateSendAmountError.resetHistory() + SendAmountRow.prototype.validateAmount.resetHistory() + SendAmountRow.prototype.handleAmountChange.resetHistory() + }) + + describe('validateAmount', () => { + + it('should call updateSendAmountError with the correct params', () => { + assert.equal(propsMethodSpies.updateSendAmountError.callCount, 0) + instance.validateAmount('someAmount') + assert.equal(propsMethodSpies.updateSendAmountError.callCount, 1) + assert.deepEqual( + propsMethodSpies.updateSendAmountError.getCall(0).args, + [{ + amount: 'someAmount', + amountConversionRate: 'mockAmountConversionRate', + balance: 'mockBalance', + conversionRate: 7, + gasTotal: 'mockGasTotal', + primaryCurrency: 'mockPrimaryCurrency', + selectedToken: { address: 'mockTokenAddress' }, + tokenBalance: 'mockTokenBalance', + }] + ) + }) + + }) + + describe('handleAmountChange', () => { + + it('should call setMaxModeTo', () => { + assert.equal(propsMethodSpies.setMaxModeTo.callCount, 0) + instance.handleAmountChange('someAmount') + assert.equal(propsMethodSpies.setMaxModeTo.callCount, 1) + assert.deepEqual( + propsMethodSpies.setMaxModeTo.getCall(0).args, + [false] + ) + }) + + it('should call this.validateAmount', () => { + assert.equal(SendAmountRow.prototype.validateAmount.callCount, 0) + instance.handleAmountChange('someAmount') + assert.equal(SendAmountRow.prototype.validateAmount.callCount, 1) + assert.deepEqual( + propsMethodSpies.updateSendAmount.getCall(0).args, + ['someAmount'] + ) + }) + + it('should call updateSendAmount', () => { + assert.equal(propsMethodSpies.updateSendAmount.callCount, 0) + instance.handleAmountChange('someAmount') + assert.equal(propsMethodSpies.updateSendAmount.callCount, 1) + assert.deepEqual( + propsMethodSpies.updateSendAmount.getCall(0).args, + ['someAmount'] + ) + }) + + }) + + describe('render', () => { + it('should render a SendRowWrapper component', () => { + assert.equal(wrapper.find(SendRowWrapper).length, 1) + }) + + it('should pass the correct props to SendRowWrapper', () => { + const { + errorType, + label, + showError, + } = wrapper.find(SendRowWrapper).props() + + assert.equal(errorType, 'amount') + + assert.equal(label, 'amount_t:') + + assert.equal(showError, false) + }) + + it('should render an AmountMaxButton as the first child of the SendRowWrapper', () => { + assert(wrapper.find(SendRowWrapper).childAt(0).is(AmountMaxButton)) + }) + + it('should render a CurrencyDisplay as the second child of the SendRowWrapper', () => { + assert(wrapper.find(SendRowWrapper).childAt(1).is(CurrencyDisplay)) + }) + + it('should render the CurrencyDisplay with the correct props', () => { + const { + conversionRate, + convertedCurrency, + handleChange, + inError, + primaryCurrency, + selectedToken, + value, + } = wrapper.find(SendRowWrapper).childAt(1).props() + assert.equal(conversionRate, 'mockAmountConversionRate') + assert.equal(convertedCurrency, 'mockConvertedCurrency') + assert.equal(inError, false) + assert.equal(primaryCurrency, 'mockPrimaryCurrency') + assert.deepEqual(selectedToken, { address: 'mockTokenAddress' }) + assert.equal(value, 'mockAmount') + assert.equal(SendAmountRow.prototype.handleAmountChange.callCount, 0) + handleChange('mockNewAmount') + assert.equal(SendAmountRow.prototype.handleAmountChange.callCount, 1) + assert.deepEqual( + SendAmountRow.prototype.handleAmountChange.getCall(0).args, + ['mockNewAmount'] + ) + }) + }) +}) -- cgit From c2ed2d4e5003abd01552570452a5b0b38626abca Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 14 May 2018 07:01:41 -0230 Subject: Lint fixes --- .../tests/account-list-item-component.test.js | 4 +--- .../send-amount-row/tests/send-amount-row-component.test.js | 2 -- .../tests/send-dropdown-list-component.test.js | 3 --- .../from-dropdown/tests/from-dropdown-component.test.js | 2 -- .../send-from-row/tests/send-from-row-component.test.js | 8 ++------ .../send-gas-row/tests/send-gas-row-component.test.js | 10 +++------- .../send-to-row/tests/send-to-row-component.test.js | 2 -- .../send_/send-footer/tests/send-footer-component.test.js | 2 -- .../send_/send-header/tests/send-header-component.test.js | 2 -- ui/app/components/send_/tests/send-component.test.js | 2 -- 10 files changed, 6 insertions(+), 31 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js b/ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js index 1a98934f5..f312896c4 100644 --- a/ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js +++ b/ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js @@ -7,7 +7,7 @@ import Identicon from '../../../identicon' import CurrencyDisplay from '../../../send/currency-display' const utilsMethodStubs = { - checksumAddress: sinon.stub().returns('mockCheckSumAddress') + checksumAddress: sinon.stub().returns('mockCheckSumAddress'), } const AccountListItem = proxyquire('../account-list-item.component.js', { @@ -21,7 +21,6 @@ const propsMethodSpies = { describe('AccountListItem Component', function () { let wrapper - let instance beforeEach(() => { wrapper = shallow(} />, { context: { t: str => str + '_t' } }) - instance = wrapper.instance() }) afterEach(() => { diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js index 4f884eb69..8355ebf10 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js @@ -14,8 +14,6 @@ const propsMethodSpies = { updateSendAmountError: sinon.spy(), } -const MOCK_EVENT = { preventDefault: () => {} } - sinon.spy(SendAmountRow.prototype, 'handleAmountChange') sinon.spy(SendAmountRow.prototype, 'validateAmount') diff --git a/ui/app/components/send_/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js b/ui/app/components/send_/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js index 44de529d4..b92dd4dfe 100644 --- a/ui/app/components/send_/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js +++ b/ui/app/components/send_/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js @@ -15,7 +15,6 @@ sinon.spy(SendDropdownList.prototype, 'getListItemIcon') describe('SendDropdownList Component', function () { let wrapper - let instance beforeEach(() => { wrapper = shallow(, { context: { t: str => str + '_t' } }) - instance = wrapper.instance() }) afterEach(() => { @@ -81,7 +79,6 @@ describe('SendDropdownList Component', function () { account, className, handleClick, - icon, } = accountListItem.props() assert.deepEqual(account, { address: 'mockAccount' + index }) assert.equal(className, 'account-list-item__dropdown') diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js index 333775341..84fcb281e 100644 --- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js +++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js @@ -15,7 +15,6 @@ const propsMethodSpies = { describe('FromDropdown Component', function () { let wrapper - let instance beforeEach(() => { wrapper = shallow(, { context: { t: str => str + '_t' } }) - instance = wrapper.instance() }) afterEach(() => { diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js index 16463abf3..9ba8d1739 100644 --- a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js +++ b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js @@ -14,8 +14,6 @@ const propsMethodSpies = { setSendTokenBalance: sinon.spy(), } -const MOCK_EVENT = { preventDefault: () => {} } - sinon.spy(SendFromRow.prototype, 'handleFromChange') describe('SendFromRow Component', function () { @@ -60,8 +58,8 @@ describe('SendFromRow Component', function () { it('should call tokenContract.balanceOf and setSendTokenBalance if tokenContract is defined', async () => { wrapper.setProps({ tokenContract: { - balanceOf: () => new Promise((resolve) => resolve('mockUsersToken')) - } + balanceOf: () => new Promise((resolve) => resolve('mockUsersToken')), + }, }) assert.equal(propsMethodSpies.setSendTokenBalance.callCount, 0) await instance.handleFromChange('mockFrom') @@ -81,9 +79,7 @@ describe('SendFromRow Component', function () { it('should pass the correct props to SendRowWrapper', () => { const { - errorType, label, - showError, } = wrapper.find(SendRowWrapper).props() assert.equal(label, 'from_t:') diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js index a96e8c8bb..e4f05d708 100644 --- a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js +++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js @@ -11,11 +11,8 @@ const propsMethodSpies = { showCustomizeGasModal: sinon.spy(), } -const MOCK_EVENT = { preventDefault: () => {} } - describe('SendGasRow Component', function () { let wrapper - let instance beforeEach(() => { wrapper = shallow(, { context: { t: str => str + '_t' } }) - instance = wrapper.instance() }) afterEach(() => { @@ -57,10 +53,10 @@ describe('SendGasRow Component', function () { gasTotal, onClick, } = wrapper.find(SendRowWrapper).childAt(0).props() - assert.equal(conversionRate,20) - assert.equal(convertedCurrency,'mockConvertedCurrency') + assert.equal(conversionRate, 20) + assert.equal(convertedCurrency, 'mockConvertedCurrency') assert.equal(gasLoadingError, false) - assert.equal(gasTotal,'mockGasTotal') + assert.equal(gasTotal, 'mockGasTotal') assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 0) onClick() assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 1) diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js index a4084a360..df6ad6fe8 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js @@ -14,8 +14,6 @@ const propsMethodSpies = { updateSendToError: sinon.spy(), } -const MOCK_EVENT = { preventDefault: () => {} } - sinon.spy(SendToRow.prototype, 'handleToChange') describe('SendToRow Component', function () { diff --git a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js index 4689434d4..c0b8f956f 100644 --- a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js +++ b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js @@ -23,7 +23,6 @@ sinon.spy(SendFooter.prototype, 'onSubmit') describe('SendFooter Component', function () { let wrapper - let instance beforeEach(() => { wrapper = shallow(, { context: { t: str => str } }) - instance = wrapper.instance() }) afterEach(() => { diff --git a/ui/app/components/send_/send-header/tests/send-header-component.test.js b/ui/app/components/send_/send-header/tests/send-header-component.test.js index 17629eb65..c9d6d8023 100644 --- a/ui/app/components/send_/send-header/tests/send-header-component.test.js +++ b/ui/app/components/send_/send-header/tests/send-header-component.test.js @@ -18,7 +18,6 @@ sinon.spy(SendHeader.prototype, 'onClose') describe('SendHeader Component', function () { let wrapper - let instance beforeEach(() => { wrapper = shallow(, { context: { t: str => str } }) - instance = wrapper.instance() }) afterEach(() => { diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js index a2f87dea8..60b160333 100644 --- a/ui/app/components/send_/tests/send-component.test.js +++ b/ui/app/components/send_/tests/send-component.test.js @@ -27,7 +27,6 @@ sinon.spy(SendTransactionScreen.prototype, 'updateGas') describe('Send Component', function () { let wrapper - let instance beforeEach(() => { wrapper = shallow() - instance = wrapper.instance() }) afterEach(() => { -- cgit From b3f08681fd943947526a294759896d093cdbd135 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 14 May 2018 09:43:55 -0230 Subject: Add missing unit tests in send_/: now 100% function test coverage in send_/ --- .../tests/account-list-item-component.test.js | 11 + ui/app/components/send_/send.selectors.js | 1 + .../send_/tests/send-selectors-test-data.js | 45 ++- .../components/send_/tests/send-selectors.test.js | 353 ++++++++++++++------- 4 files changed, 298 insertions(+), 112 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js b/ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js index f312896c4..bb7f3776c 100644 --- a/ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js +++ b/ui/app/components/send_/account-list-item/tests/account-list-item-component.test.js @@ -46,6 +46,17 @@ describe('AccountListItem Component', function () { assert(wrapper.find('.mockClassName').hasClass('account-list-item')) }) + it('should call handleClick with the expected props when the root div is clicked', () => { + const { onClick } = wrapper.find('.mockClassName').props() + assert.equal(propsMethodSpies.handleClick.callCount, 0) + onClick() + assert.equal(propsMethodSpies.handleClick.callCount, 1) + assert.deepEqual( + propsMethodSpies.handleClick.getCall(0).args, + [{ address: 'mockAddress', name: 'mockName', balance: 'mockBalance' }] + ) + }) + it('should have a top row div', () => { assert.equal(wrapper.find('.mockClassName > .account-list-item__top-row').length, 1) assert(wrapper.find('.mockClassName > .account-list-item__top-row').is('div')) diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js index 4fadf442c..476e77cac 100644 --- a/ui/app/components/send_/send.selectors.js +++ b/ui/app/components/send_/send.selectors.js @@ -165,6 +165,7 @@ function getSelectedToken (state) { function getSelectedTokenContract (state) { const selectedToken = getSelectedToken(state) + return selectedToken ? global.eth.contract(abi).at(selectedToken.address) : null diff --git a/ui/app/components/send_/tests/send-selectors-test-data.js b/ui/app/components/send_/tests/send-selectors-test-data.js index bdea759fb..1a95b5f41 100644 --- a/ui/app/components/send_/tests/send-selectors-test-data.js +++ b/ui/app/components/send_/tests/send-selectors-test-data.js @@ -86,9 +86,42 @@ module.exports = { }, }, 'transactions': {}, - 'selectedAddressTxList': [], + 'selectedAddressTxList': [ + { + 'id': 'mockTokenTx1', + 'txParams': { + 'to': '0x8d6b81208414189a58339873ab429b6c47ab92d3' + }, + 'time': 1700000000000 + }, + { + 'id': 'mockTokenTx2', + 'txParams': { + 'to': '0xafaketokenaddress' + }, + 'time': 1600000000000 + }, + { + 'id': 'mockTokenTx3', + 'txParams': { + 'to': '0x8d6b81208414189a58339873ab429b6c47ab92d3' + }, + 'time': 1500000000000 + }, + { + 'id': 'mockEthTx1', + 'txParams': { + 'to': '0xd85a4b6a394794842887b8284293d69163007bbb' + }, + 'time': 1400000000000 + } + ], 'selectedTokenAddress': '0x8d6b81208414189a58339873ab429b6c47ab92d3', - 'unapprovedMsgs': {}, + 'unapprovedMsgs': { + '0xabc': { id: 'unapprovedMessage1', 'time': 1650000000000 }, + '0xdef': { id: 'unapprovedMessage2', 'time': 1550000000000 }, + '0xghi': { id: 'unapprovedMessage3', 'time': 1450000000000 }, + }, 'unapprovedMsgCount': 0, 'unapprovedPersonalMsgs': {}, 'unapprovedPersonalMsgCount': 0, @@ -116,7 +149,11 @@ module.exports = { 'provider': { 'type': 'testnet', }, - 'shapeShiftTxList': [], + 'shapeShiftTxList': [ + { id: 'shapeShiftTx1', 'time': 1675000000000 }, + { id: 'shapeShiftTx2', 'time': 1575000000000 }, + { id: 'shapeShiftTx3', 'time': 1475000000000 }, + ], 'lostAccounts': [], 'send': { 'gasLimit': '0xFFFF', @@ -158,7 +195,7 @@ module.exports = { 'txValue': 'de0b6b3a7640000', 'maxCost': 'de234b52e4a0800', 'gasPrice': '4a817c800', - }, + } }, 'currentLocale': 'en', }, diff --git a/ui/app/components/send_/tests/send-selectors.test.js b/ui/app/components/send_/tests/send-selectors.test.js index 43e7792a0..e2acc15f6 100644 --- a/ui/app/components/send_/tests/send-selectors.test.js +++ b/ui/app/components/send_/tests/send-selectors.test.js @@ -1,4 +1,5 @@ import assert from 'assert' +import sinon from 'sinon' import selectors from '../send.selectors.js' const { accountsWithSendEtherInfoSelector, @@ -20,7 +21,7 @@ const { getSelectedAddress, getSelectedIdentity, getSelectedToken, - // getSelectedTokenContract, + getSelectedTokenContract, getSelectedTokenExchangeRate, getSelectedTokenToFiatRate, getSendAmount, @@ -36,11 +37,23 @@ const { getTokenExchangeRate, getUnapprovedTxs, isSendFormInError, - // transactionsSelector, + transactionsSelector, } = selectors import mockState from './send-selectors-test-data' describe('send selectors', () => { + let tempGlobalEth = Object.assign({}, global.eth) + beforeEach(() => { + global.eth = { + contract: sinon.stub().returns({ + at: address => 'mockAt:' + address + }) + } + }) + + afterEach(() => { + global.eth = tempGlobalEth + }) describe('accountsWithSendEtherInfoSelector()', () => { it('should return an array of account objects with name info from identities', () => { @@ -48,32 +61,32 @@ describe('send selectors', () => { accountsWithSendEtherInfoSelector(mockState), [ { - 'code': '0x', - 'balance': '0x47c9d71831c76efe', - 'nonce': '0x1b', - 'address': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', - 'name': 'Send Account 1', + code: '0x', + balance: '0x47c9d71831c76efe', + nonce: '0x1b', + address: '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', + name: 'Send Account 1', }, { - 'code': '0x', - 'balance': '0x37452b1315889f80', - 'nonce': '0xa', - 'address': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', - 'name': 'Send Account 2', + code: '0x', + balance: '0x37452b1315889f80', + nonce: '0xa', + address: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', + name: 'Send Account 2', }, { - 'code': '0x', - 'balance': '0x30c9d71831c76efe', - 'nonce': '0x1c', - 'address': '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', - 'name': 'Send Account 3', + code: '0x', + balance: '0x30c9d71831c76efe', + nonce: '0x1c', + address: '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', + name: 'Send Account 3', }, { - 'code': '0x', - 'balance': '0x0', - 'nonce': '0x0', - 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', - 'name': 'Send Account 4', + code: '0x', + balance: '0x0', + nonce: '0x0', + address: '0xd85a4b6a394794842887b8284293d69163007bbb', + name: 'Send Account 4', }, ] ) @@ -95,8 +108,8 @@ describe('send selectors', () => { getAddressBook(mockState), [ { - 'address': '0x06195827297c7a80a443b6894d3bdb8824b43896', - 'name': 'Address Book Account 1', + address: '0x06195827297c7a80a443b6894d3bdb8824b43896', + name: 'Address Book Account 1', }, ], ) @@ -145,11 +158,11 @@ describe('send selectors', () => { assert.deepEqual( getCurrentAccountWithSendEtherInfo(mockState), { - 'code': '0x', - 'balance': '0x0', - 'nonce': '0x0', - 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', - 'name': 'Send Account 4', + code: '0x', + balance: '0x0', + nonce: '0x0', + address: '0xd85a4b6a394794842887b8284293d69163007bbb', + name: 'Send Account 4', } ) }) @@ -232,10 +245,10 @@ describe('send selectors', () => { assert.deepEqual( getSelectedAccount(mockState), { - 'code': '0x', - 'balance': '0x0', - 'nonce': '0x0', - 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', + code: '0x', + balance: '0x0', + nonce: '0x0', + address: '0xd85a4b6a394794842887b8284293d69163007bbb', } ) }) @@ -255,8 +268,8 @@ describe('send selectors', () => { assert.deepEqual( getSelectedIdentity(mockState), { - 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', - 'name': 'Send Account 4', + address: '0xd85a4b6a394794842887b8284293d69163007bbb', + name: 'Send Account 4', } ) }) @@ -267,18 +280,18 @@ describe('send selectors', () => { assert.deepEqual( getSelectedToken(mockState), { - 'address': '0x8d6b81208414189a58339873ab429b6c47ab92d3', - 'decimals': 4, - 'symbol': 'DEF', + address: '0x8d6b81208414189a58339873ab429b6c47ab92d3', + decimals: 4, + symbol: 'DEF', } ) }) it('should return the send token if none is currently selected, but a send token exists', () => { const mockSendToken = { - 'address': '0x123456708414189a58339873ab429b6c47ab92d3', - 'decimals': 4, - 'symbol': 'JKL', + address: '0x123456708414189a58339873ab429b6c47ab92d3', + decimals: 4, + symbol: 'JKL', } const editedMockState = { metamask: Object.assign({}, mockState.metamask, { @@ -295,15 +308,22 @@ describe('send selectors', () => { }) }) - // TODO - // describe('getSelectedTokenContract()', () => { - // it('should', () => { - // assert.deepEqual( - // getSelectedTokenContract(mockState), + describe('getSelectedTokenContract()', () => { + it('should return the contract at the selected token address', () => { + assert.equal( + getSelectedTokenContract(mockState), + 'mockAt:0x8d6b81208414189a58339873ab429b6c47ab92d3' + ) + }) - // ) - // }) - // }) + it('should return null if no token is selected', () => { + const modifiedMetamaskState = Object.assign({}, mockState.metamask, { selectedTokenAddress: false }) + assert.equal( + getSelectedTokenContract(Object.assign({}, mockState, { metamask: modifiedMetamaskState })), + null + ) + }) + }) describe('getSelectedTokenExchangeRate()', () => { it('should return the exchange rate for the selected token', () => { @@ -345,7 +365,7 @@ describe('send selectors', () => { it('should return the send.errors', () => { assert.deepEqual( getSendErrors(mockState), - { 'someError': null } + { someError: null } ) }) }) @@ -355,8 +375,8 @@ describe('send selectors', () => { assert.deepEqual( getSendFrom(mockState), { - 'address': '0xabcdefg', - 'balance': '0x5f4e3d2c1', + address: '0xabcdefg', + balance: '0x5f4e3d2c1', } ) }) @@ -390,8 +410,8 @@ describe('send selectors', () => { assert.deepEqual( getSendFromObject(mockState), { - 'address': '0xabcdefg', - 'balance': '0x5f4e3d2c1', + address: '0xabcdefg', + balance: '0x5f4e3d2c1', } ) }) @@ -407,11 +427,11 @@ describe('send selectors', () => { assert.deepEqual( getSendFromObject(editedMockState), { - 'code': '0x', - 'balance': '0x0', - 'nonce': '0x0', - 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', - 'name': 'Send Account 4', + code: '0x', + balance: '0x0', + nonce: '0x0', + address: '0xd85a4b6a394794842887b8284293d69163007bbb', + name: 'Send Account 4', } ) }) @@ -441,36 +461,36 @@ describe('send selectors', () => { getSendToAccounts(mockState), [ { - 'code': '0x', - 'balance': '0x47c9d71831c76efe', - 'nonce': '0x1b', - 'address': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', - 'name': 'Send Account 1', + code: '0x', + balance: '0x47c9d71831c76efe', + nonce: '0x1b', + address: '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', + name: 'Send Account 1', }, { - 'code': '0x', - 'balance': '0x37452b1315889f80', - 'nonce': '0xa', - 'address': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', - 'name': 'Send Account 2', + code: '0x', + balance: '0x37452b1315889f80', + nonce: '0xa', + address: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', + name: 'Send Account 2', }, { - 'code': '0x', - 'balance': '0x30c9d71831c76efe', - 'nonce': '0x1c', - 'address': '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', - 'name': 'Send Account 3', + code: '0x', + balance: '0x30c9d71831c76efe', + nonce: '0x1c', + address: '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', + name: 'Send Account 3', }, { - 'code': '0x', - 'balance': '0x0', - 'nonce': '0x0', - 'address': '0xd85a4b6a394794842887b8284293d69163007bbb', - 'name': 'Send Account 4', + code: '0x', + balance: '0x0', + nonce: '0x0', + address: '0xd85a4b6a394794842887b8284293d69163007bbb', + name: 'Send Account 4', }, { - 'address': '0x06195827297c7a80a443b6894d3bdb8824b43896', - 'name': 'Address Book Account 1', + address: '0x06195827297c7a80a443b6894d3bdb8824b43896', + name: 'Address Book Account 1', }, ] ) @@ -500,26 +520,26 @@ describe('send selectors', () => { assert.deepEqual( getUnapprovedTxs(mockState), { - '4768706228115573': { - 'id': 4768706228115573, - 'time': 1487363153561, - 'status': 'unapproved', - 'gasMultiplier': 1, - 'metamaskNetworkId': '3', - 'txParams': { - 'from': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', - 'to': '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761', - 'value': '0xde0b6b3a7640000', - 'metamaskId': 4768706228115573, - 'metamaskNetworkId': '3', - 'gas': '0x5209', + 4768706228115573: { + id: 4768706228115573, + time: 1487363153561, + status: 'unapproved', + gasMultiplier: 1, + metamaskNetworkId: '3', + txParams: { + from: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', + to: '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761', + value: '0xde0b6b3a7640000', + metamaskId: 4768706228115573, + metamaskNetworkId: '3', + gas: '0x5209', }, - 'gasLimitSpecified': false, - 'estimatedGas': '0x5209', - 'txFee': '17e0186e60800', - 'txValue': 'de0b6b3a7640000', - 'maxCost': 'de234b52e4a0800', - 'gasPrice': '4a817c800', + gasLimitSpecified: false, + estimatedGas: '0x5209', + txFee: '17e0186e60800', + txValue: 'de0b6b3a7640000', + maxCost: 'de234b52e4a0800', + gasPrice: '4a817c800', }, } ) @@ -559,14 +579,131 @@ describe('send selectors', () => { }) }) - // TODO - // describe('transactionsSelector()', () => { - // it('should', () => { - // assert.deepEqual( - // transactionsSelector(mockState), + describe('transactionsSelector()', () => { + it('should return the selected addresses selected token transactions', () => { + assert.deepEqual( + transactionsSelector(mockState), + [ + { + id: 'mockTokenTx1', + txParams: { + to: '0x8d6b81208414189a58339873ab429b6c47ab92d3', + }, + time: 1700000000000, + }, + { + id: 'mockTokenTx3', + txParams: { + to: '0x8d6b81208414189a58339873ab429b6c47ab92d3', + }, + time: 1500000000000, + }, + ] + ) + }) - // ) - // }) - // }) + it('should return all transactions if no token is selected', () => { + const modifiedMetamaskState = Object.assign({}, mockState.metamask, { selectedTokenAddress: false }) + const modifiedState = Object.assign({}, mockState, { metamask: modifiedMetamaskState }) + assert.deepEqual( + transactionsSelector(modifiedState), + [ + { + id: 'mockTokenTx1', + time: 1700000000000, + txParams: { + to: '0x8d6b81208414189a58339873ab429b6c47ab92d3', + }, + }, + { + id: 'unapprovedMessage1', + time: 1650000000000, + }, + { + id: 'mockTokenTx2', + time: 1600000000000, + txParams: { + to: '0xafaketokenaddress', + }, + }, + { + id: 'unapprovedMessage2', + time: 1550000000000, + }, + { + id: 'mockTokenTx3', + time: 1500000000000, + txParams: { + to: '0x8d6b81208414189a58339873ab429b6c47ab92d3', + }, + }, + { + id: 'unapprovedMessage3', + time: 1450000000000, + }, + { + id: 'mockEthTx1', + time: 1400000000000, + txParams: { + to: '0xd85a4b6a394794842887b8284293d69163007bbb', + }, + }, + ] + ) + }) + + it('should return shapeshift transactions if current network is 1', () => { + const modifiedMetamaskState = Object.assign({}, mockState.metamask, { selectedTokenAddress: false, network: '1' }) + const modifiedState = Object.assign({}, mockState, { metamask: modifiedMetamaskState }) + assert.deepEqual( + transactionsSelector(modifiedState), + [ + { + id: 'mockTokenTx1', + time: 1700000000000, + txParams: { + to: '0x8d6b81208414189a58339873ab429b6c47ab92d3', + }, + }, + { id: 'shapeShiftTx1', 'time': 1675000000000 }, + { + id: 'unapprovedMessage1', + time: 1650000000000, + }, + { + id: 'mockTokenTx2', + time: 1600000000000, + txParams: { + to: '0xafaketokenaddress', + }, + }, + { id: 'shapeShiftTx2', 'time': 1575000000000 }, + { + id: 'unapprovedMessage2', + time: 1550000000000, + }, + { + id: 'mockTokenTx3', + time: 1500000000000, + txParams: { + to: '0x8d6b81208414189a58339873ab429b6c47ab92d3', + }, + }, + { id: 'shapeShiftTx3', 'time': 1475000000000 }, + { + id: 'unapprovedMessage3', + time: 1450000000000, + }, + { + id: 'mockEthTx1', + time: 1400000000000, + txParams: { + to: '0xd85a4b6a394794842887b8284293d69163007bbb', + }, + }, + ] + ) + }) + }) }) -- cgit From 0076b732bd7c7d22b947f8d437c91944dac78219 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 14 May 2018 09:49:09 -0230 Subject: Rename ducks/send.js to ducks/send.duck.js --- ui/app/components/customize-gas-modal/index.js | 2 +- ui/app/components/pending-tx/confirm-send-ether.js | 2 +- ui/app/components/pending-tx/confirm-send-token.js | 2 +- .../amount-max-button.container.js | 2 +- .../tests/amount-max-button-container.test.js | 2 +- .../send-amount-row/send-amount-row.container.js | 2 +- .../tests/send-amount-row-container.test.js | 2 +- .../send-from-row/send-from-row.container.js | 2 +- .../tests/send-from-row-container.test.js | 2 +- .../send-to-row/send-to-row.container.js | 2 +- .../tests/send-to-row-container.test.js | 2 +- ui/app/components/send_/send.container.js | 2 +- .../components/send_/tests/send-container.test.js | 2 +- ui/app/ducks/send.duck.js | 70 ++++++++++++++++++++++ ui/app/ducks/send.js | 70 ---------------------- ui/app/reducers.js | 2 +- 16 files changed, 84 insertions(+), 84 deletions(-) create mode 100644 ui/app/ducks/send.duck.js delete mode 100644 ui/app/ducks/send.js (limited to 'ui') diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 1da3f7f61..845e4ddc2 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -10,7 +10,7 @@ const ethUtil = require('ethereumjs-util') import { updateSendErrors, -} from '../../ducks/send' +} from '../../ducks/send.duck' const { MIN_GAS_PRICE_DEC, diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index a450f9081..624366112 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -31,7 +31,7 @@ const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') import { updateSendErrors, -} from '../../ducks/send' +} from '../../ducks/send.duck' ConfirmSendEther.contextTypes = { t: PropTypes.func, diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 135cb2275..5b5149058 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -41,7 +41,7 @@ const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') import { updateSendErrors, -} from '../../ducks/send' +} from '../../ducks/send.duck' ConfirmSendToken.contextTypes = { t: PropTypes.func, diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js index a72f41775..2d2ec42f7 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js @@ -14,7 +14,7 @@ import { import AmountMaxButton from './amount-max-button.component' import { updateSendErrors, -} from '../../../../../ducks/send' +} from '../../../../../ducks/send.duck' export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton) diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js index 1aa0ad8fb..2cc00d6d6 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js @@ -30,7 +30,7 @@ proxyquire('../amount-max-button.container.js', { './amount-max-button.selectors.js': { getMaxModeOn: (s) => `mockMaxModeOn:${s}` }, './amount-max-button.utils.js': { calcMaxAmount: (mockObj) => mockObj.val + 1 }, '../../../../../actions': actionSpies, - '../../../../../ducks/send': duckActionSpies, + '../../../../../ducks/send.duck': duckActionSpies, }) describe('amount-max-button container', () => { diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js index 13888ec53..bbbf56971 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js @@ -20,7 +20,7 @@ import { } from '../../../../actions' import { updateSendErrors, -} from '../../../../ducks/send' +} from '../../../../ducks/send.duck' import SendAmountRow from './send-amount-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow) diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js index 0678ec38f..e4c913c69 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js @@ -35,7 +35,7 @@ proxyquire('../send-amount-row.container.js', { './send-amount-row.selectors': { sendAmountIsInError: (s) => `mockInError:${s}` }, '../../send.utils': { getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }) }, '../../../../actions': actionSpies, - '../../../../ducks/send': duckActionSpies, + '../../../../ducks/send.duck': duckActionSpies, }) describe('send-amount-row container', () => { diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js index 377aead0a..402b744e4 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js @@ -16,7 +16,7 @@ import { import { closeFromDropdown, openFromDropdown, -} from '../../../../ducks/send' +} from '../../../../ducks/send.duck' import SendFromRow from './send-from-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendFromRow) diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js index 70ab963ab..785b3d3ef 100644 --- a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js +++ b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js @@ -31,7 +31,7 @@ proxyquire('../send-from-row.container.js', { './send-from-row.selectors.js': { getFromDropdownOpen: (s) => `mockFromDropdownOpen:${s}` }, '../../send.utils.js': { calcTokenBalance: (a, b) => a + b }, '../../../../actions': actionSpies, - '../../../../ducks/send': duckActionSpies, + '../../../../ducks/send.duck': duckActionSpies, }) describe('send-from-row container', () => { diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js index bffdda49c..a10da505a 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js @@ -16,7 +16,7 @@ import { updateSendErrors, openToDropdown, closeToDropdown, -} from '../../../../ducks/send' +} from '../../../../ducks/send.duck' import SendToRow from './send-to-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js index 3415e7afa..433b242b2 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js @@ -33,7 +33,7 @@ proxyquire('../send-to-row.container.js', { }, './send-to-row.utils.js': { getToErrorObject: (t) => `mockError:${t}` }, '../../../../actions': actionSpies, - '../../../../ducks/send': duckActionSpies, + '../../../../ducks/send.duck': duckActionSpies, }) describe('send-to-row container', () => { diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js index d966fd808..8efaf5aaf 100644 --- a/ui/app/components/send_/send.container.js +++ b/ui/app/components/send_/send.container.js @@ -26,7 +26,7 @@ import { } from '../../actions' import { updateSendErrors, -} from '../../ducks/send' +} from '../../ducks/send.duck' import { calcGasTotal, generateTokenTransferData, diff --git a/ui/app/components/send_/tests/send-container.test.js b/ui/app/components/send_/tests/send-container.test.js index edd5e38ab..7b6ca1f7b 100644 --- a/ui/app/components/send_/tests/send-container.test.js +++ b/ui/app/components/send_/tests/send-container.test.js @@ -42,7 +42,7 @@ proxyquire('../send.container.js', { getTokenBalance: (s) => `mockTokenBalance:${s}`, }, '../../actions': actionSpies, - '../../ducks/send': duckActionSpies, + '../../ducks/send.duck': duckActionSpies, './send.utils.js': { calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice, generateTokenTransferData: (a, b) => `mockData:${a + b}`, diff --git a/ui/app/ducks/send.duck.js b/ui/app/ducks/send.duck.js new file mode 100644 index 000000000..aef493ea0 --- /dev/null +++ b/ui/app/ducks/send.duck.js @@ -0,0 +1,70 @@ +import extend from 'xtend' + +// Actions +const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN' +const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN' +const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN' +const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN' +const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS' + +// TODO: determine if this approach to initState is consistent with conventional ducks pattern +const initState = { + fromDropdownOpen: false, + toDropdownOpen: false, + errors: {}, +} + +// Reducer +export default function reducer ({ send: sendState = initState }, action = {}) { + switch (action.type) { + case OPEN_FROM_DROPDOWN: + return extend(sendState, { + fromDropdownOpen: true, + }) + case CLOSE_FROM_DROPDOWN: + return extend(sendState, { + fromDropdownOpen: false, + }) + case OPEN_TO_DROPDOWN: + return extend(sendState, { + toDropdownOpen: true, + }) + case CLOSE_TO_DROPDOWN: + return extend(sendState, { + toDropdownOpen: false, + }) + case UPDATE_SEND_ERRORS: + return extend(sendState, { + errors: { + ...sendState.errors, + ...action.value, + }, + }) + default: + return sendState + } +} + +// Action Creators +export function openFromDropdown () { + return { type: OPEN_FROM_DROPDOWN } +} + +export function closeFromDropdown () { + return { type: CLOSE_FROM_DROPDOWN } +} + +export function openToDropdown () { + return { type: OPEN_TO_DROPDOWN } +} + +export function closeToDropdown () { + return { type: CLOSE_TO_DROPDOWN } +} + +export function updateSendErrors (errorObject) { + return { + type: UPDATE_SEND_ERRORS, + value: errorObject, + } +} diff --git a/ui/app/ducks/send.js b/ui/app/ducks/send.js deleted file mode 100644 index aef493ea0..000000000 --- a/ui/app/ducks/send.js +++ /dev/null @@ -1,70 +0,0 @@ -import extend from 'xtend' - -// Actions -const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN' -const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN' -const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN' -const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN' -const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS' - -// TODO: determine if this approach to initState is consistent with conventional ducks pattern -const initState = { - fromDropdownOpen: false, - toDropdownOpen: false, - errors: {}, -} - -// Reducer -export default function reducer ({ send: sendState = initState }, action = {}) { - switch (action.type) { - case OPEN_FROM_DROPDOWN: - return extend(sendState, { - fromDropdownOpen: true, - }) - case CLOSE_FROM_DROPDOWN: - return extend(sendState, { - fromDropdownOpen: false, - }) - case OPEN_TO_DROPDOWN: - return extend(sendState, { - toDropdownOpen: true, - }) - case CLOSE_TO_DROPDOWN: - return extend(sendState, { - toDropdownOpen: false, - }) - case UPDATE_SEND_ERRORS: - return extend(sendState, { - errors: { - ...sendState.errors, - ...action.value, - }, - }) - default: - return sendState - } -} - -// Action Creators -export function openFromDropdown () { - return { type: OPEN_FROM_DROPDOWN } -} - -export function closeFromDropdown () { - return { type: CLOSE_FROM_DROPDOWN } -} - -export function openToDropdown () { - return { type: OPEN_TO_DROPDOWN } -} - -export function closeToDropdown () { - return { type: CLOSE_TO_DROPDOWN } -} - -export function updateSendErrors (errorObject) { - return { - type: UPDATE_SEND_ERRORS, - value: errorObject, - } -} diff --git a/ui/app/reducers.js b/ui/app/reducers.js index 26bf66c3c..d0d364da9 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -8,7 +8,7 @@ const reduceIdentities = require('./reducers/identities') const reduceMetamask = require('./reducers/metamask') const reduceApp = require('./reducers/app') const reduceLocale = require('./reducers/locale') -const reduceSend = require('./ducks/send').default +const reduceSend = require('./ducks/send.duck').default window.METAMASK_CACHED_LOG_STATE = null -- cgit From 0739618a61cfc383498e466f4fdeb5a25324598f Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 14 May 2018 10:27:36 -0230 Subject: Tests for send.duck.js --- .../send_/tests/send-selectors-test-data.js | 20 +-- .../components/send_/tests/send-selectors.test.js | 6 +- ui/app/ducks/send.duck.js | 16 ++- ui/app/ducks/tests/send-duck.test.js | 145 +++++++++++++++++++++ 4 files changed, 167 insertions(+), 20 deletions(-) create mode 100644 ui/app/ducks/tests/send-duck.test.js (limited to 'ui') diff --git a/ui/app/components/send_/tests/send-selectors-test-data.js b/ui/app/components/send_/tests/send-selectors-test-data.js index 1a95b5f41..ecfe9022f 100644 --- a/ui/app/components/send_/tests/send-selectors-test-data.js +++ b/ui/app/components/send_/tests/send-selectors-test-data.js @@ -90,31 +90,31 @@ module.exports = { { 'id': 'mockTokenTx1', 'txParams': { - 'to': '0x8d6b81208414189a58339873ab429b6c47ab92d3' + 'to': '0x8d6b81208414189a58339873ab429b6c47ab92d3', }, - 'time': 1700000000000 + 'time': 1700000000000, }, { 'id': 'mockTokenTx2', 'txParams': { - 'to': '0xafaketokenaddress' + 'to': '0xafaketokenaddress', }, - 'time': 1600000000000 + 'time': 1600000000000, }, { 'id': 'mockTokenTx3', 'txParams': { - 'to': '0x8d6b81208414189a58339873ab429b6c47ab92d3' + 'to': '0x8d6b81208414189a58339873ab429b6c47ab92d3', }, - 'time': 1500000000000 + 'time': 1500000000000, }, { 'id': 'mockEthTx1', 'txParams': { - 'to': '0xd85a4b6a394794842887b8284293d69163007bbb' + 'to': '0xd85a4b6a394794842887b8284293d69163007bbb', }, - 'time': 1400000000000 - } + 'time': 1400000000000, + }, ], 'selectedTokenAddress': '0x8d6b81208414189a58339873ab429b6c47ab92d3', 'unapprovedMsgs': { @@ -195,7 +195,7 @@ module.exports = { 'txValue': 'de0b6b3a7640000', 'maxCost': 'de234b52e4a0800', 'gasPrice': '4a817c800', - } + }, }, 'currentLocale': 'en', }, diff --git a/ui/app/components/send_/tests/send-selectors.test.js b/ui/app/components/send_/tests/send-selectors.test.js index e2acc15f6..9dc207ead 100644 --- a/ui/app/components/send_/tests/send-selectors.test.js +++ b/ui/app/components/send_/tests/send-selectors.test.js @@ -42,12 +42,12 @@ const { import mockState from './send-selectors-test-data' describe('send selectors', () => { - let tempGlobalEth = Object.assign({}, global.eth) + const tempGlobalEth = Object.assign({}, global.eth) beforeEach(() => { global.eth = { contract: sinon.stub().returns({ - at: address => 'mockAt:' + address - }) + at: address => 'mockAt:' + address, + }), } }) diff --git a/ui/app/ducks/send.duck.js b/ui/app/ducks/send.duck.js index aef493ea0..055cc05c1 100644 --- a/ui/app/ducks/send.duck.js +++ b/ui/app/ducks/send.duck.js @@ -16,32 +16,34 @@ const initState = { // Reducer export default function reducer ({ send: sendState = initState }, action = {}) { + const newState = extend({}, sendState) + switch (action.type) { case OPEN_FROM_DROPDOWN: - return extend(sendState, { + return extend(newState, { fromDropdownOpen: true, }) case CLOSE_FROM_DROPDOWN: - return extend(sendState, { + return extend(newState, { fromDropdownOpen: false, }) case OPEN_TO_DROPDOWN: - return extend(sendState, { + return extend(newState, { toDropdownOpen: true, }) case CLOSE_TO_DROPDOWN: - return extend(sendState, { + return extend(newState, { toDropdownOpen: false, }) case UPDATE_SEND_ERRORS: - return extend(sendState, { + return extend(newState, { errors: { - ...sendState.errors, + ...newState.errors, ...action.value, }, }) default: - return sendState + return newState } } diff --git a/ui/app/ducks/tests/send-duck.test.js b/ui/app/ducks/tests/send-duck.test.js new file mode 100644 index 000000000..c06cf55d2 --- /dev/null +++ b/ui/app/ducks/tests/send-duck.test.js @@ -0,0 +1,145 @@ +import assert from 'assert' + +import SendReducer, { + openFromDropdown, + closeFromDropdown, + openToDropdown, + closeToDropdown, + updateSendErrors, +} from '../send.duck.js' + +describe('Send Duck', () => { + const mockState = { + send: { + mockProp: 123, + }, + } + const initState = { + fromDropdownOpen: false, + toDropdownOpen: false, + errors: {}, + } + const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN' + const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN' + const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN' + const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN' + const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS' + + describe('SendReducer()', () => { + it('should initialize state', () => { + assert.deepEqual( + SendReducer({}), + initState + ) + }) + + it('should return state unchanged if it does not match a dispatched actions type', () => { + assert.deepEqual( + SendReducer(mockState, { + type: 'someOtherAction', + value: 'someValue', + }), + Object.assign({}, mockState.send) + ) + }) + + it('should set fromDropdownOpen to true when receiving a OPEN_FROM_DROPDOWN action', () => { + assert.deepEqual( + SendReducer(mockState, { + type: OPEN_FROM_DROPDOWN, + }), + Object.assign({fromDropdownOpen: true}, mockState.send) + ) + }) + + it('should return a new object (and not just modify the existing state object)', () => { + assert.deepEqual(SendReducer(mockState), mockState.send) + assert.notEqual(SendReducer(mockState), mockState.send) + }) + + it('should set fromDropdownOpen to false when receiving a CLOSE_FROM_DROPDOWN action', () => { + assert.deepEqual( + SendReducer(mockState, { + type: CLOSE_FROM_DROPDOWN, + }), + Object.assign({fromDropdownOpen: false}, mockState.send) + ) + }) + + it('should set toDropdownOpen to true when receiving a OPEN_TO_DROPDOWN action', () => { + assert.deepEqual( + SendReducer(mockState, { + type: OPEN_TO_DROPDOWN, + }), + Object.assign({toDropdownOpen: true}, mockState.send) + ) + }) + + it('should set toDropdownOpen to false when receiving a CLOSE_TO_DROPDOWN action', () => { + assert.deepEqual( + SendReducer(mockState, { + type: CLOSE_TO_DROPDOWN, + }), + Object.assign({toDropdownOpen: false}, mockState.send) + ) + }) + + it('should extend send.errors with the value of a UPDATE_SEND_ERRORS action', () => { + const modifiedMockState = Object.assign({}, mockState, { + send: { + errors: { + someError: false, + }, + }, + }) + assert.deepEqual( + SendReducer(modifiedMockState, { + type: UPDATE_SEND_ERRORS, + value: { someOtherError: true }, + }), + Object.assign({}, modifiedMockState.send, { + errors: { + someError: false, + someOtherError: true, + }, + }) + ) + }) + }) + + describe('openFromDropdown', () => { + assert.deepEqual( + openFromDropdown(), + { type: OPEN_FROM_DROPDOWN } + ) + }) + + describe('closeFromDropdown', () => { + assert.deepEqual( + closeFromDropdown(), + { type: CLOSE_FROM_DROPDOWN } + ) + }) + + describe('openToDropdown', () => { + assert.deepEqual( + openToDropdown(), + { type: OPEN_TO_DROPDOWN } + ) + }) + + describe('closeToDropdown', () => { + assert.deepEqual( + closeToDropdown(), + { type: CLOSE_TO_DROPDOWN } + ) + }) + + describe('updateSendErrors', () => { + assert.deepEqual( + updateSendErrors('mockErrorObject'), + { type: UPDATE_SEND_ERRORS, value: 'mockErrorObject' } + ) + }) + +}) -- cgit From 6bc8cc819a16118acc010d0efdec90afbda14590 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 14 May 2018 11:00:50 -0230 Subject: Merge branch 'develop' into i3725-refactor-send-component- --- ui/app/actions.js | 124 +++++----- ui/app/app.js | 149 ++---------- .../components/app-header/app-header.component.js | 106 +++++++++ .../components/app-header/app-header.container.js | 38 +++ ui/app/components/app-header/index.js | 2 + ui/app/components/button/button.component.js | 43 ++++ ui/app/components/button/button.stories.js | 41 ++++ ui/app/components/button/index.js | 2 + ui/app/components/buy-button-subview.js | 2 +- ui/app/components/customize-gas-modal/index.js | 4 +- .../export-text-container.component.js | 45 ++++ .../export-text-container.scss | 52 +++++ ui/app/components/export-text-container/index.js | 2 + ui/app/components/loading-screen/index.js | 2 + .../loading-screen/loading-screen.component.js | 35 +++ ui/app/components/loading.js | 34 --- .../page-container-footer.component.js | 17 +- ui/app/components/pages/add-token.js | 2 +- .../pages/create-account/import-account/json.js | 2 + .../create-account/import-account/private-key.js | 2 + ui/app/components/pages/home.js | 4 +- ui/app/components/pages/keychains/reveal-seed.js | 259 +++++++++------------ ui/app/components/pages/unlock-page/index.js | 2 + .../pages/unlock-page/unlock-page.component.js | 181 ++++++++++++++ .../pages/unlock-page/unlock-page.container.js | 33 +++ .../components/pages/unlock-page/unlock-page.scss | 51 ++++ ui/app/components/pages/unlock.js | 194 --------------- ui/app/components/pending-tx/index.js | 40 ++-- ui/app/components/send/currency-display.js | 15 +- ui/app/components/send_/send.constants.js | 4 +- ui/app/components/shapeshift-form.js | 13 +- ui/app/components/spinner/index.js | 2 + ui/app/components/spinner/spinner.component.js | 78 +++++++ ui/app/components/text-field/index.js | 2 + .../components/text-field/text-field.component.js | 54 +++++ ui/app/components/text-field/text-field.stories.js | 24 ++ ui/app/components/wallet-view.js | 6 +- ui/app/conf-tx.js | 2 +- ui/app/css/itcss/components/account-dropdown.scss | 3 +- ui/app/css/itcss/components/account-menu.scss | 3 - ui/app/css/itcss/components/add-token.scss | 1 - ui/app/css/itcss/components/buttons.scss | 1 + ui/app/css/itcss/components/confirm.scss | 4 - ui/app/css/itcss/components/currency-display.scss | 1 - ui/app/css/itcss/components/header.scss | 118 +++++----- ui/app/css/itcss/components/index.scss | 2 + ui/app/css/itcss/components/loading-overlay.scss | 21 ++ ui/app/css/itcss/components/menu.scss | 1 - ui/app/css/itcss/components/modal.scss | 2 - ui/app/css/itcss/components/newui-sections.scss | 2 - ui/app/css/itcss/components/pages/index.scss | 4 +- ui/app/css/itcss/components/pages/reveal-seed.scss | 17 ++ ui/app/css/itcss/components/pages/unlock.scss | 9 - ui/app/css/itcss/components/request-signature.scss | 2 - ui/app/css/itcss/components/sections.scss | 41 ---- ui/app/css/itcss/components/send.scss | 3 - ui/app/css/itcss/components/settings.scss | 4 +- ui/app/css/itcss/generic/index.scss | 70 +++++- ui/app/css/itcss/settings/variables.scss | 1 + ui/app/keychains/hd/recover-seed/confirmation.js | 138 ----------- ui/app/main-container.js | 2 +- ui/app/token-util.js | 46 ++-- ui/app/unlock.js | 141 ----------- ui/i18n-helper.js | 3 +- 64 files changed, 1261 insertions(+), 1047 deletions(-) create mode 100644 ui/app/components/app-header/app-header.component.js create mode 100644 ui/app/components/app-header/app-header.container.js create mode 100644 ui/app/components/app-header/index.js create mode 100644 ui/app/components/button/button.component.js create mode 100644 ui/app/components/button/button.stories.js create mode 100644 ui/app/components/button/index.js create mode 100644 ui/app/components/export-text-container/export-text-container.component.js create mode 100644 ui/app/components/export-text-container/export-text-container.scss create mode 100644 ui/app/components/export-text-container/index.js create mode 100644 ui/app/components/loading-screen/index.js create mode 100644 ui/app/components/loading-screen/loading-screen.component.js delete mode 100644 ui/app/components/loading.js create mode 100644 ui/app/components/pages/unlock-page/index.js create mode 100644 ui/app/components/pages/unlock-page/unlock-page.component.js create mode 100644 ui/app/components/pages/unlock-page/unlock-page.container.js create mode 100644 ui/app/components/pages/unlock-page/unlock-page.scss delete mode 100644 ui/app/components/pages/unlock.js create mode 100644 ui/app/components/spinner/index.js create mode 100644 ui/app/components/spinner/spinner.component.js create mode 100644 ui/app/components/text-field/index.js create mode 100644 ui/app/components/text-field/text-field.component.js create mode 100644 ui/app/components/text-field/text-field.stories.js create mode 100644 ui/app/css/itcss/components/pages/reveal-seed.scss delete mode 100644 ui/app/css/itcss/components/pages/unlock.scss delete mode 100644 ui/app/keychains/hd/recover-seed/confirmation.js delete mode 100644 ui/app/unlock.js (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 7b838940a..8b9928d5b 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1,4 +1,5 @@ const abi = require('human-standard-token-abi') +const pify = require('pify') const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url') const { getTokenAddressFromTokenObject } = require('./util') const { @@ -88,7 +89,7 @@ var actions = { REVEAL_SEED_CONFIRMATION: 'REVEAL_SEED_CONFIRMATION', revealSeedConfirmation: revealSeedConfirmation, requestRevealSeed: requestRevealSeed, - + requestRevealSeedWords, // unlock screen UNLOCK_IN_PROGRESS: 'UNLOCK_IN_PROGRESS', UNLOCK_FAILED: 'UNLOCK_FAILED', @@ -325,6 +326,7 @@ function tryUnlockMetamask (password) { background.verifySeedPhrase(err => { if (err) { dispatch(actions.displayWarning(err.message)) + return reject(err) } resolve() @@ -338,6 +340,7 @@ function tryUnlockMetamask (password) { .catch(err => { dispatch(actions.unlockFailed(err.message)) dispatch(actions.hideLoadingIndication()) + return Promise.reject(err) }) } } @@ -354,11 +357,13 @@ function transitionBackward () { } } -function clearSeedWordCache () { - log.debug(`background.clearSeedWordCache`) +function confirmSeedWords () { return dispatch => { + dispatch(actions.showLoadingIndication()) + log.debug(`background.clearSeedWordCache`) return new Promise((resolve, reject) => { background.clearSeedWordCache((err, account) => { + dispatch(actions.hideLoadingIndication()) if (err) { dispatch(actions.displayWarning(err.message)) return reject(err) @@ -372,22 +377,6 @@ function clearSeedWordCache () { } } -function confirmSeedWords () { - return async dispatch => { - dispatch(actions.showLoadingIndication()) - const account = await dispatch(clearSeedWordCache()) - return dispatch(setIsRevealingSeedWords(false)) - .then(() => { - dispatch(actions.hideLoadingIndication()) - return account - }) - .catch(() => { - dispatch(actions.hideLoadingIndication()) - return account - }) - } -} - function createNewVaultAndRestore (password, seed) { return (dispatch) => { dispatch(actions.showLoadingIndication()) @@ -450,6 +439,30 @@ function revealSeedConfirmation () { } } +function verifyPassword (password) { + return new Promise((resolve, reject) => { + background.submitPassword(password, error => { + if (error) { + return reject(error) + } + + resolve(true) + }) + }) +} + +function verifySeedPhrase () { + return new Promise((resolve, reject) => { + background.verifySeedPhrase((error, seedWords) => { + if (error) { + return reject(error) + } + + resolve(seedWords) + }) + }) +} + function requestRevealSeed (password) { return dispatch => { dispatch(actions.showLoadingIndication()) @@ -469,13 +482,29 @@ function requestRevealSeed (password) { } dispatch(actions.showNewVaultSeed(result)) + dispatch(actions.hideLoadingIndication()) resolve() }) }) }) - .then(() => dispatch(setIsRevealingSeedWords(true))) - .then(() => dispatch(actions.hideLoadingIndication())) - .catch(() => dispatch(actions.hideLoadingIndication())) + } +} + +function requestRevealSeedWords (password) { + return async dispatch => { + dispatch(actions.showLoadingIndication()) + log.debug(`background.submitPassword`) + + try { + await verifyPassword(password) + const seedWords = await verifySeedPhrase() + dispatch(actions.hideLoadingIndication()) + return seedWords + } catch (error) { + dispatch(actions.hideLoadingIndication()) + dispatch(actions.displayWarning(error.message)) + throw new Error(error.message) + } } } @@ -506,31 +535,26 @@ function addNewKeyring (type, opts) { } function importNewAccount (strategy, args) { - return (dispatch) => { - dispatch(actions.showLoadingIndication('This may take a while, be patient.')) - log.debug(`background.importAccountWithStrategy`) - return new Promise((resolve, reject) => { - background.importAccountWithStrategy(strategy, args, (err) => { - if (err) { - dispatch(actions.displayWarning(err.message)) - return reject(err) - } - log.debug(`background.getState`) - background.getState((err, newState) => { - dispatch(actions.hideLoadingIndication()) - if (err) { - dispatch(actions.displayWarning(err.message)) - return reject(err) - } - dispatch(actions.updateMetamaskState(newState)) - dispatch({ - type: actions.SHOW_ACCOUNT_DETAIL, - value: newState.selectedAddress, - }) - resolve(newState) - }) - }) + return async (dispatch) => { + let newState + dispatch(actions.showLoadingIndication('This may take a while, please be patient.')) + try { + log.debug(`background.importAccountWithStrategy`) + await pify(background.importAccountWithStrategy).call(background, strategy, args) + log.debug(`background.getState`) + newState = await pify(background.getState).call(background) + } catch (err) { + dispatch(actions.hideLoadingIndication()) + dispatch(actions.displayWarning(err.message)) + throw err + } + dispatch(actions.hideLoadingIndication()) + dispatch(actions.updateMetamaskState(newState)) + dispatch({ + type: actions.SHOW_ACCOUNT_DETAIL, + value: newState.selectedAddress, }) + return newState } } @@ -1975,11 +1999,3 @@ function updateNetworkEndpointType (networkEndpointType) { value: networkEndpointType, } } - -function setIsRevealingSeedWords (reveal) { - return dispatch => { - log.debug(`background.setIsRevealingSeedWords`) - background.setIsRevealingSeedWords(reveal) - return forceUpdateMetamaskState(dispatch) - } -} diff --git a/ui/app/app.js b/ui/app/app.js index 6511979b2..0c0c6ec44 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -1,7 +1,7 @@ const { Component } = require('react') const PropTypes = require('prop-types') const connect = require('react-redux').connect -const { Route, Switch, withRouter } = require('react-router-dom') +const { Route, Switch, withRouter, matchPath } = require('react-router-dom') const { compose } = require('recompose') const h = require('react-hyperscript') const actions = require('./actions') @@ -22,16 +22,14 @@ const Home = require('./components/pages/home') const Authenticated = require('./components/pages/authenticated') const Initialized = require('./components/pages/initialized') const Settings = require('./components/pages/settings') -const UnlockPage = require('./components/pages/unlock') +const UnlockPage = require('./components/pages/unlock-page') const RestoreVaultPage = require('./components/pages/keychains/restore-vault') -const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation') +const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed') const AddTokenPage = require('./components/pages/add-token') const CreateAccountPage = require('./components/pages/create-account') const NoticeScreen = require('./components/pages/notice') -const Loading = require('./components/loading') -const NetworkIndicator = require('./components/network') -const Identicon = require('./components/identicon') +const Loading = require('./components/loading-screen') const ReactCSSTransitionGroup = require('react-addons-css-transition-group') const NetworkDropdown = require('./components/dropdowns/network-dropdown') const AccountMenu = require('./components/account-menu') @@ -39,6 +37,8 @@ const AccountMenu = require('./components/account-menu') // Global Modals const Modal = require('./components/modals/index').Modal +const AppHeader = require('./components/app-header') + // Routes const { DEFAULT_ROUTE, @@ -56,20 +56,11 @@ const { class App extends Component { componentWillMount () { - const { - currentCurrency, - setCurrentCurrencyToUSD, - isRevealingSeedWords, - clearSeedWords, - } = this.props + const { currentCurrency, setCurrentCurrencyToUSD } = this.props if (!currentCurrency) { setCurrentCurrencyToUSD() } - - if (isRevealingSeedWords) { - clearSeedWords() - } } renderRoutes () { @@ -78,11 +69,11 @@ class App extends Component { return ( h(Switch, [ h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }), - h(Initialized, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }), h(Initialized, { path: UNLOCK_ROUTE, exact, component: UnlockPage }), - h(Initialized, { path: SETTINGS_ROUTE, component: Settings }), h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }), - h(Initialized, { path: NOTICE_ROUTE, exact, component: NoticeScreen }), + h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }), + h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }), + h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }), h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }), h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen }), h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), @@ -92,6 +83,15 @@ class App extends Component { ) } + renderAppHeader () { + const { location } = this.props + const isInitializing = matchPath(location.pathname, { + path: INITIALIZE_ROUTE, exact: false, + }) + + return isInitializing ? null : h(AppHeader) + } + render () { const { isLoading, @@ -128,8 +128,7 @@ class App extends Component { // global modal h(Modal, {}, []), - // app bar - this.renderAppBar(), + this.renderAppHeader(), // sidebar this.renderSidebar(), @@ -144,6 +143,7 @@ class App extends Component { (isLoading || isLoadingNetwork) && h(Loading, { loadingMessage: loadMessage, + fullScreen: true, }), // content @@ -205,110 +205,6 @@ class App extends Component { ]) } - renderAppBar () { - const { - isUnlocked, - network, - provider, - networkDropdownOpen, - showNetworkDropdown, - hideNetworkDropdown, - isInitialized, - welcomeScreenSeen, - isPopup, - betaUI, - } = this.props - - if (window.METAMASK_UI_TYPE === 'notification') { - return null - } - - const props = this.props - const {isMascara, isOnboarding} = props - - // Do not render header if user is in mascara onboarding - if (isMascara && isOnboarding) { - return null - } - - // Do not render header if user is in mascara buy ether - if (isMascara && props.currentView.name === 'buyEth') { - return null - } - - return ( - - h('.full-width', { - style: {}, - }, [ - - (isInitialized || welcomeScreenSeen || isPopup || !betaUI) && h('.app-header.flex-row.flex-space-between', { - className: classnames({ - 'app-header--initialized': !isOnboarding, - }), - }, [ - h('div.app-header-contents', {}, [ - h('div.left-menu-wrapper', { - onClick: () => props.history.push(DEFAULT_ROUTE), - }, [ - // mini logo - h('img.metafox-icon', { - height: 42, - width: 42, - src: '/images/metamask-fox.svg', - }), - - // metamask name - h('.flex-row', [ - h('h1', this.context.t('appName')), - h('div.beta-label', this.context.t('beta')), - ]), - - ]), - - betaUI && isInitialized && h('div.header__right-actions', [ - h('div.network-component-wrapper', { - style: {}, - }, [ - // Network Indicator - h(NetworkIndicator, { - network, - provider, - disabled: this.props.location.pathname === CONFIRM_TRANSACTION_ROUTE, - onClick: (event) => { - event.preventDefault() - event.stopPropagation() - return networkDropdownOpen === false - ? showNetworkDropdown() - : hideNetworkDropdown() - }, - }), - - ]), - - isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [ - h(Identicon, { - address: this.props.selectedAddress, - diameter: 32, - }), - ]), - ]), - ]), - ]), - - !isInitialized && !isPopup && betaUI && h('.alpha-warning__container', {}, [ - h('h2', { - className: classnames({ - 'alpha-warning': welcomeScreenSeen, - 'alpha-warning-welcome-screen': !welcomeScreenSeen, - }), - }, 'Please be aware that this version is still under development'), - ]), - - ]) - ) - } - toggleMetamaskActive () { if (!this.props.isUnlocked) { // currently inactive: redirect to password box @@ -402,8 +298,6 @@ App.propTypes = { isMouseUser: PropTypes.bool, setMouseUserState: PropTypes.func, t: PropTypes.func, - isRevealingSeedWords: PropTypes.bool, - clearSeedWords: PropTypes.func, } function mapStateToProps (state) { @@ -484,7 +378,6 @@ function mapDispatchToProps (dispatch, ownProps) { setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), - clearSeedWords: () => dispatch(actions.confirmSeedWords()), } } diff --git a/ui/app/components/app-header/app-header.component.js b/ui/app/components/app-header/app-header.component.js new file mode 100644 index 000000000..cf36e0d79 --- /dev/null +++ b/ui/app/components/app-header/app-header.component.js @@ -0,0 +1,106 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../../app/scripts/lib/enums') +const { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes') +const Identicon = require('../identicon') +const NetworkIndicator = require('../network') + +class AppHeader extends Component { + static propTypes = { + history: PropTypes.object, + location: PropTypes.object, + network: PropTypes.string, + provider: PropTypes.object, + networkDropdownOpen: PropTypes.bool, + showNetworkDropdown: PropTypes.func, + hideNetworkDropdown: PropTypes.func, + toggleAccountMenu: PropTypes.func, + selectedAddress: PropTypes.string, + isUnlocked: PropTypes.bool, + } + + static contextTypes = { + t: PropTypes.func, + } + + handleNetworkIndicatorClick (event) { + event.preventDefault() + event.stopPropagation() + + const { networkDropdownOpen, showNetworkDropdown, hideNetworkDropdown } = this.props + + return networkDropdownOpen === false + ? showNetworkDropdown() + : hideNetworkDropdown() + } + + renderAccountMenu () { + const { isUnlocked, toggleAccountMenu, selectedAddress } = this.props + + return isUnlocked && ( +
+ +
+ ) + } + + render () { + const { + network, + provider, + history, + location, + isUnlocked, + } = this.props + + if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) { + return null + } + + return ( +
+
+
history.push(DEFAULT_ROUTE)} + > + +
+

{ this.context.t('appName') }

+
+ { this.context.t('beta') } +
+
+
+
+
+ this.handleNetworkIndicatorClick(event)} + disabled={location.pathname === CONFIRM_TRANSACTION_ROUTE} + /> +
+ { this.renderAccountMenu() } +
+
+
+ ) + } +} + +export default AppHeader diff --git a/ui/app/components/app-header/app-header.container.js b/ui/app/components/app-header/app-header.container.js new file mode 100644 index 000000000..30d3f8cc4 --- /dev/null +++ b/ui/app/components/app-header/app-header.container.js @@ -0,0 +1,38 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' + +import AppHeader from './app-header.component' +const actions = require('../../actions') + +const mapStateToProps = state => { + const { appState, metamask } = state + const { networkDropdownOpen } = appState + const { + network, + provider, + selectedAddress, + isUnlocked, + } = metamask + + return { + networkDropdownOpen, + network, + provider, + selectedAddress, + isUnlocked, + } +} + +const mapDispatchToProps = dispatch => { + return { + showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), + hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), + toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(AppHeader) diff --git a/ui/app/components/app-header/index.js b/ui/app/components/app-header/index.js new file mode 100644 index 000000000..daa31f621 --- /dev/null +++ b/ui/app/components/app-header/index.js @@ -0,0 +1,2 @@ +import AppHeader from './app-header.container' +module.exports = AppHeader diff --git a/ui/app/components/button/button.component.js b/ui/app/components/button/button.component.js new file mode 100644 index 000000000..7769e4875 --- /dev/null +++ b/ui/app/components/button/button.component.js @@ -0,0 +1,43 @@ +const { Component } = require('react') +const h = require('react-hyperscript') +const PropTypes = require('prop-types') +const classnames = require('classnames') + +const SECONDARY = 'secondary' +const CLASSNAME_PRIMARY = 'btn-primary' +const CLASSNAME_PRIMARY_LARGE = 'btn-primary--lg' +const CLASSNAME_SECONDARY = 'btn-secondary' +const CLASSNAME_SECONDARY_LARGE = 'btn-secondary--lg' + +const getClassName = (type, large = false) => { + let output = type === SECONDARY ? CLASSNAME_SECONDARY : CLASSNAME_PRIMARY + + if (large) { + output += ` ${type === SECONDARY ? CLASSNAME_SECONDARY_LARGE : CLASSNAME_PRIMARY_LARGE}` + } + + return output +} + +class Button extends Component { + render () { + const { type, large, className, ...buttonProps } = this.props + + return ( + h('button', { + className: classnames(getClassName(type, large), className), + ...buttonProps, + }, this.props.children) + ) + } +} + +Button.propTypes = { + type: PropTypes.string, + large: PropTypes.bool, + className: PropTypes.string, + children: PropTypes.string, +} + +module.exports = Button + diff --git a/ui/app/components/button/button.stories.js b/ui/app/components/button/button.stories.js new file mode 100644 index 000000000..d1e14e869 --- /dev/null +++ b/ui/app/components/button/button.stories.js @@ -0,0 +1,41 @@ +import React from 'react' +import { storiesOf } from '@storybook/react' +import { action } from '@storybook/addon-actions' +import Button from './' +import { text } from '@storybook/addon-knobs/react' + +storiesOf('Button', module) + .add('primary', () => + + ) + .add('secondary', () => ( + + )) + .add('large primary', () => ( + + )) + .add('large secondary', () => ( + + )) diff --git a/ui/app/components/button/index.js b/ui/app/components/button/index.js new file mode 100644 index 000000000..3dc7d1eea --- /dev/null +++ b/ui/app/components/button/index.js @@ -0,0 +1,2 @@ +const Button = require('./button.component') +module.exports = Button diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index fda7c3e17..c6957d2aa 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -6,7 +6,7 @@ const connect = require('react-redux').connect const actions = require('../actions') const CoinbaseForm = require('./coinbase-form') const ShapeshiftForm = require('./shapeshift-form') -const Loading = require('./loading') +const Loading = require('./loading-screen') const AccountPanel = require('./account-panel') const RadioList = require('./custom-radio-list') const { getNetworkDisplayName } = require('../../../app/scripts/controllers/network/util') diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 845e4ddc2..df933e363 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -284,8 +284,7 @@ CustomizeGasModal.prototype.render = function () { h(GasModalCard, { value: convertedGasPrice, min: forceGasMin || MIN_GAS_PRICE_GWEI, - // max: 1000, - step: multiplyCurrencies(MIN_GAS_PRICE_GWEI, 10), + step: 1, onChange: value => this.convertAndSetGasPrice(value), title: this.context.t('gasPrice'), copy: this.context.t('gasPriceCalculation'), @@ -294,7 +293,6 @@ CustomizeGasModal.prototype.render = function () { h(GasModalCard, { value: convertedGasLimit, min: 1, - // max: 100000, step: 1, onChange: value => this.convertAndSetGasLimit(value), title: this.context.t('gasLimit'), diff --git a/ui/app/components/export-text-container/export-text-container.component.js b/ui/app/components/export-text-container/export-text-container.component.js new file mode 100644 index 000000000..c2546fa9b --- /dev/null +++ b/ui/app/components/export-text-container/export-text-container.component.js @@ -0,0 +1,45 @@ +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const copyToClipboard = require('copy-to-clipboard') +const { exportAsFile } = require('../../util') + +class ExportTextContainer extends Component { + render () { + const { text = '', filename = '' } = this.props + const { t } = this.context + + return ( + h('.export-text-container', [ + h('.export-text-container__text-container', [ + h('.export-text-container__text', text), + ]), + h('.export-text-container__buttons-container', [ + h('.export-text-container__button.export-text-container__button--copy', { + onClick: () => copyToClipboard(text), + }, [ + h('img', { src: 'images/copy-to-clipboard.svg' }), + h('.export-text-container__button-text', t('copyToClipboard')), + ]), + h('.export-text-container__button', { + onClick: () => exportAsFile(filename, text), + }, [ + h('img', { src: 'images/download.svg' }), + h('.export-text-container__button-text', t('saveAsCsvFile')), + ]), + ]), + ]) + ) + } +} + +ExportTextContainer.propTypes = { + text: PropTypes.string, + filename: PropTypes.string, +} + +ExportTextContainer.contextTypes = { + t: PropTypes.func, +} + +module.exports = ExportTextContainer diff --git a/ui/app/components/export-text-container/export-text-container.scss b/ui/app/components/export-text-container/export-text-container.scss new file mode 100644 index 000000000..975d62f70 --- /dev/null +++ b/ui/app/components/export-text-container/export-text-container.scss @@ -0,0 +1,52 @@ +.export-text-container { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + border: 1px solid $alto; + border-radius: 4px; + font-weight: 400; + + &__text-container { + width: 100%; + display: flex; + justify-content: center; + padding: 20px; + border-radius: 4px; + background: $alabaster; + } + + &__text { + resize: none; + border: none; + background: $alabaster; + font-size: 20px; + text-align: center; + } + + &__buttons-container { + display: flex; + flex-direction: row; + border-top: 1px solid $alto; + width: 100%; + } + + &__button { + padding: 10px; + flex: 1; + display: flex; + justify-content: center; + align-items: center; + font-size: 12px; + cursor: pointer; + color: $curious-blue; + + &--copy { + border-right: 1px solid $alto; + } + } + + &__button-text { + padding-left: 10px; + } +} diff --git a/ui/app/components/export-text-container/index.js b/ui/app/components/export-text-container/index.js new file mode 100644 index 000000000..b2864a717 --- /dev/null +++ b/ui/app/components/export-text-container/index.js @@ -0,0 +1,2 @@ +const ExportTextContainer = require('./export-text-container.component') +module.exports = ExportTextContainer diff --git a/ui/app/components/loading-screen/index.js b/ui/app/components/loading-screen/index.js new file mode 100644 index 000000000..191d953f7 --- /dev/null +++ b/ui/app/components/loading-screen/index.js @@ -0,0 +1,2 @@ +const LoadingScreen = require('./loading-screen.component') +module.exports = LoadingScreen diff --git a/ui/app/components/loading-screen/loading-screen.component.js b/ui/app/components/loading-screen/loading-screen.component.js new file mode 100644 index 000000000..bce2a4aac --- /dev/null +++ b/ui/app/components/loading-screen/loading-screen.component.js @@ -0,0 +1,35 @@ +const { Component } = require('react') +const h = require('react-hyperscript') +const PropTypes = require('prop-types') +const classnames = require('classnames') +const Spinner = require('../spinner') + +class LoadingScreen extends Component { + renderMessage () { + const { loadingMessage } = this.props + return loadingMessage && h('span', loadingMessage) + } + + render () { + return ( + h('.loading-overlay', { + className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }), + }, [ + h('.loading-overlay__container', [ + h(Spinner, { + color: '#F7C06C', + }), + + this.renderMessage(), + ]), + ]) + ) + } +} + +LoadingScreen.propTypes = { + loadingMessage: PropTypes.string, + fullScreen: PropTypes.bool, +} + +module.exports = LoadingScreen diff --git a/ui/app/components/loading.js b/ui/app/components/loading.js deleted file mode 100644 index b9afc550f..000000000 --- a/ui/app/components/loading.js +++ /dev/null @@ -1,34 +0,0 @@ -const { Component } = require('react') -const h = require('react-hyperscript') -const PropTypes = require('prop-types') -const classnames = require('classnames') - -class LoadingIndicator extends Component { - renderMessage () { - const { loadingMessage } = this.props - return loadingMessage && h('span', loadingMessage) - } - - render () { - return ( - h('.loading-overlay', { - className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }), - }, [ - h('.flex-center.flex-column', [ - h('img', { - src: 'images/loading.svg', - }), - - this.renderMessage(), - ]), - ]) - ) - } -} - -LoadingIndicator.propTypes = { - loadingMessage: PropTypes.string, - fullScreen: PropTypes.bool, -} - -module.exports = LoadingIndicator diff --git a/ui/app/components/page-container/page-container-footer/page-container-footer.component.js b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js index fafe1c19e..84d8296b0 100644 --- a/ui/app/components/page-container/page-container-footer/page-container-footer.component.js +++ b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js @@ -1,5 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' +import Button from './components/button' export default class PageContainerFooter extends Component { @@ -27,20 +28,24 @@ export default class PageContainerFooter extends Component { return (
- + - +
) diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js index 1e7fc3a41..6240499be 100644 --- a/ui/app/components/pages/add-token.js +++ b/ui/app/components/pages/add-token.js @@ -192,7 +192,7 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) if (symbol && decimals) { this.setState({ customSymbol: symbol, - customDecimals: decimals.toString(), + customDecimals: decimals, autoFilled: true, }) } diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js index 946907a47..0a3314b2a 100644 --- a/ui/app/components/pages/create-account/import-account/json.js +++ b/ui/app/components/pages/create-account/import-account/json.js @@ -105,6 +105,8 @@ class JsonImportSubview extends Component { } this.props.importNewJsonAccount([ fileContents, password ]) + // JS runtime requires caught rejections but failures are handled by Redux + .catch() } } diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js index c77612ea4..df7ac910a 100644 --- a/ui/app/components/pages/create-account/import-account/private-key.js +++ b/ui/app/components/pages/create-account/import-account/private-key.js @@ -91,5 +91,7 @@ PrivateKeyImportView.prototype.createNewKeychain = function () { const { importNewAccount, history } = this.props importNewAccount('Private Key', [ privateKey ]) + // JS runtime requires caught rejections but failures are handled by Redux + .catch() .then(() => history.push(DEFAULT_ROUTE)) } diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js index 90b8e1d37..9110f8202 100644 --- a/ui/app/components/pages/home.js +++ b/ui/app/components/pages/home.js @@ -21,7 +21,7 @@ const QrView = require('../../components/qr-code') // Routes const { - REVEAL_SEED_ROUTE, + INITIALIZE_BACKUP_PHRASE_ROUTE, RESTORE_VAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE, NOTICE_ROUTE, @@ -69,7 +69,7 @@ class Home extends Component { log.debug('rendering seed words') return h(Redirect, { to: { - pathname: REVEAL_SEED_ROUTE, + pathname: INITIALIZE_BACKUP_PHRASE_ROUTE, }, }) } diff --git a/ui/app/components/pages/keychains/reveal-seed.js b/ui/app/components/pages/keychains/reveal-seed.js index 247f3c8e2..685c81074 100644 --- a/ui/app/components/pages/keychains/reveal-seed.js +++ b/ui/app/components/pages/keychains/reveal-seed.js @@ -2,11 +2,27 @@ const { Component } = require('react') const { connect } = require('react-redux') const PropTypes = require('prop-types') const h = require('react-hyperscript') -const { exportAsFile } = require('../../../util') -const { requestRevealSeed, confirmSeedWords } = require('../../../actions') +const classnames = require('classnames') + +const { requestRevealSeedWords } = require('../../../actions') const { DEFAULT_ROUTE } = require('../../../routes') +const ExportTextContainer = require('../../export-text-container') + +const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' +const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' class RevealSeedPage extends Component { + constructor (props) { + super(props) + + this.state = { + screen: PASSWORD_PROMPT_SCREEN, + password: '', + seedWords: null, + error: null, + } + } + componentDidMount () { const passwordBox = document.getElementById('password-box') if (passwordBox) { @@ -14,182 +30,135 @@ class RevealSeedPage extends Component { } } - checkConfirmation (event) { - if (event.key === 'Enter') { - event.preventDefault() - this.revealSeedWords() - } + handleSubmit (event) { + event.preventDefault() + this.setState({ seedWords: null, error: null }) + this.props.requestRevealSeedWords(this.state.password) + .then(seedWords => this.setState({ seedWords, screen: REVEAL_SEED_SCREEN })) + .catch(error => this.setState({ error: error.message })) } - revealSeedWords () { - const password = document.getElementById('password-box').value - this.props.requestRevealSeed(password) - } - - renderSeed () { - const { seedWords, confirmSeedWords, history } = this.props - + renderWarning () { return ( - h('.initialize-screen.flex-column.flex-center.flex-grow', [ - - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginTop: 36, - marginBottom: 8, - width: '100%', - fontSize: '20px', - padding: 6, - }, - }, [ - 'Vault Created', - ]), - - h('div', { - style: { - fontSize: '1em', - marginTop: '10px', - textAlign: 'center', - }, - }, [ - h('span.error', 'These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret.'), - ]), - - h('textarea.twelve-word-phrase', { - readOnly: true, - value: seedWords, + h('.page-container__warning-container', [ + h('img.page-container__warning-icon', { + src: 'images/warning.svg', }), - - h('button.primary', { - onClick: () => confirmSeedWords().then(() => history.push(DEFAULT_ROUTE)), - style: { - margin: '24px', - fontSize: '0.9em', - marginBottom: '10px', - }, - }, 'I\'ve copied it somewhere safe'), - - h('button.primary', { - onClick: () => exportAsFile(`MetaMask Seed Words`, seedWords), - style: { - margin: '10px', - fontSize: '0.9em', - }, - }, 'Save Seed Words As File'), + h('.page-container__warning-message', [ + h('.page-container__warning-title', [this.context.t('revealSeedWordsWarningTitle')]), + h('div', [this.context.t('revealSeedWordsWarning')]), + ]), ]) ) } - renderConfirmation () { - const { history, warning, inProgress } = this.props + renderContent () { + return this.state.screen === PASSWORD_PROMPT_SCREEN + ? this.renderPasswordPromptContent() + : this.renderRevealSeedContent() + } + + renderPasswordPromptContent () { + const { t } = this.context return ( - h('.initialize-screen.flex-column.flex-center.flex-grow', { - style: { maxWidth: '420px' }, + h('form', { + onSubmit: event => this.handleSubmit(event), }, [ - - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginBottom: 24, - width: '100%', - fontSize: '20px', - padding: 6, - }, - }, [ - 'Reveal Seed Words', - ]), - - h('.div', { - style: { - display: 'flex', - flexDirection: 'column', - padding: '20px', - justifyContent: 'center', - }, - }, [ - - h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'), - - // confirmation - h('input.large-input.letter-spacey', { + h('label.input-label', { + htmlFor: 'password-box', + }, t('enterPasswordContinue')), + h('.input-group', [ + h('input.form-control', { type: 'password', + placeholder: t('password'), id: 'password-box', - placeholder: 'Enter your password to confirm', - onKeyPress: this.checkConfirmation.bind(this), - style: { - width: 260, - marginTop: '12px', - }, + value: this.state.password, + onChange: event => this.setState({ password: event.target.value }), + className: classnames({ 'form-control--error': this.state.error }), }), + ]), + this.state.error && h('.reveal-seed__error', this.state.error), + ]) + ) + } - h('.flex-row.flex-start', { - style: { - marginTop: 30, - width: '50%', - }, - }, [ - // cancel - h('button.primary', { - onClick: () => history.push(DEFAULT_ROUTE), - }, 'CANCEL'), - - // submit - h('button.primary', { - style: { marginLeft: '10px' }, - onClick: this.revealSeedWords.bind(this), - }, 'OK'), + renderRevealSeedContent () { + const { t } = this.context - ]), + return ( + h('div', [ + h('label.reveal-seed__label', t('yourPrivateSeedPhrase')), + h(ExportTextContainer, { + text: this.state.seedWords, + filename: t('metamaskSeedWords'), + }), + ]) + ) + } - warning && ( - h('span.error', { - style: { - margin: '20px', - }, - }, warning.split('-')) - ), - - inProgress && ( - h('span.in-progress-notification', 'Generating Seed...') - ), - ]), + renderFooter () { + return this.state.screen === PASSWORD_PROMPT_SCREEN + ? this.renderPasswordPromptFooter() + : this.renderRevealSeedFooter() + } + + renderPasswordPromptFooter () { + return ( + h('.page-container__footer', [ + h('button.btn-secondary--lg.page-container__footer-button', { + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('cancel')), + h('button.btn-primary--lg.page-container__footer-button', { + onClick: event => this.handleSubmit(event), + disabled: this.state.password === '', + }, this.context.t('next')), + ]) + ) + } + + renderRevealSeedFooter () { + return ( + h('.page-container__footer', [ + h('button.btn-secondary--lg.page-container__footer-button', { + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('close')), ]) ) } render () { - return this.props.seedWords - ? this.renderSeed() - : this.renderConfirmation() + return ( + h('.page-container', [ + h('.page-container__header', [ + h('.page-container__title', this.context.t('revealSeedWordsTitle')), + h('.page-container__subtitle', this.context.t('revealSeedWordsDescription')), + ]), + h('.page-container__content', [ + this.renderWarning(), + h('.reveal-seed__content', [ + this.renderContent(), + ]), + ]), + this.renderFooter(), + ]) + ) } } RevealSeedPage.propTypes = { - requestRevealSeed: PropTypes.func, - confirmSeedWords: PropTypes.func, - seedWords: PropTypes.string, - inProgress: PropTypes.bool, + requestRevealSeedWords: PropTypes.func, history: PropTypes.object, - warning: PropTypes.string, } -const mapStateToProps = state => { - const { appState: { warning }, metamask: { seedWords } } = state - - return { - warning, - seedWords, - } +RevealSeedPage.contextTypes = { + t: PropTypes.func, } const mapDispatchToProps = dispatch => { return { - requestRevealSeed: password => dispatch(requestRevealSeed(password)), - confirmSeedWords: () => dispatch(confirmSeedWords()), + requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)), } } -module.exports = connect(mapStateToProps, mapDispatchToProps)(RevealSeedPage) +module.exports = connect(null, mapDispatchToProps)(RevealSeedPage) diff --git a/ui/app/components/pages/unlock-page/index.js b/ui/app/components/pages/unlock-page/index.js new file mode 100644 index 000000000..be80cde4f --- /dev/null +++ b/ui/app/components/pages/unlock-page/index.js @@ -0,0 +1,2 @@ +import UnlockPage from './unlock-page.container' +module.exports = UnlockPage diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js new file mode 100644 index 000000000..d5e2a3224 --- /dev/null +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -0,0 +1,181 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Button from 'material-ui/Button' +import TextField from '../../text-field' + +const { ENVIRONMENT_TYPE_POPUP } = require('../../../../../app/scripts/lib/enums') +const { getEnvironmentType } = require('../../../../../app/scripts/lib/util') +const getCaretCoordinates = require('textarea-caret') +const EventEmitter = require('events').EventEmitter +const Mascot = require('../../mascot') +const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../../routes') + +class UnlockPage extends Component { + static contextTypes = { + t: PropTypes.func, + } + + constructor (props) { + super(props) + + this.state = { + password: '', + error: null, + } + + this.animationEventEmitter = new EventEmitter() + } + + componentWillMount () { + const { isUnlocked, history } = this.props + + if (isUnlocked) { + history.push(DEFAULT_ROUTE) + } + } + + tryUnlockMetamask (password) { + const { tryUnlockMetamask, history } = this.props + tryUnlockMetamask(password) + .then(() => history.push(DEFAULT_ROUTE)) + .catch(({ message }) => this.setState({ error: message })) + } + + handleSubmit (event) { + event.preventDefault() + event.stopPropagation() + + const { password } = this.state + const { tryUnlockMetamask, history } = this.props + + if (password === '') { + return + } + + this.setState({ error: null }) + + tryUnlockMetamask(password) + .then(() => history.push(DEFAULT_ROUTE)) + .catch(({ message }) => this.setState({ error: message })) + } + + handleInputChange ({ target }) { + this.setState({ password: target.value, error: null }) + + // tell mascot to look at page action + const element = target + const boundingRect = element.getBoundingClientRect() + const coordinates = getCaretCoordinates(element, element.selectionEnd) + this.animationEventEmitter.emit('point', { + x: boundingRect.left + coordinates.left - element.scrollLeft, + y: boundingRect.top + coordinates.top - element.scrollTop, + }) + } + + renderSubmitButton () { + const style = { + backgroundColor: '#f7861c', + color: 'white', + marginTop: '20px', + height: '60px', + fontWeight: '400', + boxShadow: 'none', + borderRadius: '4px', + } + + return ( + + ) + } + + render () { + const { error } = this.state + + return ( +
+
+
+ +
+

+ { this.context.t('welcomeBack') } +

+
{ this.context.t('unlockMessage') }
+
this.handleSubmit(event)} + > + this.handleInputChange(event)} + error={error} + autoFocus + autoComplete="current-password" + fullWidth + /> + + { this.renderSubmitButton() } +
+
{ + this.props.markPasswordForgotten() + this.props.history.push(RESTORE_VAULT_ROUTE) + + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { + global.platform.openExtensionInBrowser() + } + }} + > + { this.context.t('restoreFromSeed') } +
+
{ + this.props.markPasswordForgotten() + this.props.history.push(RESTORE_VAULT_ROUTE) + + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { + global.platform.openExtensionInBrowser() + } + }} + > + { this.context.t('importUsingSeed') } +
+
+
+
+ ) + } +} + +UnlockPage.propTypes = { + forgotPassword: PropTypes.func, + tryUnlockMetamask: PropTypes.func, + markPasswordForgotten: PropTypes.func, + history: PropTypes.object, + isUnlocked: PropTypes.bool, + t: PropTypes.func, + useOldInterface: PropTypes.func, + setNetworkEndpoints: PropTypes.func, +} + +export default UnlockPage diff --git a/ui/app/components/pages/unlock-page/unlock-page.container.js b/ui/app/components/pages/unlock-page/unlock-page.container.js new file mode 100644 index 000000000..9788a18ea --- /dev/null +++ b/ui/app/components/pages/unlock-page/unlock-page.container.js @@ -0,0 +1,33 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' + +const { + tryUnlockMetamask, + forgotPassword, + markPasswordForgotten, + setNetworkEndpoints, +} = require('../../../actions') + +import UnlockPage from './unlock-page.component' + +const mapStateToProps = state => { + const { metamask: { isUnlocked } } = state + return { + isUnlocked, + } +} + +const mapDispatchToProps = dispatch => { + return { + forgotPassword: () => dispatch(forgotPassword()), + tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), + markPasswordForgotten: () => dispatch(markPasswordForgotten()), + setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(UnlockPage) diff --git a/ui/app/components/pages/unlock-page/unlock-page.scss b/ui/app/components/pages/unlock-page/unlock-page.scss new file mode 100644 index 000000000..3d44bd037 --- /dev/null +++ b/ui/app/components/pages/unlock-page/unlock-page.scss @@ -0,0 +1,51 @@ +.unlock-page { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + width: 357px; + padding: 30px; + font-weight: 400; + color: $silver-chalice; + + &__container { + background: $white; + display: flex; + align-self: stretch; + justify-content: center; + flex: 1 0 auto; + } + + &__mascot-container { + margin-top: 24px; + } + + &__title { + margin-top: 5px; + font-size: 2rem; + font-weight: 800; + color: $tundora; + } + + &__form { + width: 100%; + margin: 56px 0 8px; + } + + &__links { + margin-top: 25px; + width: 100%; + } + + &__link { + cursor: pointer; + + &--import { + color: $ecstasy; + } + + &--use-classic { + margin-top: 10px; + } + } +} diff --git a/ui/app/components/pages/unlock.js b/ui/app/components/pages/unlock.js deleted file mode 100644 index 30144b978..000000000 --- a/ui/app/components/pages/unlock.js +++ /dev/null @@ -1,194 +0,0 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const connect = require('../../metamask-connect') -const h = require('react-hyperscript') -const { withRouter } = require('react-router-dom') -const { compose } = require('recompose') -const { - tryUnlockMetamask, - forgotPassword, - markPasswordForgotten, - setNetworkEndpoints, - setFeatureFlag, -} = require('../../actions') -const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums') -const { getEnvironmentType } = require('../../../../app/scripts/lib/util') -const getCaretCoordinates = require('textarea-caret') -const EventEmitter = require('events').EventEmitter -const Mascot = require('../mascot') -const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/controllers/network/enums') -const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes') - -class UnlockScreen extends Component { - constructor (props) { - super(props) - - this.state = { - error: null, - } - - this.animationEventEmitter = new EventEmitter() - } - - componentWillMount () { - const { isUnlocked, history } = this.props - - if (isUnlocked) { - history.push(DEFAULT_ROUTE) - } - } - - componentDidMount () { - const passwordBox = document.getElementById('password-box') - - if (passwordBox) { - passwordBox.focus() - } - } - - tryUnlockMetamask (password) { - const { tryUnlockMetamask, history } = this.props - tryUnlockMetamask(password) - .then(() => history.push(DEFAULT_ROUTE)) - .catch(({ message }) => this.setState({ error: message })) - } - - onSubmit (event) { - const input = document.getElementById('password-box') - const password = input.value - this.tryUnlockMetamask(password) - } - - onKeyPress (event) { - if (event.key === 'Enter') { - this.submitPassword(event) - } - } - - submitPassword (event) { - var element = event.target - var password = element.value - // reset input - element.value = '' - this.tryUnlockMetamask(password) - } - - inputChanged (event) { - // tell mascot to look at page action - var element = event.target - var boundingRect = element.getBoundingClientRect() - var coordinates = getCaretCoordinates(element, element.selectionEnd) - this.animationEventEmitter.emit('point', { - x: boundingRect.left + coordinates.left - element.scrollLeft, - y: boundingRect.top + coordinates.top - element.scrollTop, - }) - } - - render () { - const { error } = this.state - return ( - h('.unlock-screen', [ - - h(Mascot, { - animationEventEmitter: this.animationEventEmitter, - }), - - h('h1', { - style: { - fontSize: '1.4em', - textTransform: 'uppercase', - color: '#7F8082', - }, - }, this.props.t('appName')), - - h('input.large-input', { - type: 'password', - id: 'password-box', - placeholder: 'enter password', - style: { - background: 'white', - }, - onKeyPress: this.onKeyPress.bind(this), - onInput: this.inputChanged.bind(this), - }), - - h('.error', { - style: { - display: error ? 'block' : 'none', - padding: '0 20px', - textAlign: 'center', - }, - }, error), - - h('button.primary.cursor-pointer', { - onClick: this.onSubmit.bind(this), - style: { - margin: 10, - }, - }, this.props.t('login')), - - h('p.pointer', { - onClick: () => { - this.props.markPasswordForgotten() - this.props.history.push(RESTORE_VAULT_ROUTE) - - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { - global.platform.openExtensionInBrowser() - } - }, - style: { - fontSize: '0.8em', - color: 'rgb(247, 134, 28)', - textDecoration: 'underline', - }, - }, this.props.t('restoreFromSeed')), - - h('p.pointer', { - onClick: () => { - this.props.useOldInterface() - .then(() => this.props.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)) - }, - style: { - fontSize: '0.8em', - color: '#aeaeae', - textDecoration: 'underline', - marginTop: '32px', - }, - }, this.props.t('classicInterface')), - ]) - ) - } -} - -UnlockScreen.propTypes = { - forgotPassword: PropTypes.func, - tryUnlockMetamask: PropTypes.func, - markPasswordForgotten: PropTypes.func, - history: PropTypes.object, - isUnlocked: PropTypes.bool, - t: PropTypes.func, - useOldInterface: PropTypes.func, - setNetworkEndpoints: PropTypes.func, -} - -const mapStateToProps = state => { - const { metamask: { isUnlocked } } = state - return { - isUnlocked, - } -} - -const mapDispatchToProps = dispatch => { - return { - forgotPassword: () => dispatch(forgotPassword()), - tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), - markPasswordForgotten: () => dispatch(markPasswordForgotten()), - useOldInterface: () => dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')), - setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)), - } -} - -module.exports = compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(UnlockScreen) diff --git a/ui/app/components/pending-tx/index.js b/ui/app/components/pending-tx/index.js index 6ee83ba7e..893538bcf 100644 --- a/ui/app/components/pending-tx/index.js +++ b/ui/app/components/pending-tx/index.js @@ -8,11 +8,11 @@ const abiDecoder = require('abi-decoder') abiDecoder.addABI(abi) const inherits = require('util').inherits const actions = require('../../actions') -const util = require('../../util') +const { getSymbolAndDecimals } = require('../../token-util') const ConfirmSendEther = require('./confirm-send-ether') const ConfirmSendToken = require('./confirm-send-token') const ConfirmDeployContract = require('./confirm-deploy-contract') -const Loading = require('../loading') +const Loading = require('../loading-screen') const TX_TYPES = { DEPLOY_CONTRACT: 'deploy_contract', @@ -26,6 +26,7 @@ function mapStateToProps (state) { const { conversionRate, identities, + tokens: existingTokens, } = state.metamask const accounts = state.metamask.accounts const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] @@ -33,6 +34,7 @@ function mapStateToProps (state) { conversionRate, identities, selectedAddress, + existingTokens, } } @@ -66,6 +68,7 @@ PendingTx.prototype.componentDidUpdate = function (prevProps, prevState) { } PendingTx.prototype.setTokenData = async function () { + const { existingTokens } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} @@ -89,30 +92,15 @@ PendingTx.prototype.setTokenData = async function () { } if (isTokenTransaction) { - const token = util.getContractAtAddress(txParams.to) - const results = await Promise.all([ - token.symbol(), - token.decimals(), - ]) - const [ symbol, decimals ] = results - - if (symbol[0] && decimals[0]) { - this.setState({ - transactionType: TX_TYPES.SEND_TOKEN, - tokenAddress: txParams.to, - tokenSymbol: symbol[0], - tokenDecimals: decimals[0], - isFetching: false, - }) - } else { - this.setState({ - transactionType: TX_TYPES.SEND_TOKEN, - tokenAddress: txParams.to, - tokenSymbol: null, - tokenDecimals: null, - isFetching: false, - }) - } + const { symbol, decimals } = await getSymbolAndDecimals(txParams.to, existingTokens) + + this.setState({ + transactionType: TX_TYPES.SEND_TOKEN, + tokenAddress: txParams.to, + tokenSymbol: symbol, + tokenDecimals: decimals, + isFetching: false, + }) } else { this.setState({ transactionType: TX_TYPES.SEND_ETHER, diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index a7bd5d7ea..90fb2b66c 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -89,7 +89,6 @@ CurrencyDisplay.prototype.render = function () { } = this.props const valueToRender = this.getValueToRender() - const convertedValueToRender = this.getConvertedValueToRender(valueToRender) return h('div', { @@ -97,22 +96,24 @@ CurrencyDisplay.prototype.render = function () { style: { borderColor: inError ? 'red' : null, }, - onClick: () => this.currencyInput.focus(), + onClick: () => this.currencyInput && this.currencyInput.focus(), }, [ h('div.currency-display__primary-row', [ h('div.currency-display__input-wrapper', [ - h(CurrencyInput, { + h(readOnly ? 'input' : CurrencyInput, { className: primaryBalanceClassName, value: `${valueToRender}`, placeholder: '0', readOnly, - onInputChange: newValue => { - handleChange(this.getAmount(newValue)) - }, - inputRef: input => { this.currencyInput = input }, + ...(!readOnly ? { + onInputChange: newValue => { + handleChange(this.getAmount(newValue)) + }, + inputRef: input => { this.currencyInput = input }, + } : {}), }), h('span.currency-display__currency-symbol', primaryCurrency), diff --git a/ui/app/components/send_/send.constants.js b/ui/app/components/send_/send.constants.js index d047ed704..b59fcaaf0 100644 --- a/ui/app/components/send_/send.constants.js +++ b/ui/app/components/send_/send.constants.js @@ -1,8 +1,8 @@ const ethUtil = require('ethereumjs-util') const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') -const MIN_GAS_PRICE_HEX = (100000000).toString(16) -const MIN_GAS_PRICE_DEC = '100000000' +const MIN_GAS_PRICE_DEC = '0' +const MIN_GAS_PRICE_HEX = (parseInt(MIN_GAS_PRICE_DEC)).toString(16) const MIN_GAS_LIMIT_DEC = '21000' const MIN_GAS_LIMIT_HEX = (parseInt(MIN_GAS_LIMIT_DEC)).toString(16) diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index fd4a80a4a..22ab64426 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -55,6 +55,10 @@ function ShapeshiftForm () { } } +ShapeshiftForm.prototype.getCoinPair = function () { + return `${this.state.depositCoin.toUpperCase()}_ETH` +} + ShapeshiftForm.prototype.componentWillMount = function () { this.props.shapeShiftSubview() } @@ -120,14 +124,12 @@ ShapeshiftForm.prototype.renderMetadata = function (label, value) { } ShapeshiftForm.prototype.renderMarketInfo = function () { - const { depositCoin } = this.state - const coinPair = `${depositCoin}_eth` const { tokenExchangeRates } = this.props const { limit, rate, minimum, - } = tokenExchangeRates[coinPair] || {} + } = tokenExchangeRates[this.getCoinPair()] || {} return h('div.shapeshift-form__metadata', {}, [ @@ -172,10 +174,9 @@ ShapeshiftForm.prototype.renderQrCode = function () { ShapeshiftForm.prototype.render = function () { const { coinOptions, btnClass, warning } = this.props - const { depositCoin, errorMessage, showQrCode, depositAddress } = this.state - const coinPair = `${depositCoin}_eth` + const { errorMessage, showQrCode, depositAddress } = this.state const { tokenExchangeRates } = this.props - const token = tokenExchangeRates[coinPair] + const token = tokenExchangeRates[this.getCoinPair()] return h('div.shapeshift-form-wrapper', [ showQrCode diff --git a/ui/app/components/spinner/index.js b/ui/app/components/spinner/index.js new file mode 100644 index 000000000..9589efcf0 --- /dev/null +++ b/ui/app/components/spinner/index.js @@ -0,0 +1,2 @@ +const Spinner = require('./spinner.component') +module.exports = Spinner diff --git a/ui/app/components/spinner/spinner.component.js b/ui/app/components/spinner/spinner.component.js new file mode 100644 index 000000000..b9a2eb52a --- /dev/null +++ b/ui/app/components/spinner/spinner.component.js @@ -0,0 +1,78 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const Spinner = ({ className = '', color = '#000000' }) => { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ) +} + +Spinner.propTypes = { + className: PropTypes.string, + color: PropTypes.string, +} + +module.exports = Spinner diff --git a/ui/app/components/text-field/index.js b/ui/app/components/text-field/index.js new file mode 100644 index 000000000..171caf7a4 --- /dev/null +++ b/ui/app/components/text-field/index.js @@ -0,0 +1,2 @@ +import TextField from './text-field.component' +module.exports = TextField diff --git a/ui/app/components/text-field/text-field.component.js b/ui/app/components/text-field/text-field.component.js new file mode 100644 index 000000000..4a02f76d8 --- /dev/null +++ b/ui/app/components/text-field/text-field.component.js @@ -0,0 +1,54 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { withStyles } from 'material-ui/styles' +import { default as MaterialTextField } from 'material-ui/TextField' + +const styles = { + cssLabel: { + '&$cssFocused': { + color: '#aeaeae', + }, + fontWeight: '400', + color: '#aeaeae', + }, + cssFocused: {}, + cssUnderline: { + '&:after': { + backgroundColor: '#f7861c', + }, + }, +} + +const TextField = props => { + const { error, classes, ...textFieldProps } = props + + return ( + + ) +} + +TextField.defaultProps = { + error: null, +} + +TextField.propTypes = { + error: PropTypes.string, + classes: PropTypes.object, +} + +export default withStyles(styles)(TextField) diff --git a/ui/app/components/text-field/text-field.stories.js b/ui/app/components/text-field/text-field.stories.js new file mode 100644 index 000000000..ee3e5faaf --- /dev/null +++ b/ui/app/components/text-field/text-field.stories.js @@ -0,0 +1,24 @@ +import React from 'react' +import { storiesOf } from '@storybook/react' +import TextField from './' + +storiesOf('TextField', module) + .add('text', () => + + ) + .add('password', () => + + ) + .add('error', () => + + ) diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 9e430f87b..3b29dacac 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -102,6 +102,7 @@ WalletView.prototype.render = function () { selectedIdentity, keyrings, showAccountDetailModal, + sidebarOpen, hideSidebar, history, } = this.props @@ -182,7 +183,10 @@ WalletView.prototype.render = function () { h(TokenList), h('button.btn-primary.wallet-view__add-token-button', { - onClick: () => history.push(ADD_TOKEN_ROUTE), + onClick: () => { + history.push(ADD_TOKEN_ROUTE) + sidebarOpen && hideSidebar() + }, }, this.context.t('addToken')), ]) } diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index b71538e31..fb38aaa76 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -13,7 +13,7 @@ const SignatureRequest = require('./components/signature-request') // const PendingMsg = require('./components/pending-msg') // const PendingPersonalMsg = require('./components/pending-personal-msg') // const PendingTypedMsg = require('./components/pending-typed-msg') -const Loading = require('./components/loading') +const Loading = require('./components/loading-screen') const { DEFAULT_ROUTE } = require('./routes') module.exports = compose( diff --git a/ui/app/css/itcss/components/account-dropdown.scss b/ui/app/css/itcss/components/account-dropdown.scss index 725da9d39..b29afdc8c 100644 --- a/ui/app/css/itcss/components/account-dropdown.scss +++ b/ui/app/css/itcss/components/account-dropdown.scss @@ -33,7 +33,7 @@ margin-top: 4px; position: relative; } - + &__account-name { font-size: 16px; margin-left: 8px; @@ -50,7 +50,6 @@ font-family: Roboto; line-height: 16px; font-size: 12px; - font-weight: 300; } &__account-primary-balance { diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss index c4037d862..824b2ddb6 100644 --- a/ui/app/css/itcss/components/account-menu.scss +++ b/ui/app/css/itcss/components/account-menu.scss @@ -40,7 +40,6 @@ font-size: 12px; line-height: 23px; padding: 0 24px; - font-weight: 300; } &__item-icon { @@ -113,7 +112,6 @@ &__name { color: $white; font-size: 18px; - font-weight: 300; } &__balance { @@ -124,7 +122,6 @@ &__action { font-size: 16px; line-height: 18px; - font-weight: 300; cursor: pointer; } } diff --git a/ui/app/css/itcss/components/add-token.scss b/ui/app/css/itcss/components/add-token.scss index 01579c27c..a3ea0d85b 100644 --- a/ui/app/css/itcss/components/add-token.scss +++ b/ui/app/css/itcss/components/add-token.scss @@ -377,7 +377,6 @@ &__amount { color: $scorpion; font-size: 43px; - font-weight: 300; line-height: 43px; margin-right: 8px; } diff --git a/ui/app/css/itcss/components/buttons.scss b/ui/app/css/itcss/components/buttons.scss index 04e1ed96e..86daf60d8 100644 --- a/ui/app/css/itcss/components/buttons.scss +++ b/ui/app/css/itcss/components/buttons.scss @@ -18,6 +18,7 @@ padding: 0 20px; min-width: 140px; text-transform: uppercase; + outline: none; } .btn-primary, diff --git a/ui/app/css/itcss/components/confirm.scss b/ui/app/css/itcss/components/confirm.scss index 47762e8de..44cfcf870 100644 --- a/ui/app/css/itcss/components/confirm.scss +++ b/ui/app/css/itcss/components/confirm.scss @@ -175,7 +175,6 @@ margin-top: 12px; text-align: center; font-size: 40px; - font-weight: 300; line-height: 53px; flex: 0 0 auto; } @@ -235,7 +234,6 @@ section .confirm-screen-account-number, padding-left: 35px; font-size: 16px; line-height: 22px; - font-weight: 300; &:not(:last-of-type) { border-bottom: 1px solid $alto; @@ -336,7 +334,6 @@ section .confirm-screen-account-number, border-width: 0; box-shadow: none; flex: 1 0 auto; - font-weight: 300; margin: 0 5px; } @@ -353,6 +350,5 @@ section .confirm-screen-account-number, box-shadow: none; cursor: pointer; flex: 1 0 auto; - font-weight: 300; margin: 0 5px; } diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss index e043c1966..36d843c79 100644 --- a/ui/app/css/itcss/components/currency-display.scss +++ b/ui/app/css/itcss/components/currency-display.scss @@ -7,7 +7,6 @@ color: $scorpion; font-family: Roboto; font-size: 16px; - font-weight: 300; padding: 8px 10px; position: relative; diff --git a/ui/app/css/itcss/components/header.scss b/ui/app/css/itcss/components/header.scss index eeed9ee06..cef61d0e2 100644 --- a/ui/app/css/itcss/components/header.scss +++ b/ui/app/css/itcss/components/header.scss @@ -1,15 +1,15 @@ .app-header { align-items: center; - visibility: visible; background: $gallery; position: relative; z-index: $header-z-index; display: flex; flex-flow: column nowrap; + width: 100%; + flex: 0 0 auto; @media screen and (max-width: 575px) { padding: 12px; - width: 100%; box-shadow: 0 0 0 1px rgba(0, 0, 0, .08); z-index: $mobile-header-z-index; } @@ -17,48 +17,75 @@ @media screen and (min-width: 576px) { height: 75px; justify-content: center; + + &--back-drop { + &::after { + content: ''; + position: absolute; + width: 100%; + height: 32px; + background: $gallery; + bottom: -32px; + } + } } - .metafox-icon { + &__metafox { cursor: pointer; } -} - -.app-header--initialized { - @media screen and (min-width: 576px) { - &::after { - content: ''; - position: absolute; - width: 100%; - height: 32px; - background: $gallery; - bottom: -32px; + &__beta-label { + font-family: Roboto; + text-transform: uppercase; + font-weight: 500; + font-size: .8rem; + color: $buttercup; + margin-left: 5px; + line-height: initial; + + @media screen and (max-width: 575px) { + display: none; } } -} -.app-header-contents { - display: flex; - justify-content: space-between; - flex-flow: row nowrap; - width: 100%; - height: 6.9vh; + &__contents { + display: flex; + justify-content: space-between; + flex-flow: row nowrap; + width: 100%; - @media screen and (max-width: 575px) { - height: 100%; - } + @media screen and (max-width: 575px) { + height: 100%; + } - @media screen and (min-width: 576px) { - width: 85vw; + @media screen and (min-width: 576px) { + width: 85vw; + } + + @media screen and (min-width: 769px) { + width: 80vw; + } + + @media screen and (min-width: 1281px) { + width: 62vw; + } } - @media screen and (min-width: 769px) { - width: 80vw; + &__logo-container { + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; } - @media screen and (min-width: 1281px) { - width: 62vw; + &__account-menu-container { + display: flex; + flex-flow: row nowrap; + align-items: center; + + .identicon { + cursor: pointer; + } } } @@ -76,20 +103,6 @@ } } -.beta-label { - font-family: Roboto; - text-transform: uppercase; - font-weight: 500; - font-size: .8rem; - color: $buttercup; - margin-left: 5px; - line-height: initial; - - @media screen and (max-width: 575px) { - display: none; - } -} - h2.page-subtitle { text-transform: uppercase; color: #aeaeae; @@ -102,20 +115,3 @@ h2.page-subtitle { flex-direction: row; align-items: center; } - -.left-menu-wrapper { - display: flex; - flex-direction: row; - align-items: center; - cursor: pointer; -} - -.header__right-actions { - display: flex; - flex-flow: row nowrap; - align-items: center; - - .identicon { - cursor: pointer; - } -} diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index 959eb9d15..1c544e162 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -61,3 +61,5 @@ @import './welcome-screen.scss'; @import './sender-to-recipient.scss'; + +@import '../../../components/export-text-container/export-text-container.scss'; diff --git a/ui/app/css/itcss/components/loading-overlay.scss b/ui/app/css/itcss/components/loading-overlay.scss index a92fffec5..c18b7fa59 100644 --- a/ui/app/css/itcss/components/loading-overlay.scss +++ b/ui/app/css/itcss/components/loading-overlay.scss @@ -26,4 +26,25 @@ width: 100vw; margin-top: 0; } + + &__container { + position: absolute; + top: 33%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + &__message { + margin-top: 32px; + font-weight: 400; + font-size: 20px; + color: $manatee; + } +} + +.spinner { + height: 58px; + width: 58px; } diff --git a/ui/app/css/itcss/components/menu.scss b/ui/app/css/itcss/components/menu.scss index eb92a1b70..6409ad545 100644 --- a/ui/app/css/itcss/components/menu.scss +++ b/ui/app/css/itcss/components/menu.scss @@ -11,7 +11,6 @@ flex-flow: row nowrap; align-items: center; position: relative; - font-weight: 300; z-index: 201; @media screen and (max-width: 575px) { diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index 9ae3ea7fa..f972c0f7a 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -368,7 +368,6 @@ resize: none; padding: 9px 13px 8px; text-transform: uppercase; - font-weight: 300; } @@ -796,7 +795,6 @@ .simple-dropdown { color: #5B5D67; font-size: 16px; - font-weight: 300; line-height: 21px; border: 1px solid #D8D8D8; background-color: #FFFFFF; diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index 777a82318..2903e07b4 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -117,7 +117,6 @@ $wallet-view-bg: $alabaster; font-size: 14px; line-height: 12px; padding: 4px 12px; - font-weight: 300; cursor: pointer; flex: 0 0 auto; @@ -264,7 +263,6 @@ $wallet-view-bg: $alabaster; // wallet view .account-name { font-size: 24px; - font-weight: 300; color: $black; margin-top: 8px; margin-bottom: .9rem; diff --git a/ui/app/css/itcss/components/pages/index.scss b/ui/app/css/itcss/components/pages/index.scss index 82446fd7a..195185fff 100644 --- a/ui/app/css/itcss/components/pages/index.scss +++ b/ui/app/css/itcss/components/pages/index.scss @@ -1 +1,3 @@ -@import './unlock.scss'; +@import './reveal-seed.scss'; + +@import '../../../../components/pages/unlock-page/unlock-page.scss'; diff --git a/ui/app/css/itcss/components/pages/reveal-seed.scss b/ui/app/css/itcss/components/pages/reveal-seed.scss new file mode 100644 index 000000000..b8f13af4a --- /dev/null +++ b/ui/app/css/itcss/components/pages/reveal-seed.scss @@ -0,0 +1,17 @@ +.reveal-seed { + &__content { + padding: 20px; + } + + &__label { + padding-bottom: 10px; + font-weight: 400; + display: inline-block; + } + + &__error { + color: $crimson; + font-size: 14px; + padding-top: 5px; + } +} diff --git a/ui/app/css/itcss/components/pages/unlock.scss b/ui/app/css/itcss/components/pages/unlock.scss deleted file mode 100644 index 5d438377b..000000000 --- a/ui/app/css/itcss/components/pages/unlock.scss +++ /dev/null @@ -1,9 +0,0 @@ -.unlock-page { - box-shadow: none; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background: rgb(247, 247, 247); - width: 100%; -} diff --git a/ui/app/css/itcss/components/request-signature.scss b/ui/app/css/itcss/components/request-signature.scss index 083481b8f..8bba6c98e 100644 --- a/ui/app/css/itcss/components/request-signature.scss +++ b/ui/app/css/itcss/components/request-signature.scss @@ -48,7 +48,6 @@ color: #5B5D67; font-family: Roboto; font-size: 22px; - font-weight: 300; line-height: 29px; z-index: 3; } @@ -125,7 +124,6 @@ color: $tundora; font-family: Roboto; font-size: 18px; - font-weight: 300; line-height: 24px; text-align: center; margin-top: 20px; diff --git a/ui/app/css/itcss/components/sections.scss b/ui/app/css/itcss/components/sections.scss index ace46bd8a..feec71c89 100644 --- a/ui/app/css/itcss/components/sections.scss +++ b/ui/app/css/itcss/components/sections.scss @@ -95,19 +95,6 @@ textarea.twelve-word-phrase { margin: -2px 8px 0px -8px; } -.unlock-screen #metamask-mascot-container { - margin-top: 24px; -} - -.unlock-screen h1 { - margin-top: -28px; - margin-bottom: 42px; -} - -.unlock-screen input[type=password] { - width: 260px; -} - .sizing-input { font-size: 14px; height: 30px; @@ -118,34 +105,6 @@ textarea.twelve-word-phrase { display: flex; } -/* Webkit */ - -.unlock-screen input::-webkit-input-placeholder { - text-align: center; - font-size: 1.2em; -} - -/* Firefox 18- */ - -.unlock-screen input:-moz-placeholder { - text-align: center; - font-size: 1.2em; -} - -/* Firefox 19+ */ - -.unlock-screen input::-moz-placeholder { - text-align: center; - font-size: 1.2em; -} - -/* IE */ - -.unlock-screen input:-ms-input-placeholder { - text-align: center; - font-size: 1.2em; -} - /* accounts */ .accounts-section { diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss index 362feeec8..c168242cf 100644 --- a/ui/app/css/itcss/components/send.scss +++ b/ui/app/css/itcss/components/send.scss @@ -507,7 +507,6 @@ &__copy { color: $gray; font-size: 14px; - font-weight: 300; line-height: 19px; text-align: center; margin-top: 10px; @@ -641,7 +640,6 @@ font-family: Roboto; font-size: 16px; line-height: 21px; - font-weight: 300; } } @@ -832,7 +830,6 @@ color: $tundora; font-family: Roboto; font-size: 20px; - font-weight: 300; line-height: 26px; margin-top: 17px; } diff --git a/ui/app/css/itcss/components/settings.scss b/ui/app/css/itcss/components/settings.scss index dcc9b98d5..0dd61ac5e 100644 --- a/ui/app/css/itcss/components/settings.scss +++ b/ui/app/css/itcss/components/settings.scss @@ -3,8 +3,6 @@ background: $white; display: flex; flex-flow: column nowrap; - height: auto; - overflow: auto; } .settings__header { @@ -29,6 +27,8 @@ .settings__content { padding: 0 25px; + height: auto; + overflow: auto; } .settings__content-row { diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss index 92321394b..9b2982096 100644 --- a/ui/app/css/itcss/generic/index.scss +++ b/ui/app/css/itcss/generic/index.scss @@ -12,7 +12,7 @@ html, body { font-family: Roboto, Arial; color: #4d4d4d; - font-weight: 300; + font-weight: 400; background: #f7f7f7; width: 100%; height: 100%; @@ -205,8 +205,27 @@ input.large-input { } &__content { - height: 100%; overflow-y: auto; + flex: 1; + } + + &__warning-container { + background: $linen; + padding: 20px; + display: flex; + align-items: start; + } + + &__warning-message { + padding-left: 15px; + } + + &__warning-title { + font-weight: 500; + } + + &__warning-icon { + padding-top: 5px; } } @@ -235,5 +254,52 @@ input.large-input { overflow-y: auto; background-color: $white; border-radius: 0; + flex: 1; + } +} + +@media screen and (min-width: 576px) { + .page-container { + height: 600px; + flex: 0 0 auto; + } +} + +.input-label { + padding-bottom: 10px; + font-weight: 400; + display: inline-block; +} + +input.form-control { + padding-left: 10px; + font-size: 14px; + height: 40px; + border: 1px solid $alto; + border-radius: 3px; + width: 100%; + + &::-webkit-input-placeholder { + font-weight: 100; + color: $dusty-gray; + } + + &::-moz-placeholder { + font-weight: 100; + color: $dusty-gray; + } + + &:-ms-input-placeholder { + font-weight: 100; + color: $dusty-gray; + } + + &:-moz-placeholder { + font-weight: 100; + color: $dusty-gray; + } + + &--error { + border: 1px solid $monzo; } } diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss index 51548306f..814d7a382 100644 --- a/ui/app/css/itcss/settings/variables.scss +++ b/ui/app/css/itcss/settings/variables.scss @@ -54,6 +54,7 @@ $saffron: #f6c343; $dodger-blue: #3099f2; $zumthor: #edf7ff; $ecstasy: #f7861c; +$linen: #fdf4f4; /* Z-Indicies diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js deleted file mode 100644 index eb588415f..000000000 --- a/ui/app/keychains/hd/recover-seed/confirmation.js +++ /dev/null @@ -1,138 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const PropTypes = require('prop-types') -const connect = require('react-redux').connect -const h = require('react-hyperscript') -const actions = require('../../../actions') -const { withRouter } = require('react-router-dom') -const { compose } = require('recompose') -const { - DEFAULT_ROUTE, - INITIALIZE_BACKUP_PHRASE_ROUTE, -} = require('../../../routes') - -RevealSeedConfirmation.contextTypes = { - t: PropTypes.func, -} - -module.exports = compose( - withRouter, - connect(mapStateToProps) -)(RevealSeedConfirmation) - - -inherits(RevealSeedConfirmation, Component) -function RevealSeedConfirmation () { - Component.call(this) -} - -function mapStateToProps (state) { - return { - warning: state.appState.warning, - } -} - -RevealSeedConfirmation.prototype.render = function () { - const props = this.props - - return ( - - h('.initialize-screen.flex-column.flex-center.flex-grow', { - style: { maxWidth: '420px' }, - }, [ - - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginBottom: 24, - width: '100%', - fontSize: '20px', - padding: 6, - }, - }, [ - 'Reveal Seed Words', - ]), - - h('.div', { - style: { - display: 'flex', - flexDirection: 'column', - padding: '20px', - justifyContent: 'center', - }, - }, [ - - h('h4', this.context.t('revealSeedWordsWarning')), - - // confirmation - h('input.large-input.letter-spacey', { - type: 'password', - id: 'password-box', - placeholder: this.context.t('enterPasswordConfirm'), - onKeyPress: this.checkConfirmation.bind(this), - style: { - width: 260, - marginTop: '12px', - }, - }), - - h('.flex-row.flex-start', { - style: { - marginTop: 30, - width: '50%', - }, - }, [ - // cancel - h('button.primary', { - onClick: this.goHome.bind(this), - }, 'CANCEL'), - - // submit - h('button.primary', { - style: { marginLeft: '10px' }, - onClick: this.revealSeedWords.bind(this), - }, 'OK'), - - ]), - - (props.warning) && ( - h('span.error', { - style: { - margin: '20px', - }, - }, props.warning.split('-')) - ), - - props.inProgress && ( - h('span.in-progress-notification', this.context.t('generatingSeed')) - ), - ]), - ]) - ) -} - -RevealSeedConfirmation.prototype.componentDidMount = function () { - document.getElementById('password-box').focus() -} - -RevealSeedConfirmation.prototype.goHome = function () { - this.props.dispatch(actions.showConfigPage(false)) - this.props.dispatch(actions.confirmSeedWords()) - .then(() => this.props.history.push(DEFAULT_ROUTE)) -} - -// create vault - -RevealSeedConfirmation.prototype.checkConfirmation = function (event) { - if (event.key === 'Enter') { - event.preventDefault() - this.revealSeedWords() - } -} - -RevealSeedConfirmation.prototype.revealSeedWords = function () { - var password = document.getElementById('password-box').value - this.props.dispatch(actions.requestRevealSeed(password)) - .then(() => this.props.history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)) -} diff --git a/ui/app/main-container.js b/ui/app/main-container.js index 68d1d45e7..b49a52363 100644 --- a/ui/app/main-container.js +++ b/ui/app/main-container.js @@ -3,7 +3,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const AccountAndTransactionDetails = require('./account-and-transaction-details') const Settings = require('./components/pages/settings') -const UnlockScreen = require('./components/pages/unlock') +const UnlockScreen = require('./components/pages/unlock-page') const log = require('loglevel') module.exports = MainContainer diff --git a/ui/app/token-util.js b/ui/app/token-util.js index f84051ef5..920442bfc 100644 --- a/ui/app/token-util.js +++ b/ui/app/token-util.js @@ -1,14 +1,6 @@ -const abi = require('human-standard-token-abi') -const Eth = require('ethjs-query') -const EthContract = require('ethjs-contract') - -const tokenInfoGetter = function () { - if (typeof global.ethereumProvider === 'undefined') return - - const eth = new Eth(global.ethereumProvider) - const contract = new EthContract(eth) - const TokenContract = contract(abi) +const util = require('./util') +function tokenInfoGetter () { const tokens = {} return async (address) => { @@ -16,18 +8,35 @@ const tokenInfoGetter = function () { return tokens[address] } - const contract = TokenContract.at(address) + tokens[address] = await getSymbolAndDecimals(address) - const result = await Promise.all([ - contract.symbol(), - contract.decimals(), - ]) + return tokens[address] + } +} - const [ symbol = [], decimals = [] ] = result +async function getSymbolAndDecimals (tokenAddress, existingTokens = []) { + const existingToken = existingTokens.find(({ address }) => tokenAddress === address) + if (existingToken) { + return existingToken + } + + let result = [] + try { + const token = util.getContractAtAddress(tokenAddress) + + result = await Promise.all([ + token.symbol(), + token.decimals(), + ]) + } catch (err) { + console.log(`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`, err) + } - tokens[address] = { symbol: symbol[0], decimals: decimals[0] } + const [ symbol = [], decimals = [] ] = result - return tokens[address] + return { + symbol: symbol[0] || null, + decimals: decimals[0] && decimals[0].toString() || null, } } @@ -42,4 +51,5 @@ function calcTokenAmount (value, decimals) { module.exports = { tokenInfoGetter, calcTokenAmount, + getSymbolAndDecimals, } diff --git a/ui/app/unlock.js b/ui/app/unlock.js deleted file mode 100644 index 099e5f9c6..000000000 --- a/ui/app/unlock.js +++ /dev/null @@ -1,141 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('./actions') -const getCaretCoordinates = require('textarea-caret') -const EventEmitter = require('events').EventEmitter -const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums') -const { getEnvironmentType } = require('../../app/scripts/lib/util') -const { ENVIRONMENT_TYPE_POPUP } = require('../../app/scripts/lib/enums') - -const Mascot = require('./components/mascot') - -UnlockScreen.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect(mapStateToProps)(UnlockScreen) - - -inherits(UnlockScreen, Component) -function UnlockScreen () { - Component.call(this) - this.animationEventEmitter = new EventEmitter() -} - -function mapStateToProps (state) { - return { - warning: state.appState.warning, - } -} - -UnlockScreen.prototype.render = function () { - const state = this.props - const warning = state.warning - return ( - h('.unlock-screen', [ - - h(Mascot, { - animationEventEmitter: this.animationEventEmitter, - }), - - h('h1', { - style: { - fontSize: '1.4em', - textTransform: 'uppercase', - color: '#7F8082', - }, - }, this.context.t('appName')), - - h('input.large-input', { - type: 'password', - id: 'password-box', - placeholder: 'enter password', - style: { - background: 'white', - }, - onKeyPress: this.onKeyPress.bind(this), - onInput: this.inputChanged.bind(this), - }), - - h('.error', { - style: { - display: warning ? 'block' : 'none', - padding: '0 20px', - textAlign: 'center', - }, - }, warning), - - h('button.primary.cursor-pointer', { - onClick: this.onSubmit.bind(this), - style: { - margin: 10, - }, - }, this.context.t('login')), - - h('p.pointer', { - onClick: () => { - this.props.dispatch(actions.markPasswordForgotten()) - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { - global.platform.openExtensionInBrowser() - } - }, - style: { - fontSize: '0.8em', - color: 'rgb(247, 134, 28)', - textDecoration: 'underline', - }, - }, this.context.t('restoreFromSeed')), - - h('p.pointer', { - onClick: () => { - this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')) - .then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE))) - }, - style: { - fontSize: '0.8em', - color: '#aeaeae', - textDecoration: 'underline', - marginTop: '32px', - }, - }, this.context.t('classicInterface')), - ]) - ) -} - -UnlockScreen.prototype.componentDidMount = function () { - document.getElementById('password-box').focus() -} - -UnlockScreen.prototype.onSubmit = function (event) { - const input = document.getElementById('password-box') - const password = input.value - this.props.dispatch(actions.tryUnlockMetamask(password)) -} - -UnlockScreen.prototype.onKeyPress = function (event) { - if (event.key === 'Enter') { - this.submitPassword(event) - } -} - -UnlockScreen.prototype.submitPassword = function (event) { - var element = event.target - var password = element.value - // reset input - element.value = '' - this.props.dispatch(actions.tryUnlockMetamask(password)) -} - -UnlockScreen.prototype.inputChanged = function (event) { - // tell mascot to look at page action - var element = event.target - var boundingRect = element.getBoundingClientRect() - var coordinates = getCaretCoordinates(element, element.selectionEnd) - this.animationEventEmitter.emit('point', { - x: boundingRect.left + coordinates.left - element.scrollLeft, - y: boundingRect.top + coordinates.top - element.scrollTop, - }) -} diff --git a/ui/i18n-helper.js b/ui/i18n-helper.js index 3eee55ae9..79aa93116 100644 --- a/ui/i18n-helper.js +++ b/ui/i18n-helper.js @@ -11,8 +11,9 @@ const getMessage = (locale, key, substitutions) => { const { current, en } = locale const entry = current[key] || en[key] if (!entry) { - log.error(`Translator - Unable to find value for "${key}"`) // throw new Error(`Translator - Unable to find value for "${key}"`) + log.error(`Translator - Unable to find value for "${key}"`) + return `[${key}]` } let phrase = entry.message // perform substitutions -- cgit From 77ee23d4935c72816e4d141472f53d8ade170cdf Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 14 May 2018 20:33:04 -0230 Subject: Add index.js files to send_ subdirectories --- ui/app/components/send_/account-list-item/index.js | 1 + ui/app/components/send_/send-content/index.js | 1 + .../send_/send-content/send-amount-row/amount-max-button/index.js | 1 + ui/app/components/send_/send-content/send-amount-row/index.js | 1 + ui/app/components/send_/send-content/send-dropdown-list/index.js | 1 + .../components/send_/send-content/send-from-row/from-dropdown/index.js | 1 + ui/app/components/send_/send-content/send-from-row/index.js | 1 + ui/app/components/send_/send-content/send-gas-row/index.js | 1 + ui/app/components/send_/send-content/send-row-wrapper/index.js | 1 + .../send_/send-content/send-row-wrapper/send-row-error-message/index.js | 1 + ui/app/components/send_/send-content/send-to-row/index.js | 1 + ui/app/components/send_/send-footer/index.js | 1 + ui/app/components/send_/send-header/index.js | 1 + 13 files changed, 13 insertions(+) create mode 100644 ui/app/components/send_/account-list-item/index.js create mode 100644 ui/app/components/send_/send-content/index.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js create mode 100644 ui/app/components/send_/send-content/send-amount-row/index.js create mode 100644 ui/app/components/send_/send-content/send-dropdown-list/index.js create mode 100644 ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js create mode 100644 ui/app/components/send_/send-content/send-from-row/index.js create mode 100644 ui/app/components/send_/send-content/send-gas-row/index.js create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/index.js create mode 100644 ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js create mode 100644 ui/app/components/send_/send-content/send-to-row/index.js create mode 100644 ui/app/components/send_/send-footer/index.js create mode 100644 ui/app/components/send_/send-header/index.js (limited to 'ui') diff --git a/ui/app/components/send_/account-list-item/index.js b/ui/app/components/send_/account-list-item/index.js new file mode 100644 index 000000000..1fca540be --- /dev/null +++ b/ui/app/components/send_/account-list-item/index.js @@ -0,0 +1 @@ +export { default } from './account-list-item.container' \ No newline at end of file diff --git a/ui/app/components/send_/send-content/index.js b/ui/app/components/send_/send-content/index.js new file mode 100644 index 000000000..10b3c850e --- /dev/null +++ b/ui/app/components/send_/send-content/index.js @@ -0,0 +1 @@ +export { default } from './send-content.component' \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js new file mode 100644 index 000000000..548b51f33 --- /dev/null +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js @@ -0,0 +1 @@ +export { default } from './amount-max-button.container' \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-amount-row/index.js b/ui/app/components/send_/send-content/send-amount-row/index.js new file mode 100644 index 000000000..94a7da56f --- /dev/null +++ b/ui/app/components/send_/send-content/send-amount-row/index.js @@ -0,0 +1 @@ +export { default } from './send-amount-row.container' \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-dropdown-list/index.js b/ui/app/components/send_/send-content/send-dropdown-list/index.js new file mode 100644 index 000000000..ee7736376 --- /dev/null +++ b/ui/app/components/send_/send-content/send-dropdown-list/index.js @@ -0,0 +1 @@ +export { default } from './send-dropdown-list.component' \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js new file mode 100644 index 000000000..6ab9a157a --- /dev/null +++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js @@ -0,0 +1 @@ +export { default } from './from-dropdown.component' \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-from-row/index.js b/ui/app/components/send_/send-content/send-from-row/index.js new file mode 100644 index 000000000..4a0916dba --- /dev/null +++ b/ui/app/components/send_/send-content/send-from-row/index.js @@ -0,0 +1 @@ +export { default } from './send-from-row.container' \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-gas-row/index.js b/ui/app/components/send_/send-content/send-gas-row/index.js new file mode 100644 index 000000000..060ed7fd3 --- /dev/null +++ b/ui/app/components/send_/send-content/send-gas-row/index.js @@ -0,0 +1 @@ +export { default } from './send-gas-row.container' \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-row-wrapper/index.js b/ui/app/components/send_/send-content/send-row-wrapper/index.js new file mode 100644 index 000000000..5715f55c6 --- /dev/null +++ b/ui/app/components/send_/send-content/send-row-wrapper/index.js @@ -0,0 +1 @@ +export { default } from './send-row-wrapper.component' \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js new file mode 100644 index 000000000..bf49c55bd --- /dev/null +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js @@ -0,0 +1 @@ +export { default } from './send-row-error-message.container' \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-to-row/index.js b/ui/app/components/send_/send-content/send-to-row/index.js new file mode 100644 index 000000000..4e7aa9747 --- /dev/null +++ b/ui/app/components/send_/send-content/send-to-row/index.js @@ -0,0 +1 @@ +export { default } from './send-to-row.container' \ No newline at end of file diff --git a/ui/app/components/send_/send-footer/index.js b/ui/app/components/send_/send-footer/index.js new file mode 100644 index 000000000..cd1727330 --- /dev/null +++ b/ui/app/components/send_/send-footer/index.js @@ -0,0 +1 @@ +export { default } from './send-footer.container' \ No newline at end of file diff --git a/ui/app/components/send_/send-header/index.js b/ui/app/components/send_/send-header/index.js new file mode 100644 index 000000000..b808eabbf --- /dev/null +++ b/ui/app/components/send_/send-header/index.js @@ -0,0 +1 @@ +export { default } from './send-header.container' \ No newline at end of file -- cgit From 8ff60267d851be1b64f7024e6263e1e5ba1ef01c Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 15 May 2018 09:13:39 -0230 Subject: Fix Button component path in page-container footer. --- .../page-container-footer/page-container-footer.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/page-container/page-container-footer/page-container-footer.component.js b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js index 84d8296b0..f591ff83f 100644 --- a/ui/app/components/page-container/page-container-footer/page-container-footer.component.js +++ b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import Button from './components/button' +import Button from '../../button' export default class PageContainerFooter extends Component { -- cgit From 44679f6cdac5e99647964a5fb91a11ee69fe239a Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 15 May 2018 09:59:23 -0230 Subject: Import defaults in send_/ --- ui/app/components/send_/index.js | 1 + .../send-content/send-amount-row/send-amount-row.component.js | 4 ++-- ui/app/components/send_/send-content/send-content.component.js | 8 ++++---- .../send-dropdown-list/send-dropdown-list.component.js | 2 +- .../send-from-row/from-dropdown/from-dropdown.component.js | 4 ++-- .../send_/send-content/send-from-row/send-from-row.component.js | 4 ++-- .../send_/send-content/send-gas-row/send-gas-row.component.js | 2 +- .../send-content/send-row-wrapper/send-row-wrapper.component.js | 2 +- .../send_/send-content/send-to-row/send-to-row.component.js | 2 +- ui/app/components/send_/send.component.js | 6 +++--- 10 files changed, 18 insertions(+), 17 deletions(-) create mode 100644 ui/app/components/send_/index.js (limited to 'ui') diff --git a/ui/app/components/send_/index.js b/ui/app/components/send_/index.js new file mode 100644 index 000000000..9a4dd5727 --- /dev/null +++ b/ui/app/components/send_/index.js @@ -0,0 +1 @@ +export { default } from './send.container' \ No newline at end of file diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index 8e201ae41..be301ca7d 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' -import AmountMaxButton from './amount-max-button/amount-max-button.container' +import SendRowWrapper from '../send-row-wrapper/' +import AmountMaxButton from './amount-max-button/' import CurrencyDisplay from '../../../send/currency-display' export default class SendAmountRow extends Component { diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js index e213cfcf3..d610c2a3f 100644 --- a/ui/app/components/send_/send-content/send-content.component.js +++ b/ui/app/components/send_/send-content/send-content.component.js @@ -1,9 +1,9 @@ import React, { Component } from 'react' import PageContainerContent from '../../page-container/page-container-content.component' -import SendAmountRow from './send-amount-row/send-amount-row.container' -import SendFromRow from './send-from-row/send-from-row.container' -import SendGasRow from './send-gas-row/send-gas-row.container' -import SendToRow from './send-to-row/send-to-row.container' +import SendAmountRow from './send-amount-row/' +import SendFromRow from './send-from-row/' +import SendGasRow from './send-gas-row/' +import SendToRow from './send-to-row/' export default class SendContent extends Component { diff --git a/ui/app/components/send_/send-content/send-dropdown-list/send-dropdown-list.component.js b/ui/app/components/send_/send-content/send-dropdown-list/send-dropdown-list.component.js index 7bcc06c3e..5c7174ecf 100644 --- a/ui/app/components/send_/send-content/send-dropdown-list/send-dropdown-list.component.js +++ b/ui/app/components/send_/send-content/send-dropdown-list/send-dropdown-list.component.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import AccountListItem from '../../account-list-item/account-list-item.container' +import AccountListItem from '../../account-list-item/' export default class SendDropdownList extends Component { diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js index 7815887a5..bf95c50fb 100644 --- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js +++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import AccountListItem from '../../../account-list-item/account-list-item.container' -import SendDropdownList from '../../send-dropdown-list/send-dropdown-list.component' +import AccountListItem from '../../../account-list-item/' +import SendDropdownList from '../../send-dropdown-list/' export default class FromDropdown extends Component { diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js index 17e7f8e46..a580aef96 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' -import FromDropdown from './from-dropdown/from-dropdown.component' +import SendRowWrapper from '../send-row-wrapper/' +import FromDropdown from './from-dropdown/' export default class SendFromRow extends Component { diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js index 581f012b6..c80d8c0bb 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' +import SendRowWrapper from '../send-row-wrapper/' import GasFeeDisplay from '../../../send/gas-fee-display-v2' export default class SendGasRow extends Component { diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js index 707b3ae80..f484bd8d9 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import SendRowErrorMessage from './send-row-error-message/send-row-error-message.container' +import SendRowErrorMessage from './send-row-error-message/' export default class SendRowWrapper extends Component { diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js index a6e4c1624..a54608259 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' +import SendRowWrapper from '../send-row-wrapper/' import EnsInput from '../../../ens-input' export default class SendToRow extends Component { diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index e14a97537..4cfac1bde 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -6,9 +6,9 @@ import { doesAmountErrorRequireUpdate, } from './send.utils' -import SendHeader from './send-header/send-header.container' -import SendContent from './send-content/send-content.component' -import SendFooter from './send-footer/send-footer.container' +import SendHeader from './send-header/' +import SendContent from './send-content/' +import SendFooter from './send-footer/' export default class SendTransactionScreen extends PersistentForm { -- cgit From 759bc173887dbb301fd17739ce431e8dfd096adc Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 15 May 2018 11:18:33 -0230 Subject: Merge branch 'develop' into i3725-refactor-send-component- --- .../components/text-field/text-field.component.js | 5 ++ ui/app/css/itcss/components/welcome-screen.scss | 97 +++++++++++----------- 2 files changed, 54 insertions(+), 48 deletions(-) (limited to 'ui') diff --git a/ui/app/components/text-field/text-field.component.js b/ui/app/components/text-field/text-field.component.js index 4a02f76d8..6fd3b82b4 100644 --- a/ui/app/components/text-field/text-field.component.js +++ b/ui/app/components/text-field/text-field.component.js @@ -8,6 +8,9 @@ const styles = { '&$cssFocused': { color: '#aeaeae', }, + '&$cssError': { + color: '#aeaeae', + }, fontWeight: '400', color: '#aeaeae', }, @@ -17,6 +20,7 @@ const styles = { backgroundColor: '#f7861c', }, }, + cssError: {}, } const TextField = props => { @@ -30,6 +34,7 @@ const TextField = props => { FormLabelClasses: { root: classes.cssLabel, focused: classes.cssFocused, + error: classes.cssError, }, }} InputProps={{ diff --git a/ui/app/css/itcss/components/welcome-screen.scss b/ui/app/css/itcss/components/welcome-screen.scss index bfd174ad9..af1d67398 100644 --- a/ui/app/css/itcss/components/welcome-screen.scss +++ b/ui/app/css/itcss/components/welcome-screen.scss @@ -1,59 +1,60 @@ .welcome-screen { + display: flex; + flex-flow: column; + justify-content: center; + align-items: center; + font-family: Roboto; + font-weight: 400; + width: 100%; + flex: 1 0 auto; + padding: 70px 0; + background: $white; + + @media screen and (max-width: 575px) { + padding: 0; + } + + &__info { display: flex; flex-flow: column; - justify-content: center; - align-items: center; - font-family: Roboto; - font-weight: 400; width: 100%; - flex: 1 0 auto; - padding: 70px 0; - background: $white; - - @media screen and (max-width: 575px) { - padding: 0; - } - - &__info { - display: flex; - flex-flow: column; - width: 100%; - height: 100%; - align-items: center; - - &__header { - font-size: 1.65em; - margin-bottom: 14px; - - @media screen and (max-width: 575px) { - font-size: 1.5em; - } - } + height: 100%; + align-items: center; + justify-content: center; - &__copy { - font-size: 1em; - width: 400px; - max-width: 90vw; - text-align: center; + &__header { + font-size: 1.65em; + margin-bottom: 14px; - @media screen and (max-width: 575px) { - font-size: 0.9em; - } - } + @media screen and (max-width: 575px) { + font-size: 1.5em; + } } - &__button { - height: 54px; - width: 198px; - box-shadow: 0 2px 4px 0 rgba(0,0,0,0.14); - color: #FFFFFF; - font-size: 20px; - font-weight: 500; - line-height: 26px; + &__copy { + font-size: 1em; + width: 400px; + max-width: 90vw; text-align: center; - text-transform: uppercase; - margin: 35px 0 14px; - transition: 200ms ease-in-out; - background-color: rgba(247, 134, 28, 0.9); + + @media screen and (max-width: 575px) { + font-size: .9em; + } } + } + + &__button { + height: 54px; + width: 198px; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14); + color: #fff; + font-size: 20px; + font-weight: 500; + line-height: 26px; + text-align: center; + text-transform: uppercase; + margin: 35px 0 14px; + transition: 200ms ease-in-out; + background-color: rgba(247, 134, 28, .9); + } } -- cgit From c4e48d9263cb251695afc28d936f7abb0e033f5c Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 23 May 2018 16:56:13 -0230 Subject: Send refactor: fix amount max button for tokens. --- ui/app/actions.js | 2 +- ui/app/components/send_/send.component.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 071f56209..fc2a838d4 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -790,7 +790,7 @@ function updateSendTokenBalance ({ .then(usersToken => { if (usersToken) { const newTokenBalance = calcTokenBalance({ selectedToken, usersToken }) - dispatch(setSendTokenBalance(newTokenBalance)) + dispatch(setSendTokenBalance(newTokenBalance.toString(10))) } }) .catch(err => { diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index 4cfac1bde..49731ff6a 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -82,7 +82,7 @@ export default class SendTransactionScreen extends PersistentForm { } = prevProps const uninitialized = [prevBalance, prevGasTotal].every(n => n === null) - + console.log(`@#@# uninitialized`, uninitialized); if (!uninitialized) { const amountErrorRequiresUpdate = doesAmountErrorRequireUpdate({ balance, @@ -120,6 +120,17 @@ export default class SendTransactionScreen extends PersistentForm { } componentWillMount () { + const { + from: { address, balance }, + selectedToken, + tokenContract, + updateSendTokenBalance, + } = this.props + updateSendTokenBalance({ + selectedToken, + tokenContract, + address, + }) this.updateGas() } -- cgit From 1405237479621d7258468e6c7694415b0afbb045 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 24 May 2018 13:06:48 -0230 Subject: Fix display of primary currency symbol in send amount row --- .../send_/send-content/send-amount-row/send-amount-row.component.js | 4 ++-- .../send-amount-row/tests/send-amount-row-component.test.js | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index be301ca7d..b094d0cd5 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -64,7 +64,7 @@ export default class SendAmountRow extends Component { convertedCurrency, gasTotal, inError, - primaryCurrency = 'ETH', + primaryCurrency, selectedToken, } = this.props @@ -80,7 +80,7 @@ export default class SendAmountRow extends Component { convertedCurrency={convertedCurrency} handleChange={newAmount => this.handleAmountChange(newAmount)} inError={inError} - primaryCurrency={primaryCurrency} + primaryCurrency={primaryCurrency || 'ETH'} selectedToken={selectedToken} value={amount || '0x0'} /> diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js index 8355ebf10..31d2e2515 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js @@ -156,5 +156,11 @@ describe('SendAmountRow Component', function () { ['mockNewAmount'] ) }) + + it('should pass the default primaryCurrency to the CurrencyDisplay if primaryCurrency is falsy', () => { + wrapper.setProps({ primaryCurrency: null }) + const { primaryCurrency } = wrapper.find(SendRowWrapper).childAt(1).props() + assert.equal(primaryCurrency, 'ETH') + }) }) }) -- cgit From 5bb399e55a819d52f2742e3491d50547be435a97 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 24 May 2018 17:27:33 -0230 Subject: Display correct titles and subtitles on send token and editing send transaction screens. --- ui/app/components/pending-tx/confirm-send-token.js | 2 +- .../send_/send-header/send-header.component.js | 7 ++-- .../send_/send-header/send-header.container.js | 5 ++- .../send_/send-header/send-header.selectors.js | 37 +++++++++++++++++ .../tests/send-header-component.test.js | 9 +++-- .../tests/send-header-container.test.js | 8 +++- .../tests/send-header-selectors.test.js | 47 ++++++++++++++++++++++ ui/app/components/send_/send.component.js | 4 +- 8 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 ui/app/components/send_/send-header/send-header.selectors.js create mode 100644 ui/app/components/send_/send-header/tests/send-header-selectors.test.js (limited to 'ui') diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 5b5149058..b21e1473e 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -107,7 +107,7 @@ function mapDispatchToProps (dispatch, ownProps) { to, amount: tokenAmountInHex, errors: { to: null, amount: null }, - editingTransactionId: id, + editingTransactionId: id && id.toString(), token: ownProps.token, })) dispatch(actions.showSendTokenPage()) diff --git a/ui/app/components/send_/send-header/send-header.component.js b/ui/app/components/send_/send-header/send-header.component.js index 0d75dbdae..5f6617fce 100644 --- a/ui/app/components/send_/send-header/send-header.component.js +++ b/ui/app/components/send_/send-header/send-header.component.js @@ -8,7 +8,8 @@ export default class SendHeader extends Component { static propTypes = { clearSend: PropTypes.func, history: PropTypes.object, - isToken: PropTypes.bool, + titleKey: PropTypes.string, + subtitleParams: PropTypes.array, }; onClose () { @@ -20,8 +21,8 @@ export default class SendHeader extends Component { return ( this.onClose()} - subtitle={this.context.t('onlySendToEtherAddress')} - title={this.props.isToken ? this.context.t('sendTokens') : this.context.t('sendETH')} + subtitle={this.context.t(...this.props.subtitleParams)} + title={this.context.t(this.props.titleKey)} /> ) } diff --git a/ui/app/components/send_/send-header/send-header.container.js b/ui/app/components/send_/send-header/send-header.container.js index 0c92da3a6..4bcd0d1b6 100644 --- a/ui/app/components/send_/send-header/send-header.container.js +++ b/ui/app/components/send_/send-header/send-header.container.js @@ -1,13 +1,14 @@ import { connect } from 'react-redux' import { clearSend } from '../../../actions' import SendHeader from './send-header.component' -import { getSelectedToken } from '../../../selectors' +import { getSubtitleParams, getTitleKey } from './send-header.selectors' export default connect(mapStateToProps, mapDispatchToProps)(SendHeader) function mapStateToProps (state) { return { - isToken: Boolean(getSelectedToken(state)), + titleKey: getTitleKey(state), + subtitleParams: getSubtitleParams(state), } } diff --git a/ui/app/components/send_/send-header/send-header.selectors.js b/ui/app/components/send_/send-header/send-header.selectors.js new file mode 100644 index 000000000..d7c9d3766 --- /dev/null +++ b/ui/app/components/send_/send-header/send-header.selectors.js @@ -0,0 +1,37 @@ +const { + getSelectedToken, + getSendEditingTransactionId, +} = require('../send.selectors.js') + +const selectors = { + getTitleKey, + getSubtitleParams, +} + +module.exports = selectors + +function getTitleKey (state) { + const isEditing = Boolean(getSendEditingTransactionId(state)) + const isToken = Boolean(getSelectedToken(state)) + + if (isEditing) { + return 'edit' + } else if (isToken) { + return 'sendTokens' + } else { + return 'sendETH' + } +} + +function getSubtitleParams (state) { + const isEditing = Boolean(getSendEditingTransactionId(state)) + const token = getSelectedToken(state) + + if (isEditing) { + return [ 'editingTransaction' ] + } else if (token) { + return [ 'onlySendTokensToAccountAddress', [ token.symbol ] ] + } else { + return [ 'onlySendToEtherAddress' ] + } +} diff --git a/ui/app/components/send_/send-header/tests/send-header-component.test.js b/ui/app/components/send_/send-header/tests/send-header-component.test.js index c9d6d8023..930bfa387 100644 --- a/ui/app/components/send_/send-header/tests/send-header-component.test.js +++ b/ui/app/components/send_/send-header/tests/send-header-component.test.js @@ -23,8 +23,9 @@ describe('SendHeader Component', function () { wrapper = shallow(, { context: { t: str => str } }) + titleKey={'mockTitleKey'} + subtitleParams={[ 'mockSubtitleKey', 'mockVal']} + />, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) }) afterEach(() => { @@ -59,8 +60,8 @@ describe('SendHeader Component', function () { subtitle, title, } = wrapper.find(PageContainerHeader).props() - assert.equal(subtitle, 'onlySendToEtherAddress') - assert.equal(title, 'sendETH') + assert.equal(subtitle, 'mockSubtitleKeymockVal') + assert.equal(title, 'mockTitleKey') assert.equal(SendHeader.prototype.onClose.callCount, 0) onClose() assert.equal(SendHeader.prototype.onClose.callCount, 1) diff --git a/ui/app/components/send_/send-header/tests/send-header-container.test.js b/ui/app/components/send_/send-header/tests/send-header-container.test.js index abce9af6b..41a7e8a89 100644 --- a/ui/app/components/send_/send-header/tests/send-header-container.test.js +++ b/ui/app/components/send_/send-header/tests/send-header-container.test.js @@ -18,7 +18,10 @@ proxyquire('../send-header.container.js', { }, }, '../../../actions': actionSpies, - '../../../selectors': { getSelectedToken: (s) => `mockSelectedToken:${s}` }, + './send-header.selectors': { + getTitleKey: (s) => `mockTitleKey:${s}`, + getSubtitleParams: (s) => `mockSubtitleParams:${s}`, + }, }) describe('send-header container', () => { @@ -27,7 +30,8 @@ describe('send-header container', () => { it('should map the correct properties to props', () => { assert.deepEqual(mapStateToProps('mockState'), { - isToken: true, + titleKey: 'mockTitleKey:mockState', + subtitleParams: 'mockSubtitleParams:mockState', }) }) diff --git a/ui/app/components/send_/send-header/tests/send-header-selectors.test.js b/ui/app/components/send_/send-header/tests/send-header-selectors.test.js new file mode 100644 index 000000000..e0c6a3ab3 --- /dev/null +++ b/ui/app/components/send_/send-header/tests/send-header-selectors.test.js @@ -0,0 +1,47 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' + +const { + getTitleKey, + getSubtitleParams, +} = proxyquire('../send-header.selectors', { + '../send.selectors': { + getSelectedToken: (mockState) => mockState.t, + getSendEditingTransactionId: (mockState) => mockState.e, + }, +}) + +describe('send-header selectors', () => { + + describe('getTitleKey()', () => { + it('should return the correct key when getSendEditingTransactionId is truthy', () => { + assert.equal(getTitleKey({ e: 1, t: true }), 'edit') + }) + + it('should return the correct key when getSendEditingTransactionId is falsy and getSelectedToken is truthy', () => { + assert.equal(getTitleKey({ e: null, t: 'abc' }), 'sendTokens') + }) + + it('should return the correct key when getSendEditingTransactionId is falsy and getSelectedToken is falsy', () => { + assert.equal(getTitleKey({ e: null }), 'sendETH') + }) + }) + + describe('getSubtitleParams()', () => { + it('should return the correct params when getSendEditingTransactionId is truthy', () => { + assert.deepEqual(getSubtitleParams({ e: 1, t: true }), [ 'editingTransaction' ]) + }) + + it('should return the correct params when getSendEditingTransactionId is falsy and getSelectedToken is truthy', () => { + assert.deepEqual( + getSubtitleParams({ e: null, t: { symbol: 'ABC' } }), + [ 'onlySendTokensToAccountAddress', [ 'ABC' ] ] + ) + }) + + it('should return the correct params when getSendEditingTransactionId is falsy and getSelectedToken is falsy', () => { + assert.deepEqual(getSubtitleParams({ e: null }), [ 'onlySendToEtherAddress' ]) + }) + }) + +}) diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index 49731ff6a..8b0a41f9e 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -82,7 +82,7 @@ export default class SendTransactionScreen extends PersistentForm { } = prevProps const uninitialized = [prevBalance, prevGasTotal].every(n => n === null) - console.log(`@#@# uninitialized`, uninitialized); + if (!uninitialized) { const amountErrorRequiresUpdate = doesAmountErrorRequireUpdate({ balance, @@ -121,7 +121,7 @@ export default class SendTransactionScreen extends PersistentForm { componentWillMount () { const { - from: { address, balance }, + from: { address }, selectedToken, tokenContract, updateSendTokenBalance, -- cgit From 3a87d9221d7ae1d0d7cb526e5fb22ea528498105 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 24 May 2018 18:25:23 -0230 Subject: Fix order of button text in page-container-footer. --- .../page-container-footer/page-container-footer.component.js | 4 ++-- ui/app/components/send_/send-footer/send-footer.container.js | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) (limited to 'ui') diff --git a/ui/app/components/page-container/page-container-footer/page-container-footer.component.js b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js index f591ff83f..9172aea94 100644 --- a/ui/app/components/page-container/page-container-footer/page-container-footer.component.js +++ b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js @@ -34,7 +34,7 @@ export default class PageContainerFooter extends Component { className="page-container__footer-button" onClick={() => onCancel()} > - { this.context.t('cancel') || cancelText } + { cancelText || this.context.t('cancel') }
diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js index 0242dfa54..64e4b4bd3 100644 --- a/ui/app/components/send_/send-footer/send-footer.container.js +++ b/ui/app/components/send_/send-footer/send-footer.container.js @@ -26,7 +26,6 @@ import { addressIsNew, constructTxParams, constructUpdatedTx, - formShouldBeDisabled, } from './send-footer.utils' export default connect(mapStateToProps, mapDispatchToProps)(SendFooter) @@ -34,21 +33,16 @@ export default connect(mapStateToProps, mapDispatchToProps)(SendFooter) function mapStateToProps (state) { return { amount: getSendAmount(state), - disabled: formShouldBeDisabled({ - inError: isSendFormInError(state), - selectedToken: getSelectedToken(state), - tokenBalance: getTokenBalance(state), - gasTotal: getGasTotal(state), - }), editingTransactionId: getSendEditingTransactionId(state), from: getSendFromObject(state), gasLimit: getGasLimit(state), gasPrice: getGasPrice(state), + gasTotal: getGasTotal(state), inError: isSendFormInError(state), - isToken: Boolean(getSelectedToken(state)), selectedToken: getSelectedToken(state), to: getSendTo(state), toAccounts: getSendToAccounts(state), + tokenBalance: getTokenBalance(state), unapprovedTxs: getUnapprovedTxs(state), } } -- cgit From dc2b5d0ef47be2125e58018470539d65d0d64c75 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 24 May 2018 22:23:54 -0230 Subject: Move formShouldBeDisabled from send-footer util to component. --- .../send_/send-footer/send-footer.component.js | 9 ++- .../send_/send-footer/send-footer.utils.js | 6 -- .../tests/send-footer-component.test.js | 65 +++++++++++++++++++++- .../send-footer/tests/send-footer-utils.test.js | 32 ----------- 4 files changed, 72 insertions(+), 40 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-footer/send-footer.component.js b/ui/app/components/send_/send-footer/send-footer.component.js index de2a885f0..7ff44c9d5 100644 --- a/ui/app/components/send_/send-footer/send-footer.component.js +++ b/ui/app/components/send_/send-footer/send-footer.component.js @@ -17,6 +17,7 @@ export default class SendFooter extends Component { gasPrice: PropTypes.string, gasTotal: PropTypes.string, history: PropTypes.object, + inError: PropTypes.bool, selectedToken: PropTypes.object, sign: PropTypes.func, to: PropTypes.string, @@ -75,12 +76,18 @@ export default class SendFooter extends Component { this.props.history.push(CONFIRM_TRANSACTION_ROUTE) } + formShouldBeDisabled () { + const { inError, selectedToken, tokenBalance, gasTotal } = this.props + const missingTokenBalance = selectedToken && !tokenBalance + return inError || !gasTotal || missingTokenBalance + } + render () { return ( this.onCancel()} onSubmit={e => this.onSubmit(e)} - disabled={this.props.disabled} + disabled={this.formShouldBeDisabled()} /> ) } diff --git a/ui/app/components/send_/send-footer/send-footer.utils.js b/ui/app/components/send_/send-footer/send-footer.utils.js index 149d9e357..d5639629d 100644 --- a/ui/app/components/send_/send-footer/send-footer.utils.js +++ b/ui/app/components/send_/send-footer/send-footer.utils.js @@ -2,11 +2,6 @@ const ethAbi = require('ethereumjs-abi') const ethUtil = require('ethereumjs-util') const { TOKEN_TRANSFER_FUNCTION_SIGNATURE } = require('../send.constants') -function formShouldBeDisabled ({ inError, selectedToken, tokenBalance, gasTotal }) { - const missingTokenBalance = selectedToken && !tokenBalance - return inError || !gasTotal || missingTokenBalance -} - function addHexPrefixToObjectValues (obj) { return Object.keys(obj).reduce((newObj, key) => { return { ...newObj, [key]: ethUtil.addHexPrefix(obj[key]) } @@ -81,7 +76,6 @@ function addressIsNew (toAccounts, newAddress) { module.exports = { addressIsNew, - formShouldBeDisabled, constructTxParams, constructUpdatedTx, addHexPrefixToObjectValues, diff --git a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js index c0b8f956f..a74b6195c 100644 --- a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js +++ b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js @@ -37,6 +37,7 @@ describe('SendFooter Component', function () { gasPrice={'mockGasPrice'} gasTotal={'mockGasTotal'} history={historySpies} + inError={false} selectedToken={{ mockProp: 'mockSelectedTokenProp' }} sign={propsMethodSpies.sign} to={'mockTo'} @@ -73,6 +74,39 @@ describe('SendFooter Component', function () { }) }) + + describe('formShouldBeDisabled()', () => { + const config = { + 'should return true if inError is truthy': { + inError: true, + expectedResult: true, + }, + 'should return true if gasTotal is falsy': { + inError: false, + gasTotal: false, + expectedResult: true, + }, + 'should return true if selectedToken is truthy and tokenBalance is falsy': { + selectedToken: true, + tokenBalance: null, + expectedResult: true, + }, + 'should return false if inError is false and all other params are truthy': { + inError: false, + gasTotal: '0x123', + selectedToken: true, + tokenBalance: 123, + expectedResult: false, + }, + } + Object.entries(config).map(([description, obj]) => { + it(description, () => { + wrapper.setProps(obj) + assert.equal(wrapper.instance().formShouldBeDisabled(), obj.expectedResult) + }) + }) + }) + describe('onSubmit', () => { it('should call addToAddressBookIfNew with the correct params', () => { wrapper.instance().onSubmit(MOCK_EVENT) @@ -134,6 +168,35 @@ describe('SendFooter Component', function () { }) describe('render', () => { + beforeEach(() => { + sinon.stub(SendFooter.prototype, 'formShouldBeDisabled').returns('formShouldBeDisabledReturn') + wrapper = shallow(, { context: { t: str => str } }) + }) + + afterEach(() => { + SendFooter.prototype.formShouldBeDisabled.restore() + }) + it('should render a PageContainerFooter component', () => { assert.equal(wrapper.find(PageContainerFooter).length, 1) }) @@ -144,7 +207,7 @@ describe('SendFooter Component', function () { onSubmit, disabled, } = wrapper.find(PageContainerFooter).props() - assert.equal(disabled, true) + assert.equal(disabled, 'formShouldBeDisabledReturn') assert.equal(SendFooter.prototype.onSubmit.callCount, 0) onSubmit(MOCK_EVENT) diff --git a/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js b/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js index b235ea5e5..2d3135995 100644 --- a/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js +++ b/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js @@ -16,7 +16,6 @@ const sendUtils = proxyquire('../send-footer.utils.js', { }) const { addressIsNew, - formShouldBeDisabled, constructTxParams, constructUpdatedTx, addHexPrefixToObjectValues, @@ -65,37 +64,6 @@ describe('send-footer utils', () => { }) }) - describe('formShouldBeDisabled()', () => { - const config = { - 'should return true if inError is truthy': { - inError: true, - expectedResult: true, - }, - 'should return true if gasTotal is falsy': { - inError: false, - gasTotal: false, - expectedResult: true, - }, - 'should return true if selectedToken is truthy and tokenBalance is falsy': { - selectedToken: true, - tokenBalance: null, - expectedResult: true, - }, - 'should return false if inError is false and all other params are truthy': { - inError: false, - gasTotal: '0x123', - selectedToken: true, - tokenBalance: 123, - expectedResult: false, - }, - } - Object.entries(config).map(([description, obj]) => { - it(description, () => { - assert.equal(formShouldBeDisabled(obj), obj.expectedResult) - }) - }) - }) - describe('constructTxParams()', () => { it('should return a new txParams object with value and to properties if there is no selectedToken', () => { assert.deepEqual( -- cgit From 0de765aa25637cd85e22eebd11b6c4c8a32faf14 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 24 May 2018 22:30:07 -0230 Subject: Clean up for send refactor PR. --- .../send-from-row/from-dropdown/from-dropdown.component.js | 2 +- .../send-content/send-to-row/send-to-row.component.js | 2 +- .../send-to-row/tests/send-to-row-component.test.js | 2 +- .../send_/send-footer/tests/send-footer-container.test.js | 14 ++------------ ui/app/components/send_/tests/send-component.test.js | 2 ++ 5 files changed, 7 insertions(+), 15 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js index bf95c50fb..418766cd9 100644 --- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js +++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js @@ -35,7 +35,7 @@ export default class FromDropdown extends Component { closeDropdown={closeDropdown} onSelect={onSelect} activeAddress={selectedAccount.address} - />}, + />}
} diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js index a54608259..901ae97e9 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js @@ -37,7 +37,7 @@ export default class SendToRow extends Component { return ( { it('should map the correct properties to props', () => { assert.deepEqual(mapStateToProps('mockState'), { amount: 'mockAmount:mockState', - disabled: 'mockFormShouldBeDisabled', selectedToken: 'mockSelectedToken:mockState', editingTransactionId: 'mockEditingTransactionId:mockState', from: 'mockFromObject:mockState', gasLimit: 'mockGasLimit:mockState', gasPrice: 'mockGasPrice:mockState', + gasTotal: 'mockGasTotal:mockState', inError: 'mockInError:mockState', - isToken: true, to: 'mockTo:mockState', toAccounts: 'mockToAccounts:mockState', + tokenBalance: 'mockTokenBalance:mockState', unapprovedTxs: 'mockUnapprovedTxs:mockState', }) - assert.deepEqual( - utilsStubs.formShouldBeDisabled.getCall(0).args[0], - { - inError: 'mockInError:mockState', - selectedToken: 'mockSelectedToken:mockState', - tokenBalance: 'mockTokenBalance:mockState', - gasTotal: 'mockGasTotal:mockState', - } - ) }) }) diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js index 60b160333..8aeab36b4 100644 --- a/ui/app/components/send_/tests/send-component.test.js +++ b/ui/app/components/send_/tests/send-component.test.js @@ -142,6 +142,7 @@ describe('Send Component', function () { it('should not call updateSendTokenBalance or this.updateGas if network === prevNetwork', () => { SendTransactionScreen.prototype.updateGas.resetHistory() + propsMethodSpies.updateSendTokenBalance.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', @@ -168,6 +169,7 @@ describe('Send Component', function () { it('should call updateSendTokenBalance and this.updateGas with the correct params', () => { SendTransactionScreen.prototype.updateGas.resetHistory() + propsMethodSpies.updateSendTokenBalance.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', -- cgit From e712336189e1a0a453ea30dbb58abbc3c57db8f8 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 25 May 2018 14:39:31 -0230 Subject: Send refactor: fix error handling and form disabling in send form. --- .../send_/send-footer/send-footer.component.js | 4 +-- .../send_/send-footer/send-footer.container.js | 4 ++- .../send_/send-footer/send-footer.selectors.js | 3 +- .../tests/send-footer-component.test.js | 6 ++++ .../tests/send-footer-container.test.js | 3 +- .../tests/send-footer-selectors.test.js | 24 +++++++++++++++ ui/app/components/send_/send.selectors.js | 6 ---- .../components/send_/tests/send-selectors.test.js | 34 ---------------------- 8 files changed, 37 insertions(+), 47 deletions(-) create mode 100644 ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js (limited to 'ui') diff --git a/ui/app/components/send_/send-footer/send-footer.component.js b/ui/app/components/send_/send-footer/send-footer.component.js index 7ff44c9d5..6471ae1a3 100644 --- a/ui/app/components/send_/send-footer/send-footer.component.js +++ b/ui/app/components/send_/send-footer/send-footer.component.js @@ -77,9 +77,9 @@ export default class SendFooter extends Component { } formShouldBeDisabled () { - const { inError, selectedToken, tokenBalance, gasTotal } = this.props + const { inError, selectedToken, tokenBalance, gasTotal, to } = this.props const missingTokenBalance = selectedToken && !tokenBalance - return inError || !gasTotal || missingTokenBalance + return inError || !gasTotal || missingTokenBalance || !to } render () { diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js index 64e4b4bd3..260ff40bc 100644 --- a/ui/app/components/send_/send-footer/send-footer.container.js +++ b/ui/app/components/send_/send-footer/send-footer.container.js @@ -20,8 +20,10 @@ import { getSendToAccounts, getTokenBalance, getUnapprovedTxs, - isSendFormInError, } from '../send.selectors' +import { + isSendFormInError, +} from './send-footer.selectors' import { addressIsNew, constructTxParams, diff --git a/ui/app/components/send_/send-footer/send-footer.selectors.js b/ui/app/components/send_/send-footer/send-footer.selectors.js index 15a053ae0..e20addfdc 100644 --- a/ui/app/components/send_/send-footer/send-footer.selectors.js +++ b/ui/app/components/send_/send-footer/send-footer.selectors.js @@ -7,6 +7,5 @@ const selectors = { module.exports = selectors function isSendFormInError (state) { - const { amount, to } = getSendErrors(state) - return Boolean(amount || to !== null) + return Object.values(getSendErrors(state)).some(n => n) } diff --git a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js index a74b6195c..e071fe54f 100644 --- a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js +++ b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js @@ -86,6 +86,12 @@ describe('SendFooter Component', function () { gasTotal: false, expectedResult: true, }, + 'should return true if to is truthy': { + to: '0xsomevalidAddress', + inError: false, + gasTotal: false, + expectedResult: true, + }, 'should return true if selectedToken is truthy and tokenBalance is falsy': { selectedToken: true, tokenBalance: null, diff --git a/ui/app/components/send_/send-footer/tests/send-footer-container.test.js b/ui/app/components/send_/send-footer/tests/send-footer-container.test.js index 9a616777e..39d6a7686 100644 --- a/ui/app/components/send_/send-footer/tests/send-footer-container.test.js +++ b/ui/app/components/send_/send-footer/tests/send-footer-container.test.js @@ -39,9 +39,8 @@ proxyquire('../send-footer.container.js', { getSendToAccounts: (s) => `mockToAccounts:${s}`, getTokenBalance: (s) => `mockTokenBalance:${s}`, getUnapprovedTxs: (s) => `mockUnapprovedTxs:${s}`, - isSendFormInError: (s) => `mockInError:${s}`, }, - './send-footer.selectors': { isSendFormInError: () => {} }, + './send-footer.selectors': { isSendFormInError: (s) => `mockInError:${s}` }, './send-footer.utils': utilsStubs, }) diff --git a/ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js b/ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js new file mode 100644 index 000000000..8de032f57 --- /dev/null +++ b/ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js @@ -0,0 +1,24 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' + +const { + isSendFormInError, +} = proxyquire('../send-footer.selectors', { + '../send.selectors': { + getSendErrors: (mockState) => mockState.errors, + }, +}) + +describe('send-footer selectors', () => { + + describe('getTitleKey()', () => { + it('should return true if any of the values of the object returned by getSendErrors are truthy', () => { + assert.equal(isSendFormInError({ errors: { a: 'abc', b: false} }), true) + }) + + it('should return false if all of the values of the object returned by getSendErrors are falsy', () => { + assert.equal(isSendFormInError({ errors: { a: false, b: null} }), false) + }) + }) + +}) diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js index 476e77cac..c5ae1ab7f 100644 --- a/ui/app/components/send_/send.selectors.js +++ b/ui/app/components/send_/send.selectors.js @@ -39,7 +39,6 @@ const selectors = { getTokenBalance, getTokenExchangeRate, getUnapprovedTxs, - isSendFormInError, transactionsSelector, } @@ -251,11 +250,6 @@ function getUnapprovedTxs (state) { return state.metamask.unapprovedTxs } -function isSendFormInError (state) { - const { amount, to } = getSendErrors(state) - return Boolean(amount || to !== null) -} - function transactionsSelector (state) { const { network, selectedTokenAddress } = state.metamask const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs) diff --git a/ui/app/components/send_/tests/send-selectors.test.js b/ui/app/components/send_/tests/send-selectors.test.js index 9dc207ead..977fe2a47 100644 --- a/ui/app/components/send_/tests/send-selectors.test.js +++ b/ui/app/components/send_/tests/send-selectors.test.js @@ -36,7 +36,6 @@ const { getTokenBalance, getTokenExchangeRate, getUnapprovedTxs, - isSendFormInError, transactionsSelector, } = selectors import mockState from './send-selectors-test-data' @@ -546,39 +545,6 @@ describe('send selectors', () => { }) }) - describe('isSendFormInError()', () => { - it('should return true if amount or to errors are truthy', () => { - const editedMockState1 = { - send: Object.assign({}, mockState.send, { - errors: { amount: true }, - }), - } - assert.deepEqual( - isSendFormInError(editedMockState1), - true - ) - const editedMockState2 = { - send: Object.assign({}, mockState.send, { - errors: { to: true }, - }), - } - assert.deepEqual( - isSendFormInError(editedMockState2), - true - ) - }) - - it('should return false if amount is falsy and to is null', () => { - const editedMockState = { - send: Object.assign({}, mockState.send, { errors: { amount: false, to: null } }), - } - assert.deepEqual( - isSendFormInError(editedMockState), - false - ) - }) - }) - describe('transactionsSelector()', () => { it('should return the selected addresses selected token transactions', () => { assert.deepEqual( -- cgit From ea28c8a437cddd0c2cb69809a23f1f9a0ceba0dc Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 28 May 2018 14:11:23 -0230 Subject: Replaces currency-input.js with NumericInput --- ui/app/components/send/currency-display.js | 57 ++++++++++++++++------ .../send-amount-row/send-amount-row.component.js | 6 +-- .../tests/send-amount-row-component.test.js | 38 +++++++-------- ui/app/css/itcss/components/currency-display.scss | 22 +++++++++ 4 files changed, 85 insertions(+), 38 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 90fb2b66c..b98ebee09 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -1,10 +1,10 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const CurrencyInput = require('../currency-input') const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') const currencyFormatter = require('currency-formatter') const currencies = require('currency-formatter/currencies') +const NumericInput = require('react-numeric-input') module.exports = CurrencyDisplay @@ -21,21 +21,36 @@ function toHexWei (value) { }) } +CurrencyDisplay.prototype.componentWillMount = function () { + this.setState({ + valueToRender: this.getValueToRender(this.props), + }) +} + +CurrencyDisplay.prototype.componentWillReceiveProps = function (nextProps) { + const currentValueToRender = this.getValueToRender(this.props) + const newValueToRender = this.getValueToRender(nextProps) + if (currentValueToRender !== newValueToRender) { + this.setState({ + valueToRender: newValueToRender, + }) + } +} + CurrencyDisplay.prototype.getAmount = function (value) { const { selectedToken } = this.props const { decimals } = selectedToken || {} const multiplier = Math.pow(10, Number(decimals || 0)) - const sendAmount = multiplyCurrencies(value, multiplier, {toNumericBase: 'hex'}) + const sendAmount = multiplyCurrencies(value || '0', multiplier, {toNumericBase: 'hex'}) return selectedToken ? sendAmount : toHexWei(value) } -CurrencyDisplay.prototype.getValueToRender = function () { - const { selectedToken, conversionRate, value } = this.props - +CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value }) { + if (value === '0x0') return '' const { decimals, symbol } = selectedToken || {} const multiplier = Math.pow(10, Number(decimals || 0)) @@ -76,6 +91,19 @@ CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValu : convertedValue } +CurrencyDisplay.prototype.handleChange = function (newVal) { + this.setState({ valueToRender: newVal }) + this.props.onChange(this.getAmount(newVal)) +} + +CurrencyDisplay.prototype.getInputWidth = function (valueToRender, readOnly) { + const valueString = String(valueToRender) + const valueLength = valueString.length || 1 + const dynamicBuffer = readOnly ? 0 : 1 + const decimalPointDeficit = !readOnly && valueString.match(/\./) ? -0.5 : 0 + return (valueLength + dynamicBuffer + decimalPointDeficit) + 'ch' +} + CurrencyDisplay.prototype.render = function () { const { className = 'currency-display', @@ -85,10 +113,10 @@ CurrencyDisplay.prototype.render = function () { convertedCurrency, readOnly = false, inError = false, - handleChange, + onBlur, } = this.props + const { valueToRender } = this.state - const valueToRender = this.getValueToRender() const convertedValueToRender = this.getConvertedValueToRender(valueToRender) return h('div', { @@ -103,21 +131,20 @@ CurrencyDisplay.prototype.render = function () { h('div.currency-display__input-wrapper', [ - h(readOnly ? 'input' : CurrencyInput, { + h(NumericInput, { className: primaryBalanceClassName, value: `${valueToRender}`, - placeholder: '0', + placeholder: `0 ${primaryCurrency}`, readOnly, ...(!readOnly ? { - onInputChange: newValue => { - handleChange(this.getAmount(newValue)) - }, - inputRef: input => { this.currencyInput = input }, + onChange: e => this.handleChange(e), + onBlur: () => onBlur(this.getAmount(valueToRender)), } : {}), + style: false, + format: num => `${num} ${primaryCurrency}`, + parse: stringWithCurrency => stringWithCurrency && stringWithCurrency.match(/^([.\d]+)/)[1], }), - h('span.currency-display__currency-symbol', primaryCurrency), - ]), ]), diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index b094d0cd5..8aefeed4a 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -49,11 +49,10 @@ export default class SendAmountRow extends Component { }) } - handleAmountChange (amount) { + updateAmount (amount) { const { updateSendAmount, setMaxModeTo } = this.props setMaxModeTo(false) - this.validateAmount(amount) updateSendAmount(amount) } @@ -78,7 +77,8 @@ export default class SendAmountRow extends Component { this.handleAmountChange(newAmount)} + onBlur={newAmount => this.updateAmount(newAmount)} + onChange={newAmount => this.validateAmount(newAmount)} inError={inError} primaryCurrency={primaryCurrency || 'ETH'} selectedToken={selectedToken} diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js index 31d2e2515..2205579ca 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js @@ -14,7 +14,7 @@ const propsMethodSpies = { updateSendAmountError: sinon.spy(), } -sinon.spy(SendAmountRow.prototype, 'handleAmountChange') +sinon.spy(SendAmountRow.prototype, 'updateAmount') sinon.spy(SendAmountRow.prototype, 'validateAmount') describe('SendAmountRow Component', function () { @@ -45,7 +45,7 @@ describe('SendAmountRow Component', function () { propsMethodSpies.updateSendAmount.resetHistory() propsMethodSpies.updateSendAmountError.resetHistory() SendAmountRow.prototype.validateAmount.resetHistory() - SendAmountRow.prototype.handleAmountChange.resetHistory() + SendAmountRow.prototype.updateAmount.resetHistory() }) describe('validateAmount', () => { @@ -71,11 +71,11 @@ describe('SendAmountRow Component', function () { }) - describe('handleAmountChange', () => { + describe('updateAmount', () => { it('should call setMaxModeTo', () => { assert.equal(propsMethodSpies.setMaxModeTo.callCount, 0) - instance.handleAmountChange('someAmount') + instance.updateAmount('someAmount') assert.equal(propsMethodSpies.setMaxModeTo.callCount, 1) assert.deepEqual( propsMethodSpies.setMaxModeTo.getCall(0).args, @@ -83,19 +83,9 @@ describe('SendAmountRow Component', function () { ) }) - it('should call this.validateAmount', () => { - assert.equal(SendAmountRow.prototype.validateAmount.callCount, 0) - instance.handleAmountChange('someAmount') - assert.equal(SendAmountRow.prototype.validateAmount.callCount, 1) - assert.deepEqual( - propsMethodSpies.updateSendAmount.getCall(0).args, - ['someAmount'] - ) - }) - it('should call updateSendAmount', () => { assert.equal(propsMethodSpies.updateSendAmount.callCount, 0) - instance.handleAmountChange('someAmount') + instance.updateAmount('someAmount') assert.equal(propsMethodSpies.updateSendAmount.callCount, 1) assert.deepEqual( propsMethodSpies.updateSendAmount.getCall(0).args, @@ -136,7 +126,8 @@ describe('SendAmountRow Component', function () { const { conversionRate, convertedCurrency, - handleChange, + onBlur, + onChange, inError, primaryCurrency, selectedToken, @@ -148,11 +139,18 @@ describe('SendAmountRow Component', function () { assert.equal(primaryCurrency, 'mockPrimaryCurrency') assert.deepEqual(selectedToken, { address: 'mockTokenAddress' }) assert.equal(value, 'mockAmount') - assert.equal(SendAmountRow.prototype.handleAmountChange.callCount, 0) - handleChange('mockNewAmount') - assert.equal(SendAmountRow.prototype.handleAmountChange.callCount, 1) + assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0) + onBlur('mockNewAmount') + assert.equal(SendAmountRow.prototype.updateAmount.callCount, 1) + assert.deepEqual( + SendAmountRow.prototype.updateAmount.getCall(0).args, + ['mockNewAmount'] + ) + assert.equal(SendAmountRow.prototype.validateAmount.callCount, 0) + onChange('mockNewAmount') + assert.equal(SendAmountRow.prototype.validateAmount.callCount, 1) assert.deepEqual( - SendAmountRow.prototype.handleAmountChange.getCall(0).args, + SendAmountRow.prototype.validateAmount.getCall(0).args, ['mockNewAmount'] ) }) diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss index 36d843c79..3560b0b0c 100644 --- a/ui/app/css/itcss/components/currency-display.scss +++ b/ui/app/css/itcss/components/currency-display.scss @@ -47,10 +47,32 @@ &__input-wrapper { position: relative; display: flex; + + input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + display: none; + } + + input[type="number"]:hover::-webkit-inner-spin-button { + -webkit-appearance: none; + display: none; + } } &__currency-symbol { margin-top: 1px; color: $scorpion; } + + .react-numeric-input { + input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + display: none; + } + + input[type="number"]:hover::-webkit-inner-spin-button { + -webkit-appearance: none; + display: none; + } + } } \ No newline at end of file -- cgit From f33bb3e2fd8ba1cc30dac11017f26ba82c26a82d Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 29 May 2018 16:24:44 -0230 Subject: Stop using external NumericInput component. --- ui/app/components/send/currency-display.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index b98ebee09..60032bca4 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -4,7 +4,6 @@ const inherits = require('util').inherits const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') const currencyFormatter = require('currency-formatter') const currencies = require('currency-formatter/currencies') -const NumericInput = require('react-numeric-input') module.exports = CurrencyDisplay @@ -92,6 +91,7 @@ CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValu } CurrencyDisplay.prototype.handleChange = function (newVal) { + console.log(`%^ 95 newVal`, newVal); this.setState({ valueToRender: newVal }) this.props.onChange(this.getAmount(newVal)) } @@ -124,27 +124,34 @@ CurrencyDisplay.prototype.render = function () { style: { borderColor: inError ? 'red' : null, }, - onClick: () => this.currencyInput && this.currencyInput.focus(), + onClick: () => { + this.currencyInput && this.currencyInput.focus() + }, }, [ h('div.currency-display__primary-row', [ h('div.currency-display__input-wrapper', [ - h(NumericInput, { + h('input', { className: primaryBalanceClassName, value: `${valueToRender}`, - placeholder: `0 ${primaryCurrency}`, + placeholder: '0', + type: 'number', readOnly, ...(!readOnly ? { - onChange: e => this.handleChange(e), + onChange: e => this.handleChange(e.target.value), onBlur: () => onBlur(this.getAmount(valueToRender)), } : {}), - style: false, - format: num => `${num} ${primaryCurrency}`, - parse: stringWithCurrency => stringWithCurrency && stringWithCurrency.match(/^([.\d]+)/)[1], + ref: input => { this.currencyInput = input }, + style: { + width: this.getInputWidth(valueToRender, readOnly), + }, + min: 0, }), + h('span.currency-display__currency-symbol', primaryCurrency), + ]), ]), -- cgit From 2e1d962b1a36eb0369d00e1e18c0bb810c871590 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 29 May 2018 22:15:21 -0230 Subject: Remove currency input from input-number --- ui/app/components/input-number.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/input-number.js b/ui/app/components/input-number.js index 5600e35ee..de5fcca54 100644 --- a/ui/app/components/input-number.js +++ b/ui/app/components/input-number.js @@ -1,7 +1,6 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const CurrencyInput = require('./currency-input') const { addCurrencies, conversionGTE, @@ -51,14 +50,15 @@ InputNumber.prototype.render = function () { const { unitLabel, step = 1, placeholder, value = 0 } = this.props return h('div.customize-gas-input-wrapper', {}, [ - h(CurrencyInput, { + h('input', { className: 'customize-gas-input', value, placeholder, type: 'number', - onInputChange: newValue => { - this.setValue(newValue) + onChange: e => { + this.setValue(e.target.value) }, + min: 0, }), h('span.gas-tooltip-input-detail', {}, [unitLabel]), h('div.gas-tooltip-input-arrows', {}, [ -- cgit From 7f23e017b22ecdc111c2c14f97d632f5490cb5c8 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 29 May 2018 22:20:17 -0230 Subject: Delete currency-input.js --- ui/app/components/currency-input.js | 113 ----------------------------- ui/app/components/send/currency-display.js | 1 - 2 files changed, 114 deletions(-) delete mode 100644 ui/app/components/currency-input.js (limited to 'ui') diff --git a/ui/app/components/currency-input.js b/ui/app/components/currency-input.js deleted file mode 100644 index ece3eb43d..000000000 --- a/ui/app/components/currency-input.js +++ /dev/null @@ -1,113 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits - -module.exports = CurrencyInput - -inherits(CurrencyInput, Component) -function CurrencyInput (props) { - Component.call(this) - - const sanitizedValue = sanitizeValue(props.value) - - this.state = { - value: sanitizedValue, - emptyState: false, - focused: false, - } -} - -function removeNonDigits (str) { - return str.match(/\d|$/g).join('') -} - -// Removes characters that are not digits, then removes leading zeros -function sanitizeInteger (val) { - return String(parseInt(removeNonDigits(val) || '0', 10)) -} - -function sanitizeDecimal (val) { - return removeNonDigits(val) -} - -// Take a single string param and returns a non-negative integer or float as a string. -// Breaks the input into three parts: the integer, the decimal point, and the decimal/fractional part. -// Removes leading zeros from the integer, and non-digits from the integer and decimal -// The integer is returned as '0' in cases where it would be empty. A decimal point is -// included in the returned string if one is included in the param -// Examples: -// sanitizeValue('0') -> '0' -// sanitizeValue('a') -> '0' -// sanitizeValue('010.') -> '10.' -// sanitizeValue('0.005') -> '0.005' -// sanitizeValue('22.200') -> '22.200' -// sanitizeValue('.200') -> '0.200' -// sanitizeValue('a.b.1.c,89.123') -> '0.189123' -function sanitizeValue (value) { - let [ , integer, point, decimal] = (/([^.]*)([.]?)([^.]*)/).exec(value) - - integer = sanitizeInteger(integer) || '0' - decimal = sanitizeDecimal(decimal) - - return `${integer}${point}${decimal}` -} - -CurrencyInput.prototype.handleChange = function (newValue) { - const { onInputChange } = this.props - const { value } = this.state - - let parsedValue = newValue - const newValueLastIndex = newValue.length - 1 - - if (value === '0' && newValue[newValueLastIndex] === '0') { - parsedValue = parsedValue.slice(0, newValueLastIndex) - } - const sanitizedValue = sanitizeValue(parsedValue) - this.setState({ - value: sanitizedValue, - emptyState: newValue === '' && sanitizedValue === '0', - }) - onInputChange(sanitizedValue) -} - -// If state.value === props.value plus a decimal point, or at least one -// zero or a decimal point and at least one zero, then this returns state.value -// after it is sanitized with getValueParts -CurrencyInput.prototype.getValueToRender = function () { - const { value } = this.props - const { value: stateValue } = this.state - - const trailingStateString = (new RegExp(`^${value}(.+)`)).exec(stateValue) - const trailingDecimalAndZeroes = trailingStateString && (/^[.0]0*/).test(trailingStateString[1]) - - return sanitizeValue(trailingDecimalAndZeroes - ? stateValue - : value) -} - -CurrencyInput.prototype.render = function () { - const { - className, - placeholder, - readOnly, - inputRef, - type, - } = this.props - const { emptyState, focused } = this.state - - const inputSizeMultiplier = readOnly ? 1 : 1.2 - - const valueToRender = this.getValueToRender() - return h('input', { - className, - type, - value: emptyState ? '' : valueToRender, - placeholder: focused ? '' : placeholder, - size: valueToRender.length * inputSizeMultiplier, - readOnly, - onFocus: () => this.setState({ focused: true, emptyState: valueToRender === '0' }), - onBlur: () => this.setState({ focused: false, emptyState: false }), - onChange: e => this.handleChange(e.target.value), - ref: inputRef, - }) -} diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 60032bca4..0b52a69b5 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -91,7 +91,6 @@ CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValu } CurrencyDisplay.prototype.handleChange = function (newVal) { - console.log(`%^ 95 newVal`, newVal); this.setState({ valueToRender: newVal }) this.props.onChange(this.getAmount(newVal)) } -- cgit From 5347319dffa0a9e4df5fe26d5ea60293b3489f78 Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 30 May 2018 13:35:01 -0230 Subject: Fix setting of token balance on account switch in send screen from field. --- .../send_/send-content/send-from-row/send-from-row.container.js | 2 +- .../send-content/send-from-row/tests/send-from-row-container.test.js | 2 +- ui/app/components/send_/send.utils.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js index 402b744e4..33cb63b43 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js @@ -39,7 +39,7 @@ function mapDispatchToProps (dispatch) { setSendTokenBalance: (usersToken, selectedToken) => { if (!usersToken) return - const tokenBalance = calcTokenBalance(usersToken, selectedToken) + const tokenBalance = calcTokenBalance({ usersToken, selectedToken }) dispatch(setSendTokenBalance(tokenBalance)) }, } diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js index 785b3d3ef..e080b2fe3 100644 --- a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js +++ b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js @@ -29,7 +29,7 @@ proxyquire('../send-from-row.container.js', { getSendFromObject: (s) => `mockFrom:${s}`, }, './send-from-row.selectors.js': { getFromDropdownOpen: (s) => `mockFromDropdownOpen:${s}` }, - '../../send.utils.js': { calcTokenBalance: (a, b) => a + b }, + '../../send.utils.js': { calcTokenBalance: ({ usersToken, selectedToken }) => usersToken + selectedToken }, '../../../../actions': actionSpies, '../../../../ducks/send.duck': duckActionSpies, }) diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index f09a02a6a..71a8a35c4 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -158,7 +158,7 @@ function getParamsForGasEstimate (selectedAddress, symbol, data) { function calcTokenBalance ({ selectedToken, usersToken }) { const { decimals } = selectedToken || {} - return calcTokenAmount(usersToken.balance.toString(), decimals) + return calcTokenAmount(usersToken.balance.toString(), decimals) + '' } function doesAmountErrorRequireUpdate ({ -- cgit From 1c3d2aa18b85ddb83734d6afdbb1111ba0791229 Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 30 May 2018 16:43:45 -0230 Subject: Importing account by json and private key shows error and does not change account if no selectedAddress comes after import. --- ui/app/actions.js | 10 ++++++---- ui/app/app.js | 7 +++++-- .../pages/create-account/import-account/json.js | 23 +++++++++++++++++----- .../create-account/import-account/private-key.js | 22 ++++++++++++++++----- 4 files changed, 46 insertions(+), 16 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 649f740e9..ed3c147b8 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -544,10 +544,12 @@ function importNewAccount (strategy, args) { } dispatch(actions.hideLoadingIndication()) dispatch(actions.updateMetamaskState(newState)) - dispatch({ - type: actions.SHOW_ACCOUNT_DETAIL, - value: newState.selectedAddress, - }) + if (newState.selectedAddress) { + dispatch({ + type: actions.SHOW_ACCOUNT_DETAIL, + value: newState.selectedAddress, + }) + } return newState } } diff --git a/ui/app/app.js b/ui/app/app.js index 0e8b907df..7005adb7f 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -99,7 +99,7 @@ class App extends Component { } = this.props const isLoadingNetwork = network === 'loading' && currentView.name !== 'config' const loadMessage = loadingMessage || isLoadingNetwork ? - this.getConnectingLabel() : null + this.getConnectingLabel(loadingMessage) : null log.debug('Main ui render function') return ( @@ -210,7 +210,10 @@ class App extends Component { } } - getConnectingLabel = function () { + getConnectingLabel = function (loadingMessage) { + if (loadingMessage) { + return loadingMessage + } const { provider } = this.props const providerName = provider.type diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js index 0a3314b2a..3dabad68e 100644 --- a/ui/app/components/pages/create-account/import-account/json.js +++ b/ui/app/components/pages/create-account/import-account/json.js @@ -82,18 +82,19 @@ class JsonImportSubview extends Component { } createNewKeychain () { + const { firstAddress, displayWarning, importNewJsonAccount, setSelectedAddress } = this.props const state = this.state if (!state) { const message = this.context.t('validFileImport') - return this.props.displayWarning(message) + return displayWarning(message) } const { fileContents } = state if (!fileContents) { const message = this.context.t('needImportFile') - return this.props.displayWarning(message) + return displayWarning(message) } const passwordInput = document.getElementById('json-password-box') @@ -101,12 +102,20 @@ class JsonImportSubview extends Component { if (!password) { const message = this.context.t('needImportPassword') - return this.props.displayWarning(message) + return displayWarning(message) } - this.props.importNewJsonAccount([ fileContents, password ]) - // JS runtime requires caught rejections but failures are handled by Redux + importNewJsonAccount([ fileContents, password ]) + // JS runtime requires caught rejections but failures are handled by Redux .catch() + .then(({ selectedAddress }) => { + if (selectedAddress) { + history.push(DEFAULT_ROUTE) + } else { + displayWarning('Error importing account.') + setSelectedAddress(firstAddress) + } + }) } } @@ -114,14 +123,17 @@ JsonImportSubview.propTypes = { error: PropTypes.string, goHome: PropTypes.func, displayWarning: PropTypes.func, + firstAddress: PropTypes.string, importNewJsonAccount: PropTypes.func, history: PropTypes.object, + setSelectedAddress: PropTypes.func, t: PropTypes.func, } const mapStateToProps = state => { return { error: state.appState.warning, + firstAddress: Object.keys(state.metamask.accounts)[0], } } @@ -130,6 +142,7 @@ const mapDispatchToProps = dispatch => { goHome: () => dispatch(actions.goHome()), displayWarning: warning => dispatch(actions.displayWarning(warning)), importNewJsonAccount: options => dispatch(actions.importNewAccount('JSON File', options)), + setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)), } } diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js index df7ac910a..e71e47647 100644 --- a/ui/app/components/pages/create-account/import-account/private-key.js +++ b/ui/app/components/pages/create-account/import-account/private-key.js @@ -21,6 +21,7 @@ module.exports = compose( function mapStateToProps (state) { return { error: state.appState.warning, + firstAddress: Object.keys(state.metamask.accounts)[0], } } @@ -29,7 +30,8 @@ function mapDispatchToProps (dispatch) { importNewAccount: (strategy, [ privateKey ]) => { return dispatch(actions.importNewAccount(strategy, [ privateKey ])) }, - displayWarning: () => dispatch(actions.displayWarning(null)), + displayWarning: (message) => dispatch(actions.displayWarning(message || null)), + setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)), } } @@ -40,7 +42,7 @@ function PrivateKeyImportView () { } PrivateKeyImportView.prototype.render = function () { - const { error } = this.props + const { error, displayWarning } = this.props return ( h('div.new-account-import-form__private-key', [ @@ -60,7 +62,10 @@ PrivateKeyImportView.prototype.render = function () { h('div.new-account-import-form__buttons', {}, [ h('button.btn-secondary--lg.new-account-create-form__button', { - onClick: () => this.props.history.push(DEFAULT_ROUTE), + onClick: () => { + displayWarning(null) + this.props.history.push(DEFAULT_ROUTE) + }, }, [ this.context.t('cancel'), ]), @@ -88,10 +93,17 @@ PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) { PrivateKeyImportView.prototype.createNewKeychain = function () { const input = document.getElementById('private-key-box') const privateKey = input.value - const { importNewAccount, history } = this.props + const { importNewAccount, history, displayWarning, setSelectedAddress, firstAddress } = this.props importNewAccount('Private Key', [ privateKey ]) // JS runtime requires caught rejections but failures are handled by Redux .catch() - .then(() => history.push(DEFAULT_ROUTE)) + .then(({ selectedAddress }) => { + if (selectedAddress) { + history.push(DEFAULT_ROUTE) + } else { + displayWarning('Error importing account.') + setSelectedAddress(firstAddress) + } + }) } -- cgit From 6d8344d0d0af3734255a0e9e79d857d84b5fe2aa Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 30 May 2018 14:10:30 -0230 Subject: Update amount error on update of send screen. --- ui/app/components/send_/send.component.js | 37 +++++++++++----------- ui/app/components/send_/send.utils.js | 4 +-- .../components/send_/tests/send-component.test.js | 12 +++++-- 3 files changed, 30 insertions(+), 23 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index 8b0a41f9e..21e1de09b 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -83,30 +83,31 @@ export default class SendTransactionScreen extends PersistentForm { const uninitialized = [prevBalance, prevGasTotal].every(n => n === null) - if (!uninitialized) { - const amountErrorRequiresUpdate = doesAmountErrorRequireUpdate({ + const amountErrorRequiresUpdate = doesAmountErrorRequireUpdate({ + balance, + gasTotal, + prevBalance, + prevGasTotal, + prevTokenBalance, + selectedToken, + tokenBalance, + }) + + if (amountErrorRequiresUpdate) { + const amountErrorObject = getAmountErrorObject({ + amount, + amountConversionRate, balance, + conversionRate, gasTotal, - prevBalance, - prevGasTotal, - prevTokenBalance, + primaryCurrency, selectedToken, tokenBalance, }) + updateSendErrors(amountErrorObject) + } - if (amountErrorRequiresUpdate) { - const amountErrorObject = getAmountErrorObject({ - amount, - amountConversionRate, - balance, - conversionRate, - gasTotal, - primaryCurrency, - selectedToken, - tokenBalance, - }) - updateSendErrors(amountErrorObject) - } + if (!uninitialized) { if (network !== prevNetwork && network !== 'loading') { updateSendTokenBalance({ diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 71a8a35c4..a35a55bf8 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -36,7 +36,7 @@ function calcGasTotal (gasLimit, gasPrice) { function isBalanceSufficient ({ amount = '0x0', - amountConversionRate, + amountConversionRate = 0, balance, conversionRate, gasTotal = '0x0', @@ -58,7 +58,7 @@ function isBalanceSufficient ({ { value: totalAmount, fromNumericBase: 'hex', - conversionRate: amountConversionRate || conversionRate, + conversionRate: Number(amountConversionRate) || conversionRate, fromCurrency: primaryCurrency, }, ) diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js index 8aeab36b4..4aa1978e4 100644 --- a/ui/app/components/send_/tests/send-component.test.js +++ b/ui/app/components/send_/tests/send-component.test.js @@ -25,7 +25,7 @@ const SendTransactionScreen = proxyquire('../send.component.js', { sinon.spy(SendTransactionScreen.prototype, 'componentDidMount') sinon.spy(SendTransactionScreen.prototype, 'updateGas') -describe('Send Component', function () { +describe.only('Send Component', function () { let wrapper beforeEach(() => { @@ -68,14 +68,17 @@ describe('Send Component', function () { describe('componentWillMount', () => { it('should call this.updateGas', () => { - assert(SendTransactionScreen.prototype.updateGas.calledOnce) + SendTransactionScreen.prototype.updateGas.resetHistory() + propsMethodSpies.updateSendErrors.resetHistory() + assert.equal(SendTransactionScreen.prototype.updateGas.callCount, 0) wrapper.instance().componentWillMount() - assert(SendTransactionScreen.prototype.updateGas.calledTwice) + assert.equal(SendTransactionScreen.prototype.updateGas.callCount, 1) }) }) describe('componentDidUpdate', () => { it('should call doesAmountErrorRequireUpdate with the expected params', () => { + utilsMethodStubs.getAmountErrorObject.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: '', @@ -97,6 +100,7 @@ describe('Send Component', function () { }) it('should not call getAmountErrorObject if doesAmountErrorRequireUpdate returns false', () => { + utilsMethodStubs.getAmountErrorObject.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'mockBalance', @@ -106,6 +110,7 @@ describe('Send Component', function () { }) it('should call getAmountErrorObject if doesAmountErrorRequireUpdate returns true', () => { + utilsMethodStubs.getAmountErrorObject.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', @@ -128,6 +133,7 @@ describe('Send Component', function () { }) it('should call updateSendErrors with the expected params', () => { + propsMethodSpies.updateSendErrors.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', -- cgit From 8b6d08a15dc40b4cd8159a543b5cc6855f7848a8 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 17 May 2018 12:44:46 -0230 Subject: Split out getGasEstimate logic from updateGasTotal --- ui/app/actions.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index fc2a838d4..3aa046345 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -161,6 +161,7 @@ var actions = { UPDATE_TRANSACTION_PARAMS: 'UPDATE_TRANSACTION_PARAMS', // send screen estimateGas, + getGasEstimate, getGasPrice, UPDATE_GAS_LIMIT: 'UPDATE_GAS_LIMIT', UPDATE_GAS_PRICE: 'UPDATE_GAS_PRICE', @@ -757,7 +758,7 @@ function setGasTotal (gasTotal) { } } -function updateGasTotal ({ selectedAddress, selectedToken, data }) { +function getGasEstimate ({ selectedAddress, selectedToken, data }) { return (dispatch) => { const { symbol } = selectedToken || {} const estimateGasParams = getParamsForGasEstimate(selectedAddress, symbol, data) @@ -766,8 +767,16 @@ function updateGasTotal ({ selectedAddress, selectedToken, data }) { dispatch(actions.estimateGas(estimateGasParams)), ]) .then(([gasPrice, gas]) => { - const newGasTotal = calcGasTotal(gas, gasPrice) - dispatch(actions.setGasTotal(newGasTotal)) + return calcGasTotal(gas, gasPrice) + }) + } +} + +function updateGasTotal ({ selectedAddress, selectedToken, data }) { + return (dispatch) => { + return dispatch(actions.getGasEstimate({ selectedAddress, selectedToken, data })) + .then((gasEstimate) => { + dispatch(actions.setGasTotal(gasEstimate)) dispatch(updateSendErrors({ gasLoadingError: null })) }) .catch(err => { -- cgit From 6f633a97e15e8277a065f987880cd666cc2fbf89 Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 19 May 2018 22:07:44 -0230 Subject: Rename gas change actions. --- ui/app/actions.js | 16 ++++++++-------- ui/app/components/customize-gas-modal/index.js | 18 +++++++++--------- ui/app/components/send_/send.container.js | 4 ++-- ui/app/components/send_/tests/send-container.test.js | 6 +++--- 4 files changed, 22 insertions(+), 22 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 3aa046345..d83ecbe24 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -177,9 +177,9 @@ var actions = { CLEAR_SEND: 'CLEAR_SEND', OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN', CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN', - updateGasLimit, - updateGasPrice, - updateGasTotal, + setGasLimit, + setGasPrice, + updateGasData, setGasTotal, setSendTokenBalance, updateSendTokenBalance, @@ -714,14 +714,14 @@ function estimateGas (params = {}) { return reject(err) } dispatch(actions.hideWarning()) - dispatch(actions.updateGasLimit(data)) + dispatch(actions.setGasLimit(data)) return resolve(data) }) }) } } -function updateGasLimit (gasLimit) { +function setGasLimit (gasLimit) { return { type: actions.UPDATE_GAS_LIMIT, value: gasLimit, @@ -737,14 +737,14 @@ function getGasPrice () { return reject(err) } dispatch(actions.hideWarning()) - dispatch(actions.updateGasPrice(data)) + dispatch(actions.setGasPrice(data)) return resolve(data) }) }) } } -function updateGasPrice (gasPrice) { +function setGasPrice (gasPrice) { return { type: actions.UPDATE_GAS_PRICE, value: gasPrice, @@ -772,7 +772,7 @@ function getGasEstimate ({ selectedAddress, selectedToken, data }) { } } -function updateGasTotal ({ selectedAddress, selectedToken, data }) { +function updateGasData ({ selectedAddress, selectedToken, data }) { return (dispatch) => { return dispatch(actions.getGasEstimate({ selectedAddress, selectedToken, data })) .then((gasEstimate) => { diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index df933e363..9ab785760 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -65,9 +65,9 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return { hideModal: () => dispatch(actions.hideModal()), - updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)), - updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), - updateGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)), + setGasPrice: newGasPrice => dispatch(actions.setGasPrice(newGasPrice)), + setGasLimit: newGasLimit => dispatch(actions.setGasLimit(newGasLimit)), + updateGasData: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)), updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), updateSendErrors: error => dispatch(updateSendErrors(error)), } @@ -109,10 +109,10 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal) CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { const { - updateGasPrice, - updateGasLimit, + setGasPrice, + setGasLimit, hideModal, - updateGasTotal, + updateGasData, maxModeOn, selectedToken, balance, @@ -129,9 +129,9 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { updateSendAmount(maxAmount) } - updateGasPrice(ethUtil.addHexPrefix(gasPrice)) - updateGasLimit(ethUtil.addHexPrefix(gasLimit)) - updateGasTotal(ethUtil.addHexPrefix(gasTotal)) + setGasPrice(ethUtil.addHexPrefix(gasPrice)) + setGasLimit(ethUtil.addHexPrefix(gasLimit)) + updateGasData(ethUtil.addHexPrefix(gasTotal)) updateSendErrors({ insufficientFunds: false }) hideModal() } diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js index 8efaf5aaf..df28caca8 100644 --- a/ui/app/components/send_/send.container.js +++ b/ui/app/components/send_/send.container.js @@ -21,7 +21,7 @@ import { } from './send.selectors' import { updateSendTokenBalance, - updateGasTotal, + updateGasData, setGasTotal, } from '../../actions' import { @@ -73,7 +73,7 @@ function mapDispatchToProps (dispatch) { }) => { console.log(`editingTransactionId`, editingTransactionId) !editingTransactionId - ? dispatch(updateGasTotal({ selectedAddress, selectedToken, data })) + ? dispatch(updateGasData({ selectedAddress, selectedToken, data })) : dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice))) }, updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => { diff --git a/ui/app/components/send_/tests/send-container.test.js b/ui/app/components/send_/tests/send-container.test.js index 7b6ca1f7b..e589cca05 100644 --- a/ui/app/components/send_/tests/send-container.test.js +++ b/ui/app/components/send_/tests/send-container.test.js @@ -7,7 +7,7 @@ let mapDispatchToProps const actionSpies = { updateSendTokenBalance: sinon.spy(), - updateGasTotal: sinon.spy(), + updateGasData: sinon.spy(), setGasTotal: sinon.spy(), } const duckActionSpies = { @@ -104,14 +104,14 @@ describe('send container', () => { ) }) - it('should dispatch an updateGasTotal action when editingTransactionId is falsy', () => { + it('should dispatch an updateGasData action when editingTransactionId is falsy', () => { const { selectedAddress, selectedToken, data } = mockProps mapDispatchToPropsObject.updateAndSetGasTotal( Object.assign(mockProps, {editingTransactionId: false}) ) assert(dispatchSpy.calledOnce) assert.deepEqual( - actionSpies.updateGasTotal.getCall(0).args[0], + actionSpies.updateGasData.getCall(0).args[0], { selectedAddress, selectedToken, data } ) }) -- cgit From 17909465f283179aad39166b1191dbaba3770bf6 Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 19 May 2018 22:08:25 -0230 Subject: getParamsForGasEstimate extracts symbol from token instead of just accepting symbol. --- ui/app/actions.js | 3 +-- ui/app/components/send_/send.utils.js | 3 ++- ui/app/components/send_/tests/send-utils.test.js | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index d83ecbe24..bec9a8cfb 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -760,8 +760,7 @@ function setGasTotal (gasTotal) { function getGasEstimate ({ selectedAddress, selectedToken, data }) { return (dispatch) => { - const { symbol } = selectedToken || {} - const estimateGasParams = getParamsForGasEstimate(selectedAddress, symbol, data) + const estimateGasParams = getParamsForGasEstimate(selectedAddress, selectedToken, data) return Promise.all([ dispatch(actions.getGasPrice()), dispatch(actions.estimateGas(estimateGasParams)), diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index a35a55bf8..1c7fd2b42 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -139,7 +139,8 @@ function getAmountErrorObject ({ return { amount: amountError } } -function getParamsForGasEstimate (selectedAddress, symbol, data) { +function getParamsForGasEstimate (selectedAddress, selectedToken, data) { + const { symbol } = selectedToken || {} const estimatedGasParams = { from: selectedAddress, gas: '746a528800', diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index 4d471bcc1..903b531e6 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -147,9 +147,9 @@ describe('send utils', () => { ) }) - it('should return value property if symbol provided', () => { + it('should return value property if selected token provided', () => { assert.deepEqual( - getParamsForGasEstimate('mockAddress', 'ABC'), + getParamsForGasEstimate('mockAddress', { symbol: 'ABC' }), { from: 'mockAddress', gas: '746a528800', @@ -160,7 +160,7 @@ describe('send utils', () => { it('should return data property if data provided', () => { assert.deepEqual( - getParamsForGasEstimate('mockAddress', 'ABC', 'somedata'), + getParamsForGasEstimate('mockAddress', { symbol: 'ABC' }, 'somedata'), { from: 'mockAddress', gas: '746a528800', -- cgit From 166fda58777748141859c0a674a5fce454cfc3d3 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 22 May 2018 05:40:06 -0230 Subject: Simplify gas estimate actions and add local estimateGasPriceFromRecentBlocks method. --- ui/app/actions.js | 51 ++-------- ui/app/components/customize-gas-modal/index.js | 6 +- ui/app/components/send/currency-display.js | 6 +- ui/app/components/send_/send.component.js | 3 + ui/app/components/send_/send.constants.js | 8 ++ ui/app/components/send_/send.container.js | 6 +- ui/app/components/send_/send.selectors.js | 13 +++ ui/app/components/send_/send.utils.js | 37 +++++++ .../components/send_/tests/send-component.test.js | 2 + .../components/send_/tests/send-container.test.js | 9 +- .../send_/tests/send-selectors-test-data.js | 1 + .../components/send_/tests/send-selectors.test.js | 10 ++ ui/app/components/send_/tests/send-utils.test.js | 106 +++++++++++++++++++++ 13 files changed, 203 insertions(+), 55 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index bec9a8cfb..70ec3aed8 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -6,6 +6,8 @@ const { calcGasTotal, getParamsForGasEstimate, calcTokenBalance, + estimateGas, + estimateGasPriceFromRecentBlocks, } = require('./components/send_/send.utils') const ethUtil = require('ethereumjs-util') const { fetchLocale } = require('../i18n-helper') @@ -160,9 +162,6 @@ var actions = { updateTransactionParams, UPDATE_TRANSACTION_PARAMS: 'UPDATE_TRANSACTION_PARAMS', // send screen - estimateGas, - getGasEstimate, - getGasPrice, UPDATE_GAS_LIMIT: 'UPDATE_GAS_LIMIT', UPDATE_GAS_PRICE: 'UPDATE_GAS_PRICE', UPDATE_GAS_TOTAL: 'UPDATE_GAS_TOTAL', @@ -705,22 +704,6 @@ function signTx (txData) { } } -function estimateGas (params = {}) { - return (dispatch) => { - return new Promise((resolve, reject) => { - global.ethQuery.estimateGas(params, (err, data) => { - if (err) { - dispatch(actions.displayWarning(err.message)) - return reject(err) - } - dispatch(actions.hideWarning()) - dispatch(actions.setGasLimit(data)) - return resolve(data) - }) - }) - } -} - function setGasLimit (gasLimit) { return { type: actions.UPDATE_GAS_LIMIT, @@ -728,22 +711,6 @@ function setGasLimit (gasLimit) { } } -function getGasPrice () { - return (dispatch) => { - return new Promise((resolve, reject) => { - global.ethQuery.gasPrice((err, data) => { - if (err) { - dispatch(actions.displayWarning(err.message)) - return reject(err) - } - dispatch(actions.hideWarning()) - dispatch(actions.setGasPrice(data)) - return resolve(data) - }) - }) - } -} - function setGasPrice (gasPrice) { return { type: actions.UPDATE_GAS_PRICE, @@ -758,22 +725,18 @@ function setGasTotal (gasTotal) { } } -function getGasEstimate ({ selectedAddress, selectedToken, data }) { +function updateGasData ({ recentBlocks, selectedAddress, selectedToken, data }) { return (dispatch) => { const estimateGasParams = getParamsForGasEstimate(selectedAddress, selectedToken, data) return Promise.all([ - dispatch(actions.getGasPrice()), - dispatch(actions.estimateGas(estimateGasParams)), + Promise.resolve(estimateGasPriceFromRecentBlocks(recentBlocks)), + estimateGas(estimateGasParams), ]) .then(([gasPrice, gas]) => { + dispatch(actions.setGasPrice(gasPrice)) + dispatch(actions.setGasLimit(gas)) return calcGasTotal(gas, gasPrice) }) - } -} - -function updateGasData ({ selectedAddress, selectedToken, data }) { - return (dispatch) => { - return dispatch(actions.getGasEstimate({ selectedAddress, selectedToken, data })) .then((gasEstimate) => { dispatch(actions.setGasTotal(gasEstimate)) dispatch(updateSendErrors({ gasLoadingError: null })) diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 9ab785760..6637d412a 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -67,7 +67,7 @@ function mapDispatchToProps (dispatch) { hideModal: () => dispatch(actions.hideModal()), setGasPrice: newGasPrice => dispatch(actions.setGasPrice(newGasPrice)), setGasLimit: newGasLimit => dispatch(actions.setGasLimit(newGasLimit)), - updateGasData: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)), + setGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)), updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), updateSendErrors: error => dispatch(updateSendErrors(error)), } @@ -112,7 +112,7 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { setGasPrice, setGasLimit, hideModal, - updateGasData, + setGasTotal, maxModeOn, selectedToken, balance, @@ -131,7 +131,7 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { setGasPrice(ethUtil.addHexPrefix(gasPrice)) setGasLimit(ethUtil.addHexPrefix(gasLimit)) - updateGasData(ethUtil.addHexPrefix(gasTotal)) + setGasTotal(ethUtil.addHexPrefix(gasTotal)) updateSendErrors({ insufficientFunds: false }) hideModal() } diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 90fb2b66c..52dc56cb0 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -5,6 +5,7 @@ const CurrencyInput = require('../currency-input') const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') const currencyFormatter = require('currency-formatter') const currencies = require('currency-formatter/currencies') +const ethUtil = require('ethereumjs-util') module.exports = CurrencyDisplay @@ -35,18 +36,17 @@ CurrencyDisplay.prototype.getAmount = function (value) { CurrencyDisplay.prototype.getValueToRender = function () { const { selectedToken, conversionRate, value } = this.props - const { decimals, symbol } = selectedToken || {} const multiplier = Math.pow(10, Number(decimals || 0)) return selectedToken - ? conversionUtil(value, { + ? conversionUtil(ethUtil.addHexPrefix(value), { fromNumericBase: 'hex', toCurrency: symbol, conversionRate: multiplier, invertConversionRate: true, }) - : conversionUtil(value, { + : conversionUtil(ethUtil.addHexPrefix(value), { fromNumericBase: 'hex', toNumericBase: 'dec', fromDenomination: 'WEI', diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index 21e1de09b..438923ab3 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -28,6 +28,7 @@ export default class SendTransactionScreen extends PersistentForm { history: PropTypes.object, network: PropTypes.string, primaryCurrency: PropTypes.string, + recentBlocks: PropTypes.array, selectedAddress: PropTypes.string, selectedToken: PropTypes.object, tokenBalance: PropTypes.string, @@ -43,6 +44,7 @@ export default class SendTransactionScreen extends PersistentForm { editingTransactionId, gasLimit, gasPrice, + recentBlocks, selectedAddress, selectedToken = {}, updateAndSetGasTotal, @@ -53,6 +55,7 @@ export default class SendTransactionScreen extends PersistentForm { editingTransactionId, gasLimit, gasPrice, + recentBlocks, selectedAddress, selectedToken, }) diff --git a/ui/app/components/send_/send.constants.js b/ui/app/components/send_/send.constants.js index b59fcaaf0..e911a5510 100644 --- a/ui/app/components/send_/send.constants.js +++ b/ui/app/components/send_/send.constants.js @@ -28,6 +28,13 @@ const NEGATIVE_ETH_ERROR = 'negativeETH' const INVALID_RECIPIENT_ADDRESS_ERROR = 'invalidAddressRecipient' const REQUIRED_ERROR = 'required' +const ONE_GWEI_IN_WEI_HEX = ethUtil.addHexPrefix(conversionUtil('0x1', { + fromDenomination: 'GWEI', + toDenomination: 'WEI', + fromNumericBase: 'hex', + toNumericBase: 'hex', +})) + module.exports = { INSUFFICIENT_FUNDS_ERROR, INSUFFICIENT_TOKENS_ERROR, @@ -39,6 +46,7 @@ module.exports = { MIN_GAS_PRICE_HEX, MIN_GAS_TOTAL, NEGATIVE_ETH_ERROR, + ONE_GWEI_IN_WEI_HEX, REQUIRED_ERROR, TOKEN_TRANSFER_FUNCTION_SIGNATURE, } diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js index df28caca8..879aef87c 100644 --- a/ui/app/components/send_/send.container.js +++ b/ui/app/components/send_/send.container.js @@ -10,6 +10,7 @@ import { getGasPrice, getGasTotal, getPrimaryCurrency, + getRecentBlocks, getSelectedAddress, getSelectedToken, getSelectedTokenContract, @@ -53,6 +54,7 @@ function mapStateToProps (state) { gasTotal: getGasTotal(state), network: getCurrentNetwork(state), primaryCurrency: getPrimaryCurrency(state), + recentBlocks: getRecentBlocks(state), selectedAddress: getSelectedAddress(state), selectedToken: getSelectedToken(state), tokenBalance: getTokenBalance(state), @@ -68,12 +70,12 @@ function mapDispatchToProps (dispatch) { editingTransactionId, gasLimit, gasPrice, + recentBlocks, selectedAddress, selectedToken, }) => { - console.log(`editingTransactionId`, editingTransactionId) !editingTransactionId - ? dispatch(updateGasData({ selectedAddress, selectedToken, data })) + ? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, data })) : dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice))) }, updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => { diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js index c5ae1ab7f..850328e10 100644 --- a/ui/app/components/send_/send.selectors.js +++ b/ui/app/components/send_/send.selectors.js @@ -3,6 +3,9 @@ const abi = require('human-standard-token-abi') const { multiplyCurrencies, } = require('../../conversion-util') +const { + estimateGasPriceFromRecentBlocks, +} = require('./send.utils') const selectors = { accountsWithSendEtherInfoSelector, @@ -18,8 +21,10 @@ const selectors = { getForceGasMin, getGasLimit, getGasPrice, + getGasPriceFromRecentBlocks, getGasTotal, getPrimaryCurrency, + getRecentBlocks, getSelectedAccount, getSelectedAddress, getSelectedIdentity, @@ -124,6 +129,10 @@ function getGasPrice (state) { return state.metamask.send.gasPrice } +function getGasPriceFromRecentBlocks (state) { + return estimateGasPriceFromRecentBlocks(state.metamask.recentBlocks) +} + function getGasTotal (state) { return state.metamask.send.gasTotal } @@ -133,6 +142,10 @@ function getPrimaryCurrency (state) { return selectedToken && selectedToken.symbol } +function getRecentBlocks (state) { + return state.metamask.recentBlocks +} + function getSelectedAccount (state) { const accounts = state.metamask.accounts const selectedAddress = getSelectedAddress(state) diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 1c7fd2b42..6055c98b1 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -12,12 +12,15 @@ const { INSUFFICIENT_FUNDS_ERROR, INSUFFICIENT_TOKENS_ERROR, NEGATIVE_ETH_ERROR, + ONE_GWEI_IN_WEI_HEX, } = require('./send.constants') const abi = require('ethereumjs-abi') module.exports = { calcGasTotal, doesAmountErrorRequireUpdate, + estimateGas, + estimateGasPriceFromRecentBlocks, generateTokenTransferData, getAmountErrorObject, getParamsForGasEstimate, @@ -179,6 +182,17 @@ function doesAmountErrorRequireUpdate ({ return amountErrorRequiresUpdate } +function estimateGas (params = {}) { + return new Promise((resolve, reject) => { + global.ethQuery.estimateGas(params, (err, data) => { + if (err) { + return reject(err) + } + return resolve(data) + }) + }) +} + function generateTokenTransferData (selectedAddress, selectedToken) { if (!selectedToken) return console.log(`abi.rawEncode`, abi.rawEncode) @@ -187,3 +201,26 @@ function generateTokenTransferData (selectedAddress, selectedToken) { x => ('00' + x.toString(16)).slice(-2) ).join('') } + +function hexComparator (a, b) { + return conversionGreaterThan( + { value: a, fromNumericBase: 'hex' }, + { value: b, fromNumericBase: 'hex' }, + ) ? 1 : -1 +} + +function estimateGasPriceFromRecentBlocks (recentBlocks) { + // Return 1 gwei if no blocks have been observed: + if (!recentBlocks || recentBlocks.length === 0) { + return ONE_GWEI_IN_WEI_HEX + } + const lowestPrices = recentBlocks.map((block) => { + if (!block.gasPrices || block.gasPrices.length < 1) { + return ONE_GWEI_IN_WEI_HEX + } + return block.gasPrices + .sort(hexComparator)[0] + }) + .sort(hexComparator) + return lowestPrices[Math.floor(lowestPrices.length / 2)] +} diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js index 4aa1978e4..780ee1046 100644 --- a/ui/app/components/send_/tests/send-component.test.js +++ b/ui/app/components/send_/tests/send-component.test.js @@ -42,6 +42,7 @@ describe.only('Send Component', function () { history={{ mockProp: 'history-abc'}} network={'3'} primaryCurrency={'mockPrimaryCurrency'} + recentBlocks={['mockBlock']} selectedAddress={'mockSelectedAddress'} selectedToken={'mockSelectedToken'} tokenBalance={'mockTokenBalance'} @@ -211,6 +212,7 @@ describe.only('Send Component', function () { editingTransactionId: 'mockEditingTransactionId', gasLimit: 'mockGasLimit', gasPrice: 'mockGasPrice', + recentBlocks: ['mockBlock'], selectedAddress: 'mockSelectedAddress', selectedToken: 'mockSelectedToken', } diff --git a/ui/app/components/send_/tests/send-container.test.js b/ui/app/components/send_/tests/send-container.test.js index e589cca05..2129709c1 100644 --- a/ui/app/components/send_/tests/send-container.test.js +++ b/ui/app/components/send_/tests/send-container.test.js @@ -32,6 +32,7 @@ proxyquire('../send.container.js', { getGasPrice: (s) => `mockGasPrice:${s}`, getGasTotal: (s) => `mockGasTotal:${s}`, getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`, + getRecentBlocks: (s) => `mockRecentBlocks:${s}`, getSelectedAddress: (s) => `mockSelectedAddress:${s}`, getSelectedToken: (s) => `mockSelectedToken:${s}`, getSelectedTokenContract: (s) => `mockTokenContract:${s}`, @@ -66,6 +67,7 @@ describe('send container', () => { gasTotal: 'mockGasTotal:mockState', network: 'mockNetwork:mockState', primaryCurrency: 'mockPrimaryCurrency:mockState', + recentBlocks: 'mockRecentBlocks:mockState', selectedAddress: 'mockSelectedAddress:mockState', selectedToken: 'mockSelectedToken:mockState', tokenBalance: 'mockTokenBalance:mockState', @@ -91,6 +93,7 @@ describe('send container', () => { editingTransactionId: '0x2', gasLimit: '0x3', gasPrice: '0x4', + recentBlocks: ['mockBlock'], selectedAddress: '0x4', selectedToken: { address: '0x1' }, } @@ -105,14 +108,14 @@ describe('send container', () => { }) it('should dispatch an updateGasData action when editingTransactionId is falsy', () => { - const { selectedAddress, selectedToken, data } = mockProps + const { selectedAddress, selectedToken, data, recentBlocks } = mockProps mapDispatchToPropsObject.updateAndSetGasTotal( - Object.assign(mockProps, {editingTransactionId: false}) + Object.assign({}, mockProps, {editingTransactionId: false}) ) assert(dispatchSpy.calledOnce) assert.deepEqual( actionSpies.updateGasData.getCall(0).args[0], - { selectedAddress, selectedToken, data } + { selectedAddress, selectedToken, data, recentBlocks } ) }) }) diff --git a/ui/app/components/send_/tests/send-selectors-test-data.js b/ui/app/components/send_/tests/send-selectors-test-data.js index ecfe9022f..a1423675d 100644 --- a/ui/app/components/send_/tests/send-selectors-test-data.js +++ b/ui/app/components/send_/tests/send-selectors-test-data.js @@ -198,6 +198,7 @@ module.exports = { }, }, 'currentLocale': 'en', + recentBlocks: ['mockBlock1', 'mockBlock2', 'mockBlock3'], }, 'appState': { 'menuOpen': false, diff --git a/ui/app/components/send_/tests/send-selectors.test.js b/ui/app/components/send_/tests/send-selectors.test.js index 977fe2a47..22e45afdb 100644 --- a/ui/app/components/send_/tests/send-selectors.test.js +++ b/ui/app/components/send_/tests/send-selectors.test.js @@ -17,6 +17,7 @@ const { getGasPrice, getGasTotal, getPrimaryCurrency, + getRecentBlocks, getSelectedAccount, getSelectedAddress, getSelectedIdentity, @@ -239,6 +240,15 @@ describe('send selectors', () => { }) }) + describe('getRecentBlocks()', () => { + it('should return the recent blocks', () => { + assert.deepEqual( + getRecentBlocks(mockState), + ['mockBlock1', 'mockBlock2', 'mockBlock3'] + ) + }) + }) + describe('getSelectedAccount()', () => { it('should return the currently selected account', () => { assert.deepEqual( diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index 903b531e6..b5211a63d 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -1,6 +1,13 @@ import assert from 'assert' import sinon from 'sinon' import proxyquire from 'proxyquire' +import { + ONE_GWEI_IN_WEI_HEX, +} from '../send.constants' +const { + addCurrencies, + subtractCurrencies, +} = require('../../../conversion-util') const { INSUFFICIENT_FUNDS_ERROR, @@ -31,7 +38,9 @@ const sendUtils = proxyquire('../send.utils.js', { const { calcGasTotal, + estimateGas, doesAmountErrorRequireUpdate, + estimateGasPriceFromRecentBlocks, generateTokenTransferData, getAmountErrorObject, getParamsForGasEstimate, @@ -261,4 +270,101 @@ describe('send utils', () => { }) }) + describe('estimateGas', () => { + let tempEthQuery + beforeEach(() => { + tempEthQuery = global.ethQuery + global.ethQuery = { + estimateGas: sinon.stub().callsFake((data, cb) => { + return cb( + data.isMockErr ? 'mockErr' : null, + Object.assign(data, { estimateGasCalled: true }) + ) + }) + } + }) + + afterEach(() => { + global.ethQuery = tempEthQuery + }) + + it('should call ethQuery.estimateGas and resolve that call\'s data', async () => { + const result = await estimateGas({ mockParam: 'someData' }) + assert.equal(global.ethQuery.estimateGas.callCount, 1) + assert.deepEqual( + result, + { mockParam: 'someData', estimateGasCalled: true } + ) + }) + + it('should reject with ethQuery.estimateGas error', async () => { + try { + await estimateGas({ mockParam: 'someData', isMockErr: true }) + } catch (err) { + assert.equal(err, 'mockErr') + } + }) + }) + + describe('estimateGasPriceFromRecentBlocks', () => { + const ONE_GWEI_IN_WEI_HEX_PLUS_ONE = addCurrencies(ONE_GWEI_IN_WEI_HEX, '0x1', { + aBase: 16, + bBase: 16, + toNumericBase: 'hex', + }) + const ONE_GWEI_IN_WEI_HEX_PLUS_TWO = addCurrencies(ONE_GWEI_IN_WEI_HEX, '0x2', { + aBase: 16, + bBase: 16, + toNumericBase: 'hex', + }) + const ONE_GWEI_IN_WEI_HEX_MINUS_ONE = subtractCurrencies(ONE_GWEI_IN_WEI_HEX, '0x1', { + aBase: 16, + bBase: 16, + toNumericBase: 'hex', + }) + + it(`should return ${ONE_GWEI_IN_WEI_HEX} if recentBlocks is falsy`, () => { + assert.equal(estimateGasPriceFromRecentBlocks(), ONE_GWEI_IN_WEI_HEX) + }) + + it(`should return ${ONE_GWEI_IN_WEI_HEX} if recentBlocks is empty`, () => { + assert.equal(estimateGasPriceFromRecentBlocks([]), ONE_GWEI_IN_WEI_HEX) + }) + + it(`should estimate a block's gasPrice as ${ONE_GWEI_IN_WEI_HEX} if it has no gas prices`, () => { + const mockRecentBlocks = [ + { gasPrices: null }, + { gasPrices: [ ONE_GWEI_IN_WEI_HEX_PLUS_ONE ] }, + { gasPrices: [ ONE_GWEI_IN_WEI_HEX_MINUS_ONE ] }, + ] + assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), ONE_GWEI_IN_WEI_HEX) + }) + + it(`should estimate a block's gasPrice as ${ONE_GWEI_IN_WEI_HEX} if it has empty gas prices`, () => { + const mockRecentBlocks = [ + { gasPrices: [] }, + { gasPrices: [ ONE_GWEI_IN_WEI_HEX_PLUS_ONE ] }, + { gasPrices: [ ONE_GWEI_IN_WEI_HEX_MINUS_ONE ] }, + ] + assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), ONE_GWEI_IN_WEI_HEX) + }) + + it(`should return the middle value of all blocks lowest prices`, () => { + const mockRecentBlocks = [ + { gasPrices: [ ONE_GWEI_IN_WEI_HEX_PLUS_TWO ] }, + { gasPrices: [ ONE_GWEI_IN_WEI_HEX_MINUS_ONE ] }, + { gasPrices: [ ONE_GWEI_IN_WEI_HEX_PLUS_ONE ] }, + ] + assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), ONE_GWEI_IN_WEI_HEX_PLUS_ONE) + }) + + it(`should work if a block has multiple gas prices`, () => { + const mockRecentBlocks = [ + { gasPrices: [ '0x1', '0x2', '0x3', '0x4', '0x5' ] }, + { gasPrices: [ '0x101', '0x100', '0x103', '0x104', '0x102' ] }, + { gasPrices: [ '0x150', '0x50', '0x100', '0x200', '0x5' ] }, + ] + assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), '0x5') + }) + }) }) -- cgit From 4f0b4eef5030575e8ebdf35ca19fbc77376c70b9 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 22 May 2018 12:46:53 -0230 Subject: Estimate gas using same algorithm as backend. --- ui/app/actions.js | 19 +++- ui/app/components/send_/send.component.js | 3 + ui/app/components/send_/send.constants.js | 3 + ui/app/components/send_/send.container.js | 5 +- ui/app/components/send_/send.selectors.js | 5 + ui/app/components/send_/send.utils.js | 65 ++++++----- .../components/send_/tests/send-component.test.js | 2 + .../components/send_/tests/send-container.test.js | 7 +- .../send_/tests/send-selectors-test-data.js | 1 + .../components/send_/tests/send-selectors.test.js | 10 ++ ui/app/components/send_/tests/send-utils.test.js | 126 +++++++++++---------- ui/app/conversion-util.js | 5 +- 12 files changed, 153 insertions(+), 98 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 70ec3aed8..7a18b1c00 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -4,7 +4,6 @@ const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url') const { getTokenAddressFromTokenObject } = require('./util') const { calcGasTotal, - getParamsForGasEstimate, calcTokenBalance, estimateGas, estimateGasPriceFromRecentBlocks, @@ -725,12 +724,24 @@ function setGasTotal (gasTotal) { } } -function updateGasData ({ recentBlocks, selectedAddress, selectedToken, data }) { +function updateGasData ({ + blockGasLimit, + data, + recentBlocks, + selectedAddress, + selectedToken, + to, +}) { return (dispatch) => { - const estimateGasParams = getParamsForGasEstimate(selectedAddress, selectedToken, data) return Promise.all([ Promise.resolve(estimateGasPriceFromRecentBlocks(recentBlocks)), - estimateGas(estimateGasParams), + estimateGas({ + blockGasLimit, + data, + selectedAddress, + selectedToken, + to, + }), ]) .then(([gasPrice, gas]) => { dispatch(actions.setGasPrice(gasPrice)) diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index 438923ab3..0f82d3f19 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -18,6 +18,7 @@ export default class SendTransactionScreen extends PersistentForm { PropTypes.string, PropTypes.number, ]), + blockGasLimit: PropTypes.string, conversionRate: PropTypes.number, data: PropTypes.string, editingTransactionId: PropTypes.string, @@ -40,6 +41,7 @@ export default class SendTransactionScreen extends PersistentForm { updateGas () { const { + blockGasLimit, data, editingTransactionId, gasLimit, @@ -51,6 +53,7 @@ export default class SendTransactionScreen extends PersistentForm { } = this.props updateAndSetGasTotal({ + blockGasLimit, data, editingTransactionId, gasLimit, diff --git a/ui/app/components/send_/send.constants.js b/ui/app/components/send_/send.constants.js index e911a5510..df5dee371 100644 --- a/ui/app/components/send_/send.constants.js +++ b/ui/app/components/send_/send.constants.js @@ -35,6 +35,8 @@ const ONE_GWEI_IN_WEI_HEX = ethUtil.addHexPrefix(conversionUtil('0x1', { toNumericBase: 'hex', })) +const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send. + module.exports = { INSUFFICIENT_FUNDS_ERROR, INSUFFICIENT_TOKENS_ERROR, @@ -48,5 +50,6 @@ module.exports = { NEGATIVE_ETH_ERROR, ONE_GWEI_IN_WEI_HEX, REQUIRED_ERROR, + SIMPLE_GAS_COST, TOKEN_TRANSFER_FUNCTION_SIGNATURE, } diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js index 879aef87c..3b72a3a5a 100644 --- a/ui/app/components/send_/send.container.js +++ b/ui/app/components/send_/send.container.js @@ -4,6 +4,7 @@ import { withRouter } from 'react-router-dom' import { compose } from 'recompose' import { getAmountConversionRate, + getBlockGasLimit, getConversionRate, getCurrentNetwork, getGasLimit, @@ -45,6 +46,7 @@ function mapStateToProps (state) { return { amount: getSendAmount(state), amountConversionRate: getAmountConversionRate(state), + blockGasLimit: getBlockGasLimit(state), conversionRate: getConversionRate(state), data: generateTokenTransferData(selectedAddress, selectedToken), editingTransactionId: getSendEditingTransactionId(state), @@ -66,6 +68,7 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return { updateAndSetGasTotal: ({ + blockGasLimit, data, editingTransactionId, gasLimit, @@ -75,7 +78,7 @@ function mapDispatchToProps (dispatch) { selectedToken, }) => { !editingTransactionId - ? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, data })) + ? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, data, blockGasLimit })) : dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice))) }, updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => { diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js index 850328e10..7e7cfe2e9 100644 --- a/ui/app/components/send_/send.selectors.js +++ b/ui/app/components/send_/send.selectors.js @@ -12,6 +12,7 @@ const selectors = { // autoAddToBetaUI, getAddressBook, getAmountConversionRate, + getBlockGasLimit, getConversionRate, getConvertedCurrency, getCurrentAccountWithSendEtherInfo, @@ -89,6 +90,10 @@ function getAmountConversionRate (state) { : getConversionRate(state) } +function getBlockGasLimit (state) { + return state.metamask.currentBlockGasLimit +} + function getConversionRate (state) { return state.metamask.conversionRate } diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 6055c98b1..4c731c91b 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -13,18 +13,19 @@ const { INSUFFICIENT_TOKENS_ERROR, NEGATIVE_ETH_ERROR, ONE_GWEI_IN_WEI_HEX, + SIMPLE_GAS_COST, } = require('./send.constants') +const EthQuery = require('ethjs-query') const abi = require('ethereumjs-abi') module.exports = { calcGasTotal, + calcTokenBalance, doesAmountErrorRequireUpdate, estimateGas, estimateGasPriceFromRecentBlocks, generateTokenTransferData, getAmountErrorObject, - getParamsForGasEstimate, - calcTokenBalance, isBalanceSufficient, isTokenBalanceSufficient, } @@ -142,24 +143,6 @@ function getAmountErrorObject ({ return { amount: amountError } } -function getParamsForGasEstimate (selectedAddress, selectedToken, data) { - const { symbol } = selectedToken || {} - const estimatedGasParams = { - from: selectedAddress, - gas: '746a528800', - } - - if (symbol) { - Object.assign(estimatedGasParams, { value: '0x0' }) - } - - if (data) { - Object.assign(estimatedGasParams, { data }) - } - - return estimatedGasParams -} - function calcTokenBalance ({ selectedToken, usersToken }) { const { decimals } = selectedToken || {} return calcTokenAmount(usersToken.balance.toString(), decimals) + '' @@ -182,15 +165,40 @@ function doesAmountErrorRequireUpdate ({ return amountErrorRequiresUpdate } -function estimateGas (params = {}) { - return new Promise((resolve, reject) => { - global.ethQuery.estimateGas(params, (err, data) => { - if (err) { - return reject(err) - } - return resolve(data) - }) +async function estimateGas ({ selectedAddress, selectedToken, data, blockGasLimit, to }) { + const ethQuery = new EthQuery(global.ethereumProvider) + const { symbol } = selectedToken || {} + const estimatedGasParams = { from: selectedAddress } + + if (symbol) { + Object.assign(estimatedGasParams, { value: '0x0' }) + } + + if (data) { + Object.assign(estimatedGasParams, { data }) + } + // if recipient has no code, gas is 21k max: + const hasRecipient = Boolean(to) + let code + if (hasRecipient) code = await ethQuery.getCode(to) + + if (hasRecipient && (!code || code === '0x')) { + return SIMPLE_GAS_COST + } + + estimatedGasParams.to = to + + // if not, fall back to block gasLimit + estimatedGasParams.gas = multiplyCurrencies(blockGasLimit, 0.95, { + multiplicandBase: 16, + multiplierBase: 10, + roundDown: '0', + toNumericBase: 'hex', }) + + // run tx + const estimatedGas = await ethQuery.estimateGas(estimatedGasParams) + return estimatedGas.toString(16) } function generateTokenTransferData (selectedAddress, selectedToken) { @@ -222,5 +230,6 @@ function estimateGasPriceFromRecentBlocks (recentBlocks) { .sort(hexComparator)[0] }) .sort(hexComparator) + return lowestPrices[Math.floor(lowestPrices.length / 2)] } diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js index 780ee1046..3abff0d23 100644 --- a/ui/app/components/send_/tests/send-component.test.js +++ b/ui/app/components/send_/tests/send-component.test.js @@ -32,6 +32,7 @@ describe.only('Send Component', function () { wrapper = shallow( () => arg2() }, './send.selectors': { getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`, + getBlockGasLimit: (s) => `mockBlockGasLimit:${s}`, getConversionRate: (s) => `mockConversionRate:${s}`, getCurrentNetwork: (s) => `mockNetwork:${s}`, getGasLimit: (s) => `mockGasLimit:${s}`, @@ -58,6 +59,7 @@ describe('send container', () => { assert.deepEqual(mapStateToProps('mockState'), { amount: 'mockAmount:mockState', amountConversionRate: 'mockAmountConversionRate:mockState', + blockGasLimit: 'mockBlockGasLimit:mockState', conversionRate: 'mockConversionRate:mockState', data: 'mockData:mockSelectedAddress:mockStatemockSelectedToken:mockState', editingTransactionId: 'mockEditingTransactionId:mockState', @@ -89,6 +91,7 @@ describe('send container', () => { describe('updateAndSetGasTotal()', () => { const mockProps = { + blockGasLimit: 'mockBlockGasLimit', data: '0x1', editingTransactionId: '0x2', gasLimit: '0x3', @@ -108,14 +111,14 @@ describe('send container', () => { }) it('should dispatch an updateGasData action when editingTransactionId is falsy', () => { - const { selectedAddress, selectedToken, data, recentBlocks } = mockProps + const { selectedAddress, selectedToken, data, recentBlocks, blockGasLimit } = mockProps mapDispatchToPropsObject.updateAndSetGasTotal( Object.assign({}, mockProps, {editingTransactionId: false}) ) assert(dispatchSpy.calledOnce) assert.deepEqual( actionSpies.updateGasData.getCall(0).args[0], - { selectedAddress, selectedToken, data, recentBlocks } + { selectedAddress, selectedToken, data, recentBlocks, blockGasLimit } ) }) }) diff --git a/ui/app/components/send_/tests/send-selectors-test-data.js b/ui/app/components/send_/tests/send-selectors-test-data.js index a1423675d..8f9c19314 100644 --- a/ui/app/components/send_/tests/send-selectors-test-data.js +++ b/ui/app/components/send_/tests/send-selectors-test-data.js @@ -22,6 +22,7 @@ module.exports = { 'name': 'Send Account 4', }, }, + 'currentBlockGasLimit': '0x4c1878', 'currentCurrency': 'USD', 'conversionRate': 1200.88200327, 'conversionDate': 1489013762, diff --git a/ui/app/components/send_/tests/send-selectors.test.js b/ui/app/components/send_/tests/send-selectors.test.js index 22e45afdb..152af8059 100644 --- a/ui/app/components/send_/tests/send-selectors.test.js +++ b/ui/app/components/send_/tests/send-selectors.test.js @@ -5,6 +5,7 @@ const { accountsWithSendEtherInfoSelector, // autoAddToBetaUI, getAddressBook, + getBlockGasLimit, getAmountConversionRate, getConversionRate, getConvertedCurrency, @@ -135,6 +136,15 @@ describe('send selectors', () => { }) }) + describe('getBlockGasLimit', () => { + it('should return the current block gas limit', () => { + assert.deepEqual( + getBlockGasLimit(mockState), + '0x4c1878' + ) + }) + }) + describe('getConversionRate()', () => { it('should return the eth conversion rate', () => { assert.deepEqual( diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index b5211a63d..a01ab4eba 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -3,6 +3,7 @@ import sinon from 'sinon' import proxyquire from 'proxyquire' import { ONE_GWEI_IN_WEI_HEX, + SIMPLE_GAS_COST, } from '../send.constants' const { addCurrencies, @@ -18,11 +19,19 @@ const stubs = { addCurrencies: sinon.stub().callsFake((a, b, obj) => a + b), conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)), conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value > obj2.value), - multiplyCurrencies: sinon.stub().callsFake((a, b) => a * b), + multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`), calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d), rawEncode: sinon.stub().returns([16, 1100]), } +const EthQuery = function () {} +EthQuery.prototype.estimateGas = sinon.stub().callsFake( + (data) => Promise.resolve({ toString: (n) => `mockToString:${n}` }) +) +EthQuery.prototype.getCode = sinon.stub().callsFake( + (address) => Promise.resolve(address.match(/isContract/) ? 'not-0x' : '0x') +) + const sendUtils = proxyquire('../send.utils.js', { '../../conversion-util': { addCurrencies: stubs.addCurrencies, @@ -34,6 +43,7 @@ const sendUtils = proxyquire('../send.utils.js', { 'ethereumjs-abi': { rawEncode: stubs.rawEncode, }, + 'ethjs-query': EthQuery, }) const { @@ -43,7 +53,6 @@ const { estimateGasPriceFromRecentBlocks, generateTokenTransferData, getAmountErrorObject, - getParamsForGasEstimate, calcTokenBalance, isBalanceSufficient, isTokenBalanceSufficient, @@ -54,7 +63,7 @@ describe('send utils', () => { describe('calcGasTotal()', () => { it('should call multiplyCurrencies with the correct params and return the multiplyCurrencies return', () => { const result = calcGasTotal(12, 15) - assert.equal(result, 180) + assert.equal(result, '12x15') const call_ = stubs.multiplyCurrencies.getCall(0).args assert.deepEqual( call_, @@ -145,41 +154,6 @@ describe('send utils', () => { }) }) - describe('getParamsForGasEstimate()', () => { - it('should return from and gas properties if no symbol or data', () => { - assert.deepEqual( - getParamsForGasEstimate('mockAddress'), - { - from: 'mockAddress', - gas: '746a528800', - } - ) - }) - - it('should return value property if selected token provided', () => { - assert.deepEqual( - getParamsForGasEstimate('mockAddress', { symbol: 'ABC' }), - { - from: 'mockAddress', - gas: '746a528800', - value: '0x0', - } - ) - }) - - it('should return data property if data provided', () => { - assert.deepEqual( - getParamsForGasEstimate('mockAddress', { symbol: 'ABC' }, 'somedata'), - { - from: 'mockAddress', - gas: '746a528800', - value: '0x0', - data: 'somedata', - } - ) - }) - }) - describe('calcTokenBalance()', () => { it('should return the calculated token blance', () => { assert.equal(calcTokenBalance({ @@ -271,38 +245,66 @@ describe('send utils', () => { }) describe('estimateGas', () => { - let tempEthQuery - beforeEach(() => { - tempEthQuery = global.ethQuery - global.ethQuery = { - estimateGas: sinon.stub().callsFake((data, cb) => { - return cb( - data.isMockErr ? 'mockErr' : null, - Object.assign(data, { estimateGasCalled: true }) - ) - }) - } - }) + const baseMockParams = { + blockGasLimit: '0x64', + selectedAddress: 'mockAddress', + to: '0xisContract', + } + const baseExpectedCall = { + from: 'mockAddress', + gas: '0x64x0.95', + to: '0xisContract', + } afterEach(() => { - global.ethQuery = tempEthQuery + EthQuery.prototype.estimateGas.resetHistory() + EthQuery.prototype.getCode.resetHistory() + }) + + it('should call ethQuery.estimateGas with the expected params', async () => { + const result = await estimateGas(baseMockParams) + assert.equal(EthQuery.prototype.estimateGas.callCount, 1) + assert.deepEqual( + EthQuery.prototype.estimateGas.getCall(0).args[0], + baseExpectedCall + ) + assert.equal(result, 'mockToString:16') + }) + + it('should call ethQuery.estimateGas with a value of 0x0 if the passed selectedToken has a symbol', async () => { + const result = await estimateGas(Object.assign({ selectedToken: { symbol: true } }, baseMockParams)) + assert.equal(EthQuery.prototype.estimateGas.callCount, 1) + assert.deepEqual( + EthQuery.prototype.estimateGas.getCall(0).args[0], + Object.assign({ value: '0x0' }, baseExpectedCall) + ) + assert.equal(result, 'mockToString:16') + }) + + it('should call ethQuery.estimateGas with data if data is passed', async () => { + const result = await estimateGas(Object.assign({ data: 'mockData' }, baseMockParams)) + assert.equal(EthQuery.prototype.estimateGas.callCount, 1) + assert.deepEqual( + EthQuery.prototype.estimateGas.getCall(0).args[0], + Object.assign({ data: 'mockData' }, baseExpectedCall) + ) + assert.equal(result, 'mockToString:16') }) - it('should call ethQuery.estimateGas and resolve that call\'s data', async () => { - const result = await estimateGas({ mockParam: 'someData' }) - assert.equal(global.ethQuery.estimateGas.callCount, 1) + it('should call ethQuery.estimateGas with data if data is passed', async () => { + const result = await estimateGas(Object.assign({ data: 'mockData' }, baseMockParams)) + assert.equal(EthQuery.prototype.estimateGas.callCount, 1) assert.deepEqual( - result, - { mockParam: 'someData', estimateGasCalled: true } + EthQuery.prototype.estimateGas.getCall(0).args[0], + Object.assign({ data: 'mockData' }, baseExpectedCall) ) + assert.equal(result, 'mockToString:16') }) - it('should reject with ethQuery.estimateGas error', async () => { - try { - await estimateGas({ mockParam: 'someData', isMockErr: true }) - } catch (err) { - assert.equal(err, 'mockErr') - } + it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async () => { + assert.equal(EthQuery.prototype.estimateGas.callCount, 0) + const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123' })) + assert.equal(result, SIMPLE_GAS_COST) }) }) diff --git a/ui/app/conversion-util.js b/ui/app/conversion-util.js index d484ed16d..100402d95 100644 --- a/ui/app/conversion-util.js +++ b/ui/app/conversion-util.js @@ -11,7 +11,8 @@ * @param {string} [options.fromNumericBase = 'hex' | 'dec' | 'BN'] The numeric basic of the passed value. * @param {string} [options.toNumericBase = 'hex' | 'dec' | 'BN'] The desired numeric basic of the result. * @param {string} [options.fromDenomination = 'WEI'] The denomination of the passed value -* @param {number} [options.numberOfDecimals] The desired number of in the result +* @param {string} [options.numberOfDecimals] The desired number of decimals in the result +* @param {string} [options.roundDown] The desired number of decimals to round down to * @param {number} [options.conversionRate] The rate to use to make the fromCurrency -> toCurrency conversion * @returns {(number | string | BN)} * @@ -38,6 +39,7 @@ const BIG_NUMBER_GWEI_MULTIPLIER = new BigNumber('1000000000') // Individual Setters const convert = R.invoker(1, 'times') const round = R.invoker(2, 'round')(R.__, BigNumber.ROUND_HALF_DOWN) +const roundDown = R.invoker(2, 'round')(R.__, BigNumber.ROUND_DOWN) const invertConversionRate = conversionRate => () => new BigNumber(1.0).div(conversionRate) const decToBigNumberViaString = n => R.pipe(String, toBigNumber['dec']) @@ -104,6 +106,7 @@ const converter = R.pipe( whenPredSetWithPropAndSetter(fromAndToCurrencyPropsNotEqual, 'conversionRate', convert), whenPropApplySetterMap('toDenomination', toSpecifiedDenomination), whenPredSetWithPropAndSetter(R.prop('numberOfDecimals'), 'numberOfDecimals', round), + whenPredSetWithPropAndSetter(R.prop('roundDown'), 'roundDown', roundDown), whenPropApplySetterMap('toNumericBase', baseChange), R.view(R.lensProp('value')) ) -- cgit From 2eddb7b65245b7c11a3928098eea06064b9b22cf Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 22 May 2018 12:57:15 -0230 Subject: Support smaller decimals in currency-display --- ui/app/components/send/currency-display.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 52dc56cb0..6cd11f6ef 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -50,7 +50,7 @@ CurrencyDisplay.prototype.getValueToRender = function () { fromNumericBase: 'hex', toNumericBase: 'dec', fromDenomination: 'WEI', - numberOfDecimals: 6, + numberOfDecimals: 9, conversionRate, }) } -- cgit From 0f20fce9b761fc0aa16d61b2b739fa7f9b9f6a7d Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 23 May 2018 14:13:25 -0230 Subject: Auto update gas estimate when to changes. --- ui/app/actions.js | 7 ++- .../send_/send-content/send-content.component.js | 7 ++- .../send-to-row/send-to-row.component.js | 10 +++- .../send-to-row/send-to-row.container.js | 5 +- .../send-content/send-to-row/send-to-row.utils.js | 4 +- .../tests/send-to-row-component.test.js | 25 +++++++++- .../tests/send-to-row-container.test.js | 5 +- .../send_/send-footer/send-footer.utils.js | 1 - ui/app/components/send_/send.component.js | 7 ++- ui/app/components/send_/send.container.js | 4 +- ui/app/components/send_/send.utils.js | 30 ++++++------ .../components/send_/tests/send-component.test.js | 8 ++++ .../components/send_/tests/send-container.test.js | 6 ++- ui/app/components/send_/tests/send-utils.test.js | 54 +++++++++------------- 14 files changed, 109 insertions(+), 64 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 7a18b1c00..5e92583e0 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -731,16 +731,21 @@ function updateGasData ({ selectedAddress, selectedToken, to, + value, }) { + const estimatedGasPrice = estimateGasPriceFromRecentBlocks(recentBlocks) return (dispatch) => { return Promise.all([ - Promise.resolve(estimateGasPriceFromRecentBlocks(recentBlocks)), + Promise.resolve(estimatedGasPrice), estimateGas({ + estimateGasMethod: background.estimateGas, blockGasLimit, data, selectedAddress, selectedToken, to, + value, + gasPrice: estimatedGasPrice, }), ]) .then(([gasPrice, gas]) => { diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js index d610c2a3f..3a14054eb 100644 --- a/ui/app/components/send_/send-content/send-content.component.js +++ b/ui/app/components/send_/send-content/send-content.component.js @@ -1,4 +1,5 @@ import React, { Component } from 'react' +import PropTypes from 'prop-types' import PageContainerContent from '../../page-container/page-container-content.component' import SendAmountRow from './send-amount-row/' import SendFromRow from './send-from-row/' @@ -7,12 +8,16 @@ import SendToRow from './send-to-row/' export default class SendContent extends Component { + static propTypes = { + updateGas: PropTypes.func, + }; + render () { return (
- + this.props.updateGas(updateData)} />
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js index 901ae97e9..0a83186a5 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SendRowWrapper from '../send-row-wrapper/' import EnsInput from '../../../ens-input' +import { getToErrorObject } from './send-to-row.utils.js' export default class SendToRow extends Component { @@ -13,14 +14,19 @@ export default class SendToRow extends Component { to: PropTypes.string, toAccounts: PropTypes.array, toDropdownOpen: PropTypes.bool, + updateGas: PropTypes.func, updateSendTo: PropTypes.func, updateSendToError: PropTypes.func, }; handleToChange (to, nickname = '') { - const { updateSendTo, updateSendToError } = this.props + const { updateSendTo, updateSendToError, updateGas } = this.props + const toErrorObject = getToErrorObject(to) updateSendTo(to, nickname) - updateSendToError(to) + updateSendToError(toErrorObject) + if (toErrorObject.to === null) { + updateGas({ to }) + } } render () { diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js index a10da505a..1c9c9d518 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js @@ -8,7 +8,6 @@ import { getToDropdownOpen, sendToIsInError, } from './send-to-row.selectors.js' -import { getToErrorObject } from './send-to-row.utils.js' import { updateSendTo, } from '../../../../actions' @@ -36,8 +35,8 @@ function mapDispatchToProps (dispatch) { closeToDropdown: () => dispatch(closeToDropdown()), openToDropdown: () => dispatch(openToDropdown()), updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)), - updateSendToError: (to) => { - dispatch(updateSendErrors(getToErrorObject(to))) + updateSendToError: (toErrorObject) => { + dispatch(updateSendErrors(toErrorObject)) }, } } diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js index 22e2e1f34..cea51ee20 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js @@ -8,9 +8,9 @@ function getToErrorObject (to) { let toError = null if (!to) { - toError = REQUIRED_ERROR + toError = REQUIRED_ERROR } else if (!isValidAddress(to)) { - toError = INVALID_RECIPIENT_ADDRESS_ERROR + toError = INVALID_RECIPIENT_ADDRESS_ERROR } return { to: toError } diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js index e58695210..58fe51dcf 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js @@ -2,7 +2,15 @@ import React from 'react' import assert from 'assert' import { shallow } from 'enzyme' import sinon from 'sinon' -import SendToRow from '../send-to-row.component.js' +import proxyquire from 'proxyquire' + +const SendToRow = proxyquire('../send-to-row.component.js', { + './send-to-row.utils.js': { + getToErrorObject: (to) => ({ + to: to === false ? null : `mockToErrorObject:${to}`, + }), + }, +}).default import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' import EnsInput from '../../../../ens-input' @@ -10,6 +18,7 @@ import EnsInput from '../../../../ens-input' const propsMethodSpies = { closeToDropdown: sinon.spy(), openToDropdown: sinon.spy(), + updateGas: sinon.spy(), updateSendTo: sinon.spy(), updateSendToError: sinon.spy(), } @@ -29,6 +38,7 @@ describe('SendToRow Component', function () { to={'mockTo'} toAccounts={['mockAccount']} toDropdownOpen={false} + updateGas={propsMethodSpies.updateGas} updateSendTo={propsMethodSpies.updateSendTo} updateSendToError={propsMethodSpies.updateSendToError} />, { context: { t: str => str + '_t' } }) @@ -61,10 +71,21 @@ describe('SendToRow Component', function () { assert.equal(propsMethodSpies.updateSendToError.callCount, 1) assert.deepEqual( propsMethodSpies.updateSendToError.getCall(0).args, - ['mockTo2'] + [{ to: 'mockToErrorObject:mockTo2' }] ) }) + it('should not call updateGas if there is a to error', () => { + assert.equal(propsMethodSpies.updateGas.callCount, 0) + instance.handleToChange('mockTo2') + assert.equal(propsMethodSpies.updateGas.callCount, 0) + }) + + it('should call updateGas if there is no to error', () => { + assert.equal(propsMethodSpies.updateGas.callCount, 0) + instance.handleToChange(false) + assert.equal(propsMethodSpies.updateGas.callCount, 1) + }) }) describe('render', () => { diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js index 433b242b2..92355c00a 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js @@ -31,7 +31,6 @@ proxyquire('../send-to-row.container.js', { getToDropdownOpen: (s) => `mockToDropdownOpen:${s}`, sendToIsInError: (s) => `mockInError:${s}`, }, - './send-to-row.utils.js': { getToErrorObject: (t) => `mockError:${t}` }, '../../../../actions': actionSpies, '../../../../ducks/send.duck': duckActionSpies, }) @@ -99,12 +98,12 @@ describe('send-to-row container', () => { describe('updateSendToError()', () => { it('should dispatch an action', () => { - mapDispatchToPropsObject.updateSendToError('mockTo') + mapDispatchToPropsObject.updateSendToError('mockToErrorObject') assert(dispatchSpy.calledOnce) assert(duckActionSpies.updateSendErrors.calledOnce) assert.equal( duckActionSpies.updateSendErrors.getCall(0).args[0], - 'mockError:mockTo' + 'mockToErrorObject' ) }) }) diff --git a/ui/app/components/send_/send-footer/send-footer.utils.js b/ui/app/components/send_/send-footer/send-footer.utils.js index d5639629d..875e7d948 100644 --- a/ui/app/components/send_/send-footer/send-footer.utils.js +++ b/ui/app/components/send_/send-footer/send-footer.utils.js @@ -42,7 +42,6 @@ function constructUpdatedTx ({ } if (selectedToken) { - console.log(`ethAbi.rawEncode`, ethAbi.rawEncode) const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call( ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]), x => ('00' + x.toString(16)).slice(-2) diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index 0f82d3f19..97c6d1294 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -39,8 +39,9 @@ export default class SendTransactionScreen extends PersistentForm { updateSendTokenBalance: PropTypes.func, }; - updateGas () { + updateGas ({ to } = {}) { const { + amount, blockGasLimit, data, editingTransactionId, @@ -61,6 +62,8 @@ export default class SendTransactionScreen extends PersistentForm { recentBlocks, selectedAddress, selectedToken, + to: to && to.toLowerCase(), + value: amount, }) } @@ -147,7 +150,7 @@ export default class SendTransactionScreen extends PersistentForm { return (
- + this.updateGas(updateData)}/>
) diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js index 3b72a3a5a..7e241aa2d 100644 --- a/ui/app/components/send_/send.container.js +++ b/ui/app/components/send_/send.container.js @@ -76,9 +76,11 @@ function mapDispatchToProps (dispatch) { recentBlocks, selectedAddress, selectedToken, + to, + value, }) => { !editingTransactionId - ? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, data, blockGasLimit })) + ? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, data, blockGasLimit, to, value })) : dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice))) }, updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => { diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 4c731c91b..750411908 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -15,8 +15,8 @@ const { ONE_GWEI_IN_WEI_HEX, SIMPLE_GAS_COST, } = require('./send.constants') -const EthQuery = require('ethjs-query') const abi = require('ethereumjs-abi') +const ethUtil = require('ethereumjs-util') module.exports = { calcGasTotal, @@ -165,40 +165,44 @@ function doesAmountErrorRequireUpdate ({ return amountErrorRequiresUpdate } -async function estimateGas ({ selectedAddress, selectedToken, data, blockGasLimit, to }) { - const ethQuery = new EthQuery(global.ethereumProvider) +async function estimateGas ({ selectedAddress, selectedToken, data, blockGasLimit, to, value, gasPrice, estimateGasMethod }) { const { symbol } = selectedToken || {} - const estimatedGasParams = { from: selectedAddress } + const paramsForGasEstimate = { from: selectedAddress, value, gasPrice } if (symbol) { - Object.assign(estimatedGasParams, { value: '0x0' }) + Object.assign(paramsForGasEstimate, { value: '0x0' }) } if (data) { - Object.assign(estimatedGasParams, { data }) + Object.assign(paramsForGasEstimate, { data }) } // if recipient has no code, gas is 21k max: const hasRecipient = Boolean(to) let code - if (hasRecipient) code = await ethQuery.getCode(to) - + if (hasRecipient) code = await global.eth.getCode(to) if (hasRecipient && (!code || code === '0x')) { return SIMPLE_GAS_COST } - estimatedGasParams.to = to + paramsForGasEstimate.to = to // if not, fall back to block gasLimit - estimatedGasParams.gas = multiplyCurrencies(blockGasLimit, 0.95, { + paramsForGasEstimate.gas = ethUtil.addHexPrefix(multiplyCurrencies(blockGasLimit, 0.95, { multiplicandBase: 16, multiplierBase: 10, roundDown: '0', toNumericBase: 'hex', - }) + })) // run tx - const estimatedGas = await ethQuery.estimateGas(estimatedGasParams) - return estimatedGas.toString(16) + return new Promise((resolve, reject) => { + estimateGasMethod(paramsForGasEstimate, (err, estimatedGas) => { + if (err) { + reject(err) + } + resolve(estimatedGas.toString(16)) + }) + }) } function generateTokenTransferData (selectedAddress, selectedToken) { diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js index 3abff0d23..ec624b48c 100644 --- a/ui/app/components/send_/tests/send-component.test.js +++ b/ui/app/components/send_/tests/send-component.test.js @@ -217,9 +217,17 @@ describe.only('Send Component', function () { recentBlocks: ['mockBlock'], selectedAddress: 'mockSelectedAddress', selectedToken: 'mockSelectedToken', + to: undefined, + value: 'mockAmount', } ) }) + + it('should call updateAndSetGasTotal with to set to lowercase if passed', () => { + propsMethodSpies.updateAndSetGasTotal.resetHistory() + wrapper.instance().updateGas({ to: '0xABC' }) + assert.equal(propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0].to, '0xabc') + }) }) describe('render', () => { diff --git a/ui/app/components/send_/tests/send-container.test.js b/ui/app/components/send_/tests/send-container.test.js index dca274c9e..d077ab4ee 100644 --- a/ui/app/components/send_/tests/send-container.test.js +++ b/ui/app/components/send_/tests/send-container.test.js @@ -99,6 +99,8 @@ describe('send container', () => { recentBlocks: ['mockBlock'], selectedAddress: '0x4', selectedToken: { address: '0x1' }, + to: 'mockTo', + value: 'mockValue', } it('should dispatch a setGasTotal action when editingTransactionId is truthy', () => { @@ -111,14 +113,14 @@ describe('send container', () => { }) it('should dispatch an updateGasData action when editingTransactionId is falsy', () => { - const { selectedAddress, selectedToken, data, recentBlocks, blockGasLimit } = mockProps + const { selectedAddress, selectedToken, data, recentBlocks, blockGasLimit, to, value } = mockProps mapDispatchToPropsObject.updateAndSetGasTotal( Object.assign({}, mockProps, {editingTransactionId: false}) ) assert(dispatchSpy.calledOnce) assert.deepEqual( actionSpies.updateGasData.getCall(0).args[0], - { selectedAddress, selectedToken, data, recentBlocks, blockGasLimit } + { selectedAddress, selectedToken, data, recentBlocks, blockGasLimit, to, value } ) }) }) diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index a01ab4eba..3c772ed47 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -24,14 +24,6 @@ const stubs = { rawEncode: sinon.stub().returns([16, 1100]), } -const EthQuery = function () {} -EthQuery.prototype.estimateGas = sinon.stub().callsFake( - (data) => Promise.resolve({ toString: (n) => `mockToString:${n}` }) -) -EthQuery.prototype.getCode = sinon.stub().callsFake( - (address) => Promise.resolve(address.match(/isContract/) ? 'not-0x' : '0x') -) - const sendUtils = proxyquire('../send.utils.js', { '../../conversion-util': { addCurrencies: stubs.addCurrencies, @@ -43,7 +35,6 @@ const sendUtils = proxyquire('../send.utils.js', { 'ethereumjs-abi': { rawEncode: stubs.rawEncode, }, - 'ethjs-query': EthQuery, }) const { @@ -249,6 +240,9 @@ describe('send utils', () => { blockGasLimit: '0x64', selectedAddress: 'mockAddress', to: '0xisContract', + estimateGasMethod: sinon.stub().callsFake( + (data, cb) => cb(null, { toString: (n) => `mockToString:${n}` }) + ), } const baseExpectedCall = { from: 'mockAddress', @@ -256,53 +250,51 @@ describe('send utils', () => { to: '0xisContract', } + beforeEach(() => { + global.eth = { + getCode: sinon.stub().callsFake( + (address) => Promise.resolve(address.match(/isContract/) ? 'not-0x' : '0x') + ), + } + }) + afterEach(() => { - EthQuery.prototype.estimateGas.resetHistory() - EthQuery.prototype.getCode.resetHistory() + baseMockParams.estimateGasMethod.resetHistory() + global.eth.getCode.resetHistory() }) it('should call ethQuery.estimateGas with the expected params', async () => { const result = await estimateGas(baseMockParams) - assert.equal(EthQuery.prototype.estimateGas.callCount, 1) + assert.equal(baseMockParams.estimateGasMethod.callCount, 1) assert.deepEqual( - EthQuery.prototype.estimateGas.getCall(0).args[0], - baseExpectedCall + baseMockParams.estimateGasMethod.getCall(0).args[0], + Object.assign({ gasPrice: undefined, value: undefined }, baseExpectedCall) ) assert.equal(result, 'mockToString:16') }) it('should call ethQuery.estimateGas with a value of 0x0 if the passed selectedToken has a symbol', async () => { const result = await estimateGas(Object.assign({ selectedToken: { symbol: true } }, baseMockParams)) - assert.equal(EthQuery.prototype.estimateGas.callCount, 1) - assert.deepEqual( - EthQuery.prototype.estimateGas.getCall(0).args[0], - Object.assign({ value: '0x0' }, baseExpectedCall) - ) - assert.equal(result, 'mockToString:16') - }) - - it('should call ethQuery.estimateGas with data if data is passed', async () => { - const result = await estimateGas(Object.assign({ data: 'mockData' }, baseMockParams)) - assert.equal(EthQuery.prototype.estimateGas.callCount, 1) + assert.equal(baseMockParams.estimateGasMethod.callCount, 1) assert.deepEqual( - EthQuery.prototype.estimateGas.getCall(0).args[0], - Object.assign({ data: 'mockData' }, baseExpectedCall) + baseMockParams.estimateGasMethod.getCall(0).args[0], + Object.assign({ gasPrice: undefined, value: '0x0' }, baseExpectedCall) ) assert.equal(result, 'mockToString:16') }) it('should call ethQuery.estimateGas with data if data is passed', async () => { const result = await estimateGas(Object.assign({ data: 'mockData' }, baseMockParams)) - assert.equal(EthQuery.prototype.estimateGas.callCount, 1) + assert.equal(baseMockParams.estimateGasMethod.callCount, 1) assert.deepEqual( - EthQuery.prototype.estimateGas.getCall(0).args[0], - Object.assign({ data: 'mockData' }, baseExpectedCall) + baseMockParams.estimateGasMethod.getCall(0).args[0], + Object.assign({ gasPrice: undefined, value: undefined, data: 'mockData' }, baseExpectedCall) ) assert.equal(result, 'mockToString:16') }) it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async () => { - assert.equal(EthQuery.prototype.estimateGas.callCount, 0) + assert.equal(baseMockParams.estimateGasMethod.callCount, 0) const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123' })) assert.equal(result, SIMPLE_GAS_COST) }) -- cgit From 5a842e440f0ec565eba38aec4c1c953a775557ea Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 25 May 2018 11:07:16 -0230 Subject: Gas estimation uses block gas limit as fallback if query.estimateGas returns an expected error. --- ui/app/components/send_/send.utils.js | 15 ++++++++---- ui/app/components/send_/tests/send-utils.test.js | 29 +++++++++++++++++++++++- 2 files changed, 39 insertions(+), 5 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 750411908..9b8f1d118 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -193,14 +193,21 @@ async function estimateGas ({ selectedAddress, selectedToken, data, blockGasLimi roundDown: '0', toNumericBase: 'hex', })) - // run tx return new Promise((resolve, reject) => { - estimateGasMethod(paramsForGasEstimate, (err, estimatedGas) => { + return estimateGasMethod(paramsForGasEstimate, (err, estimatedGas) => { if (err) { - reject(err) + const simulationFailed = ( + err.message.includes('Transaction execution error.') || + err.message.includes('gas required exceeds allowance or always failing transaction') + ) + if (simulationFailed) { + return resolve(paramsForGasEstimate.gas) + } else { + return reject(err) + } } - resolve(estimatedGas.toString(16)) + return resolve(estimatedGas.toString(16)) }) }) } diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index 3c772ed47..4801d4a09 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -241,7 +241,10 @@ describe('send utils', () => { selectedAddress: 'mockAddress', to: '0xisContract', estimateGasMethod: sinon.stub().callsFake( - (data, cb) => cb(null, { toString: (n) => `mockToString:${n}` }) + (data, cb) => cb( + data.to.match(/willFailBecauseOf:/) ? { message: data.to.match(/\:(.+)$/)[1] } : null, + { toString: (n) => `mockToString:${n}` } + ) ), } const baseExpectedCall = { @@ -298,6 +301,30 @@ describe('send utils', () => { const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123' })) assert.equal(result, SIMPLE_GAS_COST) }) + + it(`should return the adjusted blockGasLimit if it fails with a 'Transaction execution error.'`, async () => { + const result = await estimateGas(Object.assign({}, baseMockParams, { + to: 'isContract willFailBecauseOf:Transaction execution error.', + })) + assert.equal(result, '0x64x0.95') + }) + + it(`should return the adjusted blockGasLimit if it fails with a 'gas required exceeds allowance or always failing transaction.'`, async () => { + const result = await estimateGas(Object.assign({}, baseMockParams, { + to: 'isContract willFailBecauseOf:gas required exceeds allowance or always failing transaction.', + })) + assert.equal(result, '0x64x0.95') + }) + + it(`should reject other errors`, async () => { + try { + await estimateGas(Object.assign({}, baseMockParams, { + to: 'isContract willFailBecauseOf:some other error', + })) + } catch (err) { + assert.deepEqual(err, { message: 'some other error' }) + } + }) }) describe('estimateGasPriceFromRecentBlocks', () => { -- cgit From 64aa56b5a6f753d198ceadc9cf2c79929046fc19 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 28 May 2018 12:54:19 -0700 Subject: test - send-utils.test - lint fix --- ui/app/components/send_/tests/send-utils.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index 4801d4a09..14125d7a6 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -242,7 +242,7 @@ describe('send utils', () => { to: '0xisContract', estimateGasMethod: sinon.stub().callsFake( (data, cb) => cb( - data.to.match(/willFailBecauseOf:/) ? { message: data.to.match(/\:(.+)$/)[1] } : null, + data.to.match(/willFailBecauseOf:/) ? { message: data.to.match(/:(.+)$/)[1] } : null, { toString: (n) => `mockToString:${n}` } ) ), -- cgit From 0f3480a97f2924de899e49a095ef24b9fa5506f1 Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 30 May 2018 21:33:40 -0230 Subject: Fix then-catch + error handling in import-account --- ui/app/components/pages/create-account/import-account/json.js | 3 +-- ui/app/components/pages/create-account/import-account/private-key.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js index 3dabad68e..23b7f0afd 100644 --- a/ui/app/components/pages/create-account/import-account/json.js +++ b/ui/app/components/pages/create-account/import-account/json.js @@ -106,8 +106,6 @@ class JsonImportSubview extends Component { } importNewJsonAccount([ fileContents, password ]) - // JS runtime requires caught rejections but failures are handled by Redux - .catch() .then(({ selectedAddress }) => { if (selectedAddress) { history.push(DEFAULT_ROUTE) @@ -116,6 +114,7 @@ class JsonImportSubview extends Component { setSelectedAddress(firstAddress) } }) + .catch(err => displayWarning(err)) } } diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js index e71e47647..81cef09f9 100644 --- a/ui/app/components/pages/create-account/import-account/private-key.js +++ b/ui/app/components/pages/create-account/import-account/private-key.js @@ -96,8 +96,6 @@ PrivateKeyImportView.prototype.createNewKeychain = function () { const { importNewAccount, history, displayWarning, setSelectedAddress, firstAddress } = this.props importNewAccount('Private Key', [ privateKey ]) - // JS runtime requires caught rejections but failures are handled by Redux - .catch() .then(({ selectedAddress }) => { if (selectedAddress) { history.push(DEFAULT_ROUTE) @@ -106,4 +104,5 @@ PrivateKeyImportView.prototype.createNewKeychain = function () { setSelectedAddress(firstAddress) } }) + .catch(err => displayWarning(err)) } -- cgit From 1bde2892ec222cec1ba3265d50ed59f665afbf83 Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 30 May 2018 12:56:42 -0230 Subject: Improve efficiency of estimateGasPriceFromRecentBlocks --- ui/app/components/send_/send.utils.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 9b8f1d118..e685cc274 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -221,26 +221,21 @@ function generateTokenTransferData (selectedAddress, selectedToken) { ).join('') } -function hexComparator (a, b) { - return conversionGreaterThan( - { value: a, fromNumericBase: 'hex' }, - { value: b, fromNumericBase: 'hex' }, - ) ? 1 : -1 -} - function estimateGasPriceFromRecentBlocks (recentBlocks) { // Return 1 gwei if no blocks have been observed: if (!recentBlocks || recentBlocks.length === 0) { return ONE_GWEI_IN_WEI_HEX } + const lowestPrices = recentBlocks.map((block) => { if (!block.gasPrices || block.gasPrices.length < 1) { return ONE_GWEI_IN_WEI_HEX } - return block.gasPrices - .sort(hexComparator)[0] + return block.gasPrices.reduce((currentLowest, next) => { + return parseInt(next, 16) < parseInt(currentLowest, 16) ? next : currentLowest + }) }) - .sort(hexComparator) + .sort((a, b) => parseInt(a, 16) > parseInt(b, 16) ? 1 : -1) return lowestPrices[Math.floor(lowestPrices.length / 2)] } -- cgit From 67c74cd5b6dba059f954be6867a20e1f97197f7d Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 31 May 2018 12:07:23 -0230 Subject: Fix currency display send integration tests. --- ui/app/components/send/currency-display.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 0b52a69b5..ff61bd1e7 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -49,7 +49,7 @@ CurrencyDisplay.prototype.getAmount = function (value) { } CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value }) { - if (value === '0x0') return '' + if (value === '0x0') return '0' const { decimals, symbol } = selectedToken || {} const multiplier = Math.pow(10, Number(decimals || 0)) -- cgit From d454b5de2b03111a05c4e8a8b0a91e612b8f0266 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 31 May 2018 13:20:15 -0230 Subject: Token name is not hidden in wallet if balance is exceptionally long. --- ui/app/components/token-cell.js | 4 ++-- ui/app/css/itcss/components/token-list.scss | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index c84117d84..4100d76a5 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -101,8 +101,8 @@ TokenCell.prototype.render = function () { h('div.token-list-item__balance-ellipsis', null, [ h('div.token-list-item__balance-wrapper', null, [ - h('h3.token-list-item__token-balance', `${string || 0} ${symbol}`), - + h('div.token-list-item__token-balance', `${string || 0}`), + h('div.token-list-item__token-symbol', symbol), showFiat && h('div.token-list-item__fiat-amount', { style: {}, }, formattedFiat), diff --git a/ui/app/css/itcss/components/token-list.scss b/ui/app/css/itcss/components/token-list.scss index e8de317e3..214bbc774 100644 --- a/ui/app/css/itcss/components/token-list.scss +++ b/ui/app/css/itcss/components/token-list.scss @@ -14,10 +14,16 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and ( min-width: 0; &__token-balance { - font-size: 1.5rem; + margin-right: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + flex: 1; + } + + &__token-balance, &__token-symbol { + font-size: 1.5rem; + display: inline-flex; @media #{$wallet-balance-breakpoint-range} { font-size: 95%; @@ -68,6 +74,8 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and ( &__balance-wrapper { flex: 1 1 auto; min-width: 0; + overflow: hidden; + text-overflow: ellipsis; } } -- cgit From 990b69c6552b5571391ea5fbf05b5fbef1e0ab10 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 31 May 2018 12:23:12 -0230 Subject: Improve input width calculation in currency-display.js --- ui/app/components/send/currency-display.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index ff61bd1e7..360dd15d4 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -98,9 +98,8 @@ CurrencyDisplay.prototype.handleChange = function (newVal) { CurrencyDisplay.prototype.getInputWidth = function (valueToRender, readOnly) { const valueString = String(valueToRender) const valueLength = valueString.length || 1 - const dynamicBuffer = readOnly ? 0 : 1 - const decimalPointDeficit = !readOnly && valueString.match(/\./) ? -0.5 : 0 - return (valueLength + dynamicBuffer + decimalPointDeficit) + 'ch' + const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0 + return (valueLength + decimalPointDeficit + 0.75) + 'ch' } CurrencyDisplay.prototype.render = function () { -- cgit From 2ca084b0557242b34733107701a14ba0724363b3 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 31 May 2018 15:31:50 -0230 Subject: Remove .only --- ui/app/components/send_/tests/send-component.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js index 4aa1978e4..c82edd971 100644 --- a/ui/app/components/send_/tests/send-component.test.js +++ b/ui/app/components/send_/tests/send-component.test.js @@ -25,7 +25,7 @@ const SendTransactionScreen = proxyquire('../send.component.js', { sinon.spy(SendTransactionScreen.prototype, 'componentDidMount') sinon.spy(SendTransactionScreen.prototype, 'updateGas') -describe.only('Send Component', function () { +describe('Send Component', function () { let wrapper beforeEach(() => { -- cgit From fd98ed570e09faf12ac10e6340225fd586914558 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 31 May 2018 11:16:41 -0700 Subject: Fix ellipses --- ui/app/css/itcss/components/token-list.scss | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'ui') diff --git a/ui/app/css/itcss/components/token-list.scss b/ui/app/css/itcss/components/token-list.scss index 214bbc774..72fda372f 100644 --- a/ui/app/css/itcss/components/token-list.scss +++ b/ui/app/css/itcss/components/token-list.scss @@ -18,12 +18,13 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and ( white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - flex: 1; + min-width: 0; + max-width: 100%; } &__token-balance, &__token-symbol { font-size: 1.5rem; - display: inline-flex; + flex: 0 0 auto; @media #{$wallet-balance-breakpoint-range} { font-size: 95%; @@ -72,10 +73,10 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and ( } &__balance-wrapper { - flex: 1 1 auto; + flex: 1; + flex-flow: row wrap; + display: flex; min-width: 0; - overflow: hidden; - text-overflow: ellipsis; } } -- cgit From 966583026a3eee2255ff1b05314f604874380ec5 Mon Sep 17 00:00:00 2001 From: Bobby Dresser Date: Thu, 31 May 2018 15:26:04 -0700 Subject: update helpscout links to zenhub --- .../token-list-placeholder/token-list-placeholder.component.js | 2 +- ui/app/components/pages/create-account/import-account/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js b/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js index abd599b26..1611f817b 100644 --- a/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js +++ b/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js @@ -15,7 +15,7 @@ export default class TokenListPlaceholder extends Component {
diff --git a/ui/app/components/pages/create-account/import-account/index.js b/ui/app/components/pages/create-account/import-account/index.js index 52d3dcde9..e2e973af9 100644 --- a/ui/app/components/pages/create-account/import-account/index.js +++ b/ui/app/components/pages/create-account/import-account/index.js @@ -46,7 +46,7 @@ AccountImportSubview.prototype.render = function () { }, onClick: () => { global.platform.openWindow({ - url: 'https://metamask.helpscoutdocs.com/article/17-what-are-loose-accounts', + url: 'https://consensys.zendesk.com/hc/en-us/articles/360004180111-What-are-imported-accounts-New-UI', }) }, }, this.context.t('here')), -- cgit From 1b879f45bc0d53e8c0ffa9513b525e0055ed8f81 Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 2 Jun 2018 01:53:01 -0230 Subject: Fix calculation of data property for gas estimation on token transfers. --- ui/app/actions.js | 2 - ui/app/components/send_/send.component.js | 3 -- ui/app/components/send_/send.container.js | 8 +--- ui/app/components/send_/send.utils.js | 23 +++++------- .../components/send_/tests/send-component.test.js | 2 - .../components/send_/tests/send-container.test.js | 7 +--- ui/app/components/send_/tests/send-utils.test.js | 43 ++++++++++++++-------- 7 files changed, 41 insertions(+), 47 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index b6cb9c382..465b3d5fe 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -732,7 +732,6 @@ function setGasTotal (gasTotal) { function updateGasData ({ blockGasLimit, - data, recentBlocks, selectedAddress, selectedToken, @@ -746,7 +745,6 @@ function updateGasData ({ estimateGas({ estimateGasMethod: background.estimateGas, blockGasLimit, - data, selectedAddress, selectedToken, to, diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index 97c6d1294..516251e22 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -20,7 +20,6 @@ export default class SendTransactionScreen extends PersistentForm { ]), blockGasLimit: PropTypes.string, conversionRate: PropTypes.number, - data: PropTypes.string, editingTransactionId: PropTypes.string, from: PropTypes.object, gasLimit: PropTypes.string, @@ -43,7 +42,6 @@ export default class SendTransactionScreen extends PersistentForm { const { amount, blockGasLimit, - data, editingTransactionId, gasLimit, gasPrice, @@ -55,7 +53,6 @@ export default class SendTransactionScreen extends PersistentForm { updateAndSetGasTotal({ blockGasLimit, - data, editingTransactionId, gasLimit, gasPrice, diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js index 7e241aa2d..1fd96d61f 100644 --- a/ui/app/components/send_/send.container.js +++ b/ui/app/components/send_/send.container.js @@ -31,7 +31,6 @@ import { } from '../../ducks/send.duck' import { calcGasTotal, - generateTokenTransferData, } from './send.utils.js' module.exports = compose( @@ -40,15 +39,11 @@ module.exports = compose( )(SendEther) function mapStateToProps (state) { - const selectedAddress = getSelectedAddress(state) - const selectedToken = getSelectedToken(state) - return { amount: getSendAmount(state), amountConversionRate: getAmountConversionRate(state), blockGasLimit: getBlockGasLimit(state), conversionRate: getConversionRate(state), - data: generateTokenTransferData(selectedAddress, selectedToken), editingTransactionId: getSendEditingTransactionId(state), from: getSendFromObject(state), gasLimit: getGasLimit(state), @@ -69,7 +64,6 @@ function mapDispatchToProps (dispatch) { return { updateAndSetGasTotal: ({ blockGasLimit, - data, editingTransactionId, gasLimit, gasPrice, @@ -80,7 +74,7 @@ function mapDispatchToProps (dispatch) { value, }) => { !editingTransactionId - ? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, data, blockGasLimit, to, value })) + ? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, blockGasLimit, to, value })) : dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice))) }, updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => { diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index e685cc274..855d12303 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -14,6 +14,7 @@ const { NEGATIVE_ETH_ERROR, ONE_GWEI_IN_WEI_HEX, SIMPLE_GAS_COST, + TOKEN_TRANSFER_FUNCTION_SIGNATURE, } = require('./send.constants') const abi = require('ethereumjs-abi') const ethUtil = require('ethereumjs-util') @@ -165,26 +166,23 @@ function doesAmountErrorRequireUpdate ({ return amountErrorRequiresUpdate } -async function estimateGas ({ selectedAddress, selectedToken, data, blockGasLimit, to, value, gasPrice, estimateGasMethod }) { - const { symbol } = selectedToken || {} +async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, value, gasPrice, estimateGasMethod }) { const paramsForGasEstimate = { from: selectedAddress, value, gasPrice } - if (symbol) { - Object.assign(paramsForGasEstimate, { value: '0x0' }) + if (selectedToken) { + paramsForGasEstimate.value = '0x0' + paramsForGasEstimate.data = generateTokenTransferData({ toAddress: to, amount: value, selectedToken }) } - if (data) { - Object.assign(paramsForGasEstimate, { data }) - } // if recipient has no code, gas is 21k max: const hasRecipient = Boolean(to) let code if (hasRecipient) code = await global.eth.getCode(to) - if (hasRecipient && (!code || code === '0x')) { + if (hasRecipient && (!code || code === '0x') && !selectedToken) { return SIMPLE_GAS_COST } - paramsForGasEstimate.to = to + paramsForGasEstimate.to = selectedToken ? selectedToken.address : to // if not, fall back to block gasLimit paramsForGasEstimate.gas = ethUtil.addHexPrefix(multiplyCurrencies(blockGasLimit, 0.95, { @@ -212,11 +210,10 @@ async function estimateGas ({ selectedAddress, selectedToken, data, blockGasLimi }) } -function generateTokenTransferData (selectedAddress, selectedToken) { +function generateTokenTransferData ({ toAddress = '0x0', amount = '0x0', selectedToken }) { if (!selectedToken) return - console.log(`abi.rawEncode`, abi.rawEncode) - return Array.prototype.map.call( - abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']), + return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call( + abi.rawEncode(['address', 'uint256'], [toAddress, ethUtil.addHexPrefix(amount)]), x => ('00' + x.toString(16)).slice(-2) ).join('') } diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js index 2529d6e5f..4e33d8f63 100644 --- a/ui/app/components/send_/tests/send-component.test.js +++ b/ui/app/components/send_/tests/send-component.test.js @@ -34,7 +34,6 @@ describe('Send Component', function () { amountConversionRate={'mockAmountConversionRate'} blockGasLimit={'mockBlockGasLimit'} conversionRate={10} - data={'mockData'} editingTransactionId={'mockEditingTransactionId'} from={ { address: 'mockAddress', balance: 'mockBalance' } } gasLimit={'mockGasLimit'} @@ -210,7 +209,6 @@ describe('Send Component', function () { propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0], { blockGasLimit: 'mockBlockGasLimit', - data: 'mockData', editingTransactionId: 'mockEditingTransactionId', gasLimit: 'mockGasLimit', gasPrice: 'mockGasPrice', diff --git a/ui/app/components/send_/tests/send-container.test.js b/ui/app/components/send_/tests/send-container.test.js index d077ab4ee..056aad148 100644 --- a/ui/app/components/send_/tests/send-container.test.js +++ b/ui/app/components/send_/tests/send-container.test.js @@ -47,7 +47,6 @@ proxyquire('../send.container.js', { '../../ducks/send.duck': duckActionSpies, './send.utils.js': { calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice, - generateTokenTransferData: (a, b) => `mockData:${a + b}`, }, }) @@ -61,7 +60,6 @@ describe('send container', () => { amountConversionRate: 'mockAmountConversionRate:mockState', blockGasLimit: 'mockBlockGasLimit:mockState', conversionRate: 'mockConversionRate:mockState', - data: 'mockData:mockSelectedAddress:mockStatemockSelectedToken:mockState', editingTransactionId: 'mockEditingTransactionId:mockState', from: 'mockFrom:mockState', gasLimit: 'mockGasLimit:mockState', @@ -92,7 +90,6 @@ describe('send container', () => { describe('updateAndSetGasTotal()', () => { const mockProps = { blockGasLimit: 'mockBlockGasLimit', - data: '0x1', editingTransactionId: '0x2', gasLimit: '0x3', gasPrice: '0x4', @@ -113,14 +110,14 @@ describe('send container', () => { }) it('should dispatch an updateGasData action when editingTransactionId is falsy', () => { - const { selectedAddress, selectedToken, data, recentBlocks, blockGasLimit, to, value } = mockProps + const { selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value } = mockProps mapDispatchToPropsObject.updateAndSetGasTotal( Object.assign({}, mockProps, {editingTransactionId: false}) ) assert(dispatchSpy.calledOnce) assert.deepEqual( actionSpies.updateGasData.getCall(0).args[0], - { selectedAddress, selectedToken, data, recentBlocks, blockGasLimit, to, value } + { selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value } ) }) }) diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index 14125d7a6..b3f6372ef 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -106,11 +106,23 @@ describe('send utils', () => { describe('generateTokenTransferData()', () => { it('should return undefined if not passed a selected token', () => { - assert.equal(generateTokenTransferData('mockAddress', false), undefined) + assert.equal(generateTokenTransferData({ toAddress: 'mockAddress', amount: '0xa', selectedToken: false}), undefined) + }) + + it('should call abi.rawEncode with the correct params', () => { + stubs.rawEncode.resetHistory() + generateTokenTransferData({ toAddress: 'mockAddress', amount: 'ab', selectedToken: true}) + assert.deepEqual( + stubs.rawEncode.getCall(0).args, + [['address', 'uint256'], ['mockAddress', '0xab']] + ) }) it('should return encoded token transfer data', () => { - assert.equal(generateTokenTransferData('mockAddress', true), '104c') + assert.equal( + generateTokenTransferData({ toAddress: 'mockAddress', amount: '0xa', selectedToken: true}), + '0xa9059cbb104c' + ) }) }) @@ -276,22 +288,17 @@ describe('send utils', () => { assert.equal(result, 'mockToString:16') }) - it('should call ethQuery.estimateGas with a value of 0x0 if the passed selectedToken has a symbol', async () => { - const result = await estimateGas(Object.assign({ selectedToken: { symbol: true } }, baseMockParams)) + it('should call ethQuery.estimateGas with a value of 0x0 and the expected data and to if passed a selectedToken', async () => { + const result = await estimateGas(Object.assign({ data: 'mockData', selectedToken: { address: 'mockAddress' } }, baseMockParams)) assert.equal(baseMockParams.estimateGasMethod.callCount, 1) assert.deepEqual( baseMockParams.estimateGasMethod.getCall(0).args[0], - Object.assign({ gasPrice: undefined, value: '0x0' }, baseExpectedCall) - ) - assert.equal(result, 'mockToString:16') - }) - - it('should call ethQuery.estimateGas with data if data is passed', async () => { - const result = await estimateGas(Object.assign({ data: 'mockData' }, baseMockParams)) - assert.equal(baseMockParams.estimateGasMethod.callCount, 1) - assert.deepEqual( - baseMockParams.estimateGasMethod.getCall(0).args[0], - Object.assign({ gasPrice: undefined, value: undefined, data: 'mockData' }, baseExpectedCall) + Object.assign({}, baseExpectedCall, { + gasPrice: undefined, + value: '0x0', + data: '0xa9059cbb104c', + to: 'mockAddress', + }) ) assert.equal(result, 'mockToString:16') }) @@ -302,6 +309,12 @@ describe('send utils', () => { assert.equal(result, SIMPLE_GAS_COST) }) + it(`should not return ${SIMPLE_GAS_COST} if passed a selectedToken`, async () => { + assert.equal(baseMockParams.estimateGasMethod.callCount, 0) + const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123', selectedToken: { address: '' } })) + assert.notEqual(result, SIMPLE_GAS_COST) + }) + it(`should return the adjusted blockGasLimit if it fails with a 'Transaction execution error.'`, async () => { const result = await estimateGas(Object.assign({}, baseMockParams, { to: 'isContract willFailBecauseOf:Transaction execution error.', -- cgit From ffd42c59da1e3728786d8e8cd20a9c95ea279de0 Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 2 Jun 2018 01:58:01 -0230 Subject: Default currency-display valueToRender to empty string when not readOnly ('0' if readOnly). --- ui/app/components/send/currency-display.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index ede08dbc0..3bc9ad226 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -49,8 +49,8 @@ CurrencyDisplay.prototype.getAmount = function (value) { : toHexWei(value) } -CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value }) { - if (value === '0x0') return '0' +CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value, readOnly }) { + if (value === '0x0') return readOnly ? '0' : '' const { decimals, symbol } = selectedToken || {} const multiplier = Math.pow(10, Number(decimals || 0)) -- cgit From 5a2771dd470161f5678e3245f90aeb3a1ce1b89c Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 4 Jun 2018 09:33:25 -0700 Subject: Indicate the current selected account on the popup account view (#4445) --- ui/app/components/index.scss | 2 + ui/app/components/selected-account/index.js | 2 + ui/app/components/selected-account/index.scss | 38 ++++++++++++++ .../selected-account/selected-account.component.js | 60 ++++++++++++++++++++++ .../selected-account/selected-account.container.js | 13 +++++ ui/app/components/tx-view.js | 28 +++------- ui/app/css/itcss/components/account-menu.scss | 4 ++ ui/app/css/itcss/components/hero-balance.scss | 1 + 8 files changed, 127 insertions(+), 21 deletions(-) create mode 100644 ui/app/components/selected-account/index.js create mode 100644 ui/app/components/selected-account/index.scss create mode 100644 ui/app/components/selected-account/selected-account.component.js create mode 100644 ui/app/components/selected-account/selected-account.container.js (limited to 'ui') diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index e69acff63..351640f6e 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -1,5 +1,7 @@ @import './export-text-container/index'; +@import './selected-account/index'; + @import './info-box/index'; @import './pages/index'; diff --git a/ui/app/components/selected-account/index.js b/ui/app/components/selected-account/index.js new file mode 100644 index 000000000..eb342181f --- /dev/null +++ b/ui/app/components/selected-account/index.js @@ -0,0 +1,2 @@ +import SelectedAccount from './selected-account.container' +module.exports = SelectedAccount diff --git a/ui/app/components/selected-account/index.scss b/ui/app/components/selected-account/index.scss new file mode 100644 index 000000000..5339a228b --- /dev/null +++ b/ui/app/components/selected-account/index.scss @@ -0,0 +1,38 @@ +.selected-account { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex: 1; + + &__name { + max-width: 200px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + text-align: center; + } + + &__address { + font-size: .75rem; + color: $silver-chalice; + } + + &__clickable { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 5px 15px; + border-radius: 10px; + cursor: pointer; + + &:hover { + background-color: #e8e6e8; + } + + &:active { + background-color: #d9d7da; + } + } +} diff --git a/ui/app/components/selected-account/selected-account.component.js b/ui/app/components/selected-account/selected-account.component.js new file mode 100644 index 000000000..3386a4196 --- /dev/null +++ b/ui/app/components/selected-account/selected-account.component.js @@ -0,0 +1,60 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import copyToClipboard from 'copy-to-clipboard' + +const Tooltip = require('../tooltip-v2.js') + +const addressStripper = (address = '') => { + if (address.length < 4) { + return address + } + + return `${address.slice(0, 4)}...${address.slice(-4)}` +} + +class SelectedAccount extends Component { + state = { + copied: false, + } + + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + selectedAddress: PropTypes.string, + selectedIdentity: PropTypes.object, + } + + render () { + const { t } = this.context + const { selectedAddress, selectedIdentity } = this.props + + return ( +
+ +
{ + this.setState({ copied: true }) + setTimeout(() => this.setState({ copied: false }), 3000) + copyToClipboard(selectedAddress) + }} + > +
+ { selectedIdentity.name } +
+
+ { addressStripper(selectedAddress) } +
+
+
+
+ ) + } +} + +export default SelectedAccount diff --git a/ui/app/components/selected-account/selected-account.container.js b/ui/app/components/selected-account/selected-account.container.js new file mode 100644 index 000000000..f9e061d15 --- /dev/null +++ b/ui/app/components/selected-account/selected-account.container.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux' +import SelectedAccount from './selected-account.component' + +const selectors = require('../../selectors') + +const mapStateToProps = state => { + return { + selectedAddress: selectors.getSelectedAddress(state), + selectedIdentity: selectors.getSelectedIdentity(state), + } +} + +export default connect(mapStateToProps)(SelectedAccount) diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js index 263f992c0..014497fcd 100644 --- a/ui/app/components/tx-view.js +++ b/ui/app/components/tx-view.js @@ -12,7 +12,7 @@ const { checksumAddress: toChecksumAddress } = require('../util') const BalanceComponent = require('./balance-component') const TxList = require('./tx-list') -const Identicon = require('./identicon') +const SelectedAccount = require('./selected-account') module.exports = compose( withRouter, @@ -103,7 +103,7 @@ TxView.prototype.renderButtons = function () { } TxView.prototype.render = function () { - const { selectedAddress, identity, network, isMascara } = this.props + const { isMascara } = this.props return h('div.tx-view.flex-column', { style: {}, @@ -111,10 +111,12 @@ TxView.prototype.render = function () { h('div.flex-row.phone-visible', { style: { - justifyContent: 'space-between', + justifyContent: 'center', alignItems: 'center', flex: '0 0 auto', - margin: '10px', + marginBottom: '16px', + padding: '5px', + borderBottom: '1px solid #e5e5e5', }, }, [ @@ -127,23 +129,7 @@ TxView.prototype.render = function () { onClick: () => this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar(), }), - h('.identicon-wrapper.select-none', { - style: { - marginLeft: '0.9em', - }, - }, [ - h(Identicon, { - diameter: 24, - address: selectedAddress, - network, - }), - ]), - - h('span.account-name', { - style: {}, - }, [ - identity.name, - ]), + h(SelectedAccount), !isMascara && h('div.open-in-browser', { onClick: () => global.platform.openExtensionInBrowser(), diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss index 657760ab5..96fba890c 100644 --- a/ui/app/css/itcss/components/account-menu.scss +++ b/ui/app/css/itcss/components/account-menu.scss @@ -116,6 +116,10 @@ &__name { color: $white; font-size: 18px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + max-width: 200px; } &__balance { diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss index 69cde8a0f..09d66aedd 100644 --- a/ui/app/css/itcss/components/hero-balance.scss +++ b/ui/app/css/itcss/components/hero-balance.scss @@ -6,6 +6,7 @@ justify-content: flex-start; align-items: center; flex: 0 0 auto; + padding-top: 16px; } @media screen and (min-width: $break-large) { -- cgit From ada59054c9d102cc99b950f1377256cac5545649 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 4 Jun 2018 15:50:52 -0230 Subject: Simplify recipient code check in send.utils estimateGas --- ui/app/components/send_/send.utils.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 855d12303..67699be77 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -176,10 +176,11 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, // if recipient has no code, gas is 21k max: const hasRecipient = Boolean(to) - let code - if (hasRecipient) code = await global.eth.getCode(to) - if (hasRecipient && (!code || code === '0x') && !selectedToken) { - return SIMPLE_GAS_COST + if (hasRecipient && !selectedToken) { + const code = await global.eth.getCode(to) + if (!code || code === '0x') { + return SIMPLE_GAS_COST + } } paramsForGasEstimate.to = selectedToken ? selectedToken.address : to -- cgit From 3b6e96bac918925c4edc674e26dba8cc5feb1324 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Mon, 4 Jun 2018 20:43:32 -0230 Subject: Update hide-token-confirmation-modal.js to use new modalState schema (#4482) * Update hide-token-confirmation-modal.js to use new modalState schema (added in 41e38fe55). * Fix modalState props --- ui/app/components/modals/edit-account-name-modal.js | 2 +- ui/app/components/modals/hide-token-confirmation-modal.js | 2 +- ui/app/components/modals/shapeshift-deposit-tx-modal.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/components/modals/edit-account-name-modal.js b/ui/app/components/modals/edit-account-name-modal.js index 5681a3cad..edced8725 100644 --- a/ui/app/components/modals/edit-account-name-modal.js +++ b/ui/app/components/modals/edit-account-name-modal.js @@ -9,7 +9,7 @@ const { getSelectedAccount } = require('../../selectors') function mapStateToProps (state) { return { selectedAccount: getSelectedAccount(state), - identity: state.appState.modal.modalState.identity, + identity: state.appState.modal.modalState.props.identity, } } diff --git a/ui/app/components/modals/hide-token-confirmation-modal.js b/ui/app/components/modals/hide-token-confirmation-modal.js index 72e9c84eb..1518fa9a0 100644 --- a/ui/app/components/modals/hide-token-confirmation-modal.js +++ b/ui/app/components/modals/hide-token-confirmation-modal.js @@ -9,7 +9,7 @@ const Identicon = require('../identicon') function mapStateToProps (state) { return { network: state.metamask.network, - token: state.appState.modal.modalState.token, + token: state.appState.modal.modalState.props.token, } } diff --git a/ui/app/components/modals/shapeshift-deposit-tx-modal.js b/ui/app/components/modals/shapeshift-deposit-tx-modal.js index 24af5a0de..242c7b89d 100644 --- a/ui/app/components/modals/shapeshift-deposit-tx-modal.js +++ b/ui/app/components/modals/shapeshift-deposit-tx-modal.js @@ -8,7 +8,7 @@ const AccountModalContainer = require('./account-modal-container') function mapStateToProps (state) { return { - Qr: state.appState.modal.modalState.Qr, + Qr: state.appState.modal.modalState.props.Qr, } } -- cgit From 762695bfd943a8910e4f297a66d9b8cd44eb8579 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Tue, 5 Jun 2018 12:04:03 -0700 Subject: Ensure selectedAddress exists when render wallet --- ui/app/components/wallet-view.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ui') diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 3b29dacac..538175cdf 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -111,6 +111,10 @@ WalletView.prototype.render = function () { const checksummedAddress = checksumAddress(selectedAddress) + if (!selectedAddress) { + throw new Error('selectedAddress should not be ' + String(selectedAddress)) + } + const keyring = keyrings.find((kr) => { return kr.accounts.includes(selectedAddress) || kr.accounts.includes(selectedIdentity.address) -- cgit From 665ac860e5d29c573e07161632b0043ba18ef1d4 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Tue, 5 Jun 2018 12:23:30 -0700 Subject: Remove selectedIdentity prop from wallet view The selectedIdentity property is computed based on the selectedAddress which means that using both the selectedAddress and the selectedIdentity is redundant. In the case of the Array#find call on the set of keyrings, we wouldn't have a situation where one is included and the other isn't. This changeset removes the selectedIdentity from the wallet view because it isn't needed. --- ui/app/components/wallet-view.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'ui') diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 538175cdf..da142fad8 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -36,7 +36,6 @@ function mapStateToProps (state) { tokens: state.metamask.tokens, keyrings: state.metamask.keyrings, selectedAddress: selectors.getSelectedAddress(state), - selectedIdentity: selectors.getSelectedIdentity(state), selectedAccount: selectors.getSelectedAccount(state), selectedTokenAddress: state.metamask.selectedTokenAddress, } @@ -99,12 +98,12 @@ WalletView.prototype.render = function () { const { responsiveDisplayClassname, selectedAddress, - selectedIdentity, keyrings, showAccountDetailModal, sidebarOpen, hideSidebar, history, + identities, } = this.props // temporary logs + fake extra wallets // console.log('walletview, selectedAccount:', selectedAccount) @@ -116,8 +115,7 @@ WalletView.prototype.render = function () { } const keyring = keyrings.find((kr) => { - return kr.accounts.includes(selectedAddress) || - kr.accounts.includes(selectedIdentity.address) + return kr.accounts.includes(selectedAddress) }) const type = keyring.type @@ -149,7 +147,7 @@ WalletView.prototype.render = function () { h('span.account-name', { style: {}, }, [ - selectedIdentity.name, + identities[selectedAddress].name, ]), h('button.btn-clear.wallet-view__details-button.allcaps', this.context.t('details')), -- cgit From 8b449b325d96ad28c346d87729c1ebd230b8d38e Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 4 Jun 2018 14:11:50 -0700 Subject: Remove unused identities reducer from UI --- ui/app/reducers.js | 7 ------- ui/app/reducers/identities.js | 15 --------------- 2 files changed, 22 deletions(-) delete mode 100644 ui/app/reducers/identities.js (limited to 'ui') diff --git a/ui/app/reducers.js b/ui/app/reducers.js index f155b2bf3..e3a3077d9 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -4,7 +4,6 @@ const copyToClipboard = require('copy-to-clipboard') // // Sub-Reducers take in the complete state and return their sub-state // -const reduceIdentities = require('./reducers/identities') const reduceMetamask = require('./reducers/metamask') const reduceApp = require('./reducers/app') const reduceLocale = require('./reducers/locale') @@ -21,12 +20,6 @@ function rootReducer (state, action) { return action.value } - // - // Identities - // - - state.identities = reduceIdentities(state, action) - // // MetaMask // diff --git a/ui/app/reducers/identities.js b/ui/app/reducers/identities.js deleted file mode 100644 index 341a404e7..000000000 --- a/ui/app/reducers/identities.js +++ /dev/null @@ -1,15 +0,0 @@ -const extend = require('xtend') - -module.exports = reduceIdentities - -function reduceIdentities (state, action) { - // clone + defaults - var idState = extend({ - - }, state.identities) - - switch (action.type) { - default: - return idState - } -} -- cgit From d9d09f953b9500b783a164b4aba85efcbd7ddbe2 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 4 Jun 2018 15:03:03 -0700 Subject: Render the accounts in keyring order --- ui/app/components/account-menu/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index 7638995ea..f34631ca8 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -135,11 +135,12 @@ AccountMenu.prototype.renderAccounts = function () { showAccountDetail, } = this.props - return Object.keys(identities).map((key, index) => { - const identity = identities[key] + const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), []) + return accountOrder.map((address) => { + const identity = identities[address] const isSelected = identity.address === selectedAddress - const balanceValue = accounts[key] ? accounts[key].balance : '' + const balanceValue = accounts[address] ? accounts[address].balance : '' const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...' const simpleAddress = identity.address.substring(2).toLowerCase() -- cgit From ccd4884db112a5440e7f482f644e6729e638dc49 Mon Sep 17 00:00:00 2001 From: 03-26 <37808790+03-26@users.noreply.github.com> Date: Thu, 7 Jun 2018 03:38:57 +0900 Subject: i18n - ja improvements --- ui/app/components/pages/add-token/add-token.component.js | 6 +++--- ui/app/components/pages/create-account/index.js | 15 ++++++++++++--- ui/app/components/pages/settings/index.js | 9 +++++++-- .../components/pages/unlock-page/unlock-page.component.js | 2 +- ui/app/send-v2.js | 4 ++-- ui/app/welcome-screen.js | 14 +++++++++----- 6 files changed, 34 insertions(+), 16 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pages/add-token/add-token.component.js b/ui/app/components/pages/add-token/add-token.component.js index 1f4b37b53..bcb93d401 100644 --- a/ui/app/components/pages/add-token/add-token.component.js +++ b/ui/app/components/pages/add-token/add-token.component.js @@ -231,7 +231,7 @@ class AddToken extends Component {
this.handleCustomAddressChange(e.target.value)} @@ -241,7 +241,7 @@ class AddToken extends Component { /> this.handleCustomSymbolChange(e.target.value)} @@ -252,7 +252,7 @@ class AddToken extends Component { /> this.handleCustomDecimalsChange(e.target.value)} diff --git a/ui/app/components/pages/create-account/index.js b/ui/app/components/pages/create-account/index.js index 475261253..6e3b93742 100644 --- a/ui/app/components/pages/create-account/index.js +++ b/ui/app/components/pages/create-account/index.js @@ -22,7 +22,9 @@ class CreateAccountPage extends Component { }), }), onClick: () => history.push(NEW_ACCOUNT_ROUTE), - }, 'Create'), + }, [ + this.context.t('create'), + ]), h('div.new-account__tabs__tab', { className: classnames('new-account__tabs__tab', { @@ -31,14 +33,16 @@ class CreateAccountPage extends Component { }), }), onClick: () => history.push(IMPORT_ACCOUNT_ROUTE), - }, 'Import'), + }, [ + this.context.t('import'), + ]), ]) } render () { return h('div.new-account', {}, [ h('div.new-account__header', [ - h('div.new-account__title', 'New Account'), + h('div.new-account__title', this.context.t('newAccount') ), this.renderTabs(), ]), h('div.new-account__form', [ @@ -62,6 +66,11 @@ class CreateAccountPage extends Component { CreateAccountPage.propTypes = { location: PropTypes.object, history: PropTypes.object, + t: PropTypes.func, +} + +CreateAccountPage.contextTypes = { + t: PropTypes.func, } const mapStateToProps = state => ({ diff --git a/ui/app/components/pages/settings/index.js b/ui/app/components/pages/settings/index.js index 384ae4b41..aee17e0e8 100644 --- a/ui/app/components/pages/settings/index.js +++ b/ui/app/components/pages/settings/index.js @@ -14,8 +14,8 @@ class Config extends Component { return h('div.settings__tabs', [ h(TabBar, { tabs: [ - { content: 'Settings', key: SETTINGS_ROUTE }, - { content: 'Info', key: INFO_ROUTE }, + { content: this.context.t('settings'), key: SETTINGS_ROUTE }, + { content: this.context.t('info'), key: INFO_ROUTE }, ], isActive: key => matchPath(location.pathname, { path: key, exact: true }), onSelect: key => history.push(key), @@ -54,6 +54,11 @@ class Config extends Component { Config.propTypes = { location: PropTypes.object, history: PropTypes.object, + t: PropTypes.func, +} + +Config.contextTypes = { + t: PropTypes.func, } module.exports = Config diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js index 8bc3897da..a1d3f9181 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -120,7 +120,7 @@ class UnlockPage extends Component { > this.handleInputChange(event)} diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index 4fbe8ff11..612f256df 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -224,7 +224,7 @@ SendTransactionScreen.prototype.renderFromRow = function () { return h('div.send-v2__form-row', [ - h('div.send-v2__form-label', 'From:'), + h('div.send-v2__form-label', this.context.t('from')), h('div.send-v2__form-field', [ h(FromDropdown, { @@ -396,7 +396,7 @@ SendTransactionScreen.prototype.renderAmountRow = function () { return h('div.send-v2__form-row', [ h('div.send-v2__form-label', [ - 'Amount:', + this.context.t('amount'), this.renderErrorMessage('amount'), !errors.amount && gasTotal && h('div.send-v2__amount-max', { onClick: (event) => { diff --git a/ui/app/welcome-screen.js b/ui/app/welcome-screen.js index 2fa244d9f..63512cd50 100644 --- a/ui/app/welcome-screen.js +++ b/ui/app/welcome-screen.js @@ -14,6 +14,11 @@ class WelcomeScreen extends Component { closeWelcomeScreen: PropTypes.func.isRequired, welcomeScreenSeen: PropTypes.bool, history: PropTypes.object, + t: PropTypes.func, + } + + static contextTypes = { + t: PropTypes.func, } constructor (props) { @@ -45,16 +50,15 @@ class WelcomeScreen extends Component { height: '225', }), - h('div.welcome-screen__info__header', 'Welcome to MetaMask Beta'), + h('div.welcome-screen__info__header', this.context.t('welcomeBeta')), - h('div.welcome-screen__info__copy', 'MetaMask is a secure identity vault for Ethereum.'), + h('div.welcome-screen__info__copy', this.context.t('metamaskDescription')), - h('div.welcome-screen__info__copy', `It allows you to hold ether & tokens, - and serves as your bridge to decentralized applications.`), + h('div.welcome-screen__info__copy', this.context.t('holdEther')), h('button.welcome-screen__button', { onClick: this.initiateAccountCreation, - }, 'Continue'), + }, this.context.t('continue')), ]), -- cgit From 049071a743581d142b0426d25613d1318d41093f Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 8 Jun 2018 12:52:38 -0230 Subject: Access correct property from state in confirm screen components gatherTxMeta() --- ui/app/components/pending-tx/confirm-send-ether.js | 2 +- ui/app/components/pending-tx/confirm-send-token.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index 97d0318ea..bbf5683f0 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -647,7 +647,7 @@ ConfirmSendEther.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) - const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send + const { gasPrice: sendGasPrice, gasLimit: sendGasLimit } = props.send const { lastGasPrice, txParams: { diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 1802d3143..ee066b8f4 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -651,7 +651,7 @@ ConfirmSendToken.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) - const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send + const { gasPrice: sendGasPrice, gasLimit: sendGasLimit } = props.send const { lastGasPrice, txParams: { -- cgit From 4196b16f06a45d5ebfbf8599cc2f96c9b690e29b Mon Sep 17 00:00:00 2001 From: Bobby Dresser Date: Mon, 11 Jun 2018 14:28:57 -0700 Subject: add help link to eth_sign warning --- ui/app/components/signature-request.js | 9 ++++++++- ui/app/css/itcss/components/request-signature.scss | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js index ab780dcf4..2a3e929fe 100644 --- a/ui/app/components/signature-request.js +++ b/ui/app/components/signature-request.js @@ -178,7 +178,14 @@ SignatureRequest.prototype.renderBody = function () { rows = data } else if (type === 'eth_sign') { rows = [{ name: this.context.t('message'), value: data }] - notice = this.context.t('signNotice') + notice = [this.context.t('signNotice'), + h('span.request-signature__help-link', { + onClick: () => { + global.platform.openWindow({ + url: 'https://consensys.zendesk.com/hc/en-us/articles/360004427792', + }) + }, + }, this.context.t('learnMore'))] } return h('div.request-signature__body', {}, [ diff --git a/ui/app/css/itcss/components/request-signature.scss b/ui/app/css/itcss/components/request-signature.scss index e6916c418..4707ff60e 100644 --- a/ui/app/css/itcss/components/request-signature.scss +++ b/ui/app/css/itcss/components/request-signature.scss @@ -183,6 +183,12 @@ padding: 6px 18px 15px; } + &__help-link { + cursor: pointer; + text-decoration: underline; + color: $curious-blue; + } + &__footer { width: 100%; display: flex; -- cgit From 5995b6d68dccb8d45a14b0665664717b21be5b8b Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 13 Jun 2018 09:26:21 -0230 Subject: ENS name revalidates on network change. --- ui/app/components/ens-input.js | 56 +++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 23 deletions(-) (limited to 'ui') diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index aff4b6ef6..1be6d798a 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -25,31 +25,33 @@ function EnsInput () { Component.call(this) } -EnsInput.prototype.render = function () { - const props = this.props - const opts = extend(props, { - list: 'addresses', - onChange: (recipient) => { - const network = this.props.network - const networkHasEnsSupport = getNetworkEnsSupport(network) +EnsInput.prototype.onChange = function (recipient) { + const network = this.props.network + const networkHasEnsSupport = getNetworkEnsSupport(network) - props.onChange(recipient) + this.props.onChange(recipient) - if (!networkHasEnsSupport) return + if (!networkHasEnsSupport) return - if (recipient.match(ensRE) === null) { - return this.setState({ - loadingEns: false, - ensResolution: null, - ensFailure: null, - }) - } + if (recipient.match(ensRE) === null) { + return this.setState({ + loadingEns: false, + ensResolution: null, + ensFailure: null, + }) + } - this.setState({ - loadingEns: true, - }) - this.checkName(recipient) - }, + this.setState({ + loadingEns: true, + }) + this.checkName(recipient) +} + +EnsInput.prototype.render = function () { + const props = this.props + const opts = extend(props, { + list: 'addresses', + onChange: this.onChange.bind(this), }) return h('div', { style: { width: '100%', position: 'relative' }, @@ -89,10 +91,13 @@ EnsInput.prototype.lookupEnsName = function (recipient) { } }) .catch((reason) => { - log.error(reason) + // log.error(reason) + if (reason.message !== 'ENS name not defined.') { + log.error(reason) + } return this.setState({ loadingEns: false, - ensResolution: ZERO_ADDRESS, + ensResolution: recipient, ensFailure: true, hoverText: reason.message, }) @@ -105,6 +110,11 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) { // If an address is sent without a nickname, meaning not from ENS or from // the user's own accounts, a default of a one-space string is used. const nickname = state.nickname || ' ' + if (prevProps.network !== this.props.network) { + const provider = global.ethereumProvider + this.ens = new ENS({ provider, network: this.props.network }) + this.onChange(ensResolution) + } if (prevState && ensResolution && this.props.onChange && ensResolution !== prevState.ensResolution) { this.props.onChange(ensResolution, nickname) -- cgit From 44a8e48a04ea69e1f8e530ae1bacf55890f8df98 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Jun 2018 23:30:31 -0700 Subject: notices - replace getLatestNotice with getNextNotice --- ui/app/app.js | 4 ++-- ui/app/components/pages/home.js | 8 ++++---- ui/app/components/pages/notice.js | 12 ++++++------ ui/app/reducers/metamask.js | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index ec2329463..d0e48a368 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -314,7 +314,7 @@ function mapStateToProps (state) { noActiveNotices, seedWords, unapprovedTxs, - lastUnreadNotice, + nextUnreadNotice, lostAccounts, unapprovedMsgCount, unapprovedPersonalMsgCount, @@ -348,7 +348,7 @@ function mapStateToProps (state) { network: state.metamask.network, provider: state.metamask.provider, forgottenPassword: state.appState.forgottenPassword, - lastUnreadNotice, + nextUnreadNotice, lostAccounts, frequentRpcList: state.metamask.frequentRpcList || [], currentCurrency: state.metamask.currentCurrency, diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js index 9110f8202..c53413d3b 100644 --- a/ui/app/components/pages/home.js +++ b/ui/app/components/pages/home.js @@ -86,9 +86,9 @@ class Home extends Component { // if (!props.noActiveNotices) { // log.debug('rendering notice screen for unread notices.') // return h(NoticeScreen, { - // notice: props.lastUnreadNotice, + // notice: props.nextUnreadNotice, // key: 'NoticeScreen', - // onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)), + // onConfirm: () => props.dispatch(actions.markNoticeRead(props.nextUnreadNotice)), // }) // } else if (props.lostAccounts && props.lostAccounts.length > 0) { // log.debug('rendering notice screen for lost accounts view.') @@ -279,7 +279,7 @@ function mapStateToProps (state) { noActiveNotices, seedWords, unapprovedTxs, - lastUnreadNotice, + nextUnreadNotice, lostAccounts, unapprovedMsgCount, unapprovedPersonalMsgCount, @@ -313,7 +313,7 @@ function mapStateToProps (state) { network: state.metamask.network, provider: state.metamask.provider, forgottenPassword: state.appState.forgottenPassword, - lastUnreadNotice, + nextUnreadNotice, lostAccounts, frequentRpcList: state.metamask.frequentRpcList || [], currentCurrency: state.metamask.currentCurrency, diff --git a/ui/app/components/pages/notice.js b/ui/app/components/pages/notice.js index 2329a9147..a9077b98b 100644 --- a/ui/app/components/pages/notice.js +++ b/ui/app/components/pages/notice.js @@ -154,11 +154,11 @@ class Notice extends Component { const mapStateToProps = state => { const { metamask } = state - const { noActiveNotices, lastUnreadNotice, lostAccounts } = metamask + const { noActiveNotices, nextUnreadNotice, lostAccounts } = metamask return { noActiveNotices, - lastUnreadNotice, + nextUnreadNotice, lostAccounts, } } @@ -171,21 +171,21 @@ Notice.propTypes = { const mapDispatchToProps = dispatch => { return { - markNoticeRead: lastUnreadNotice => dispatch(actions.markNoticeRead(lastUnreadNotice)), + markNoticeRead: nextUnreadNotice => dispatch(actions.markNoticeRead(nextUnreadNotice)), markAccountsFound: () => dispatch(actions.markAccountsFound()), } } const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { noActiveNotices, lastUnreadNotice, lostAccounts } = stateProps + const { noActiveNotices, nextUnreadNotice, lostAccounts } = stateProps const { markNoticeRead, markAccountsFound } = dispatchProps let notice let onConfirm if (!noActiveNotices) { - notice = lastUnreadNotice - onConfirm = () => markNoticeRead(lastUnreadNotice) + notice = nextUnreadNotice + onConfirm = () => markNoticeRead(nextUnreadNotice) } else if (lostAccounts && lostAccounts.length > 0) { notice = generateLostAccountsNotice(lostAccounts) onConfirm = () => markAccountsFound() diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index a4d1aece5..6c8ac9ed7 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -21,7 +21,7 @@ function reduceMetamask (state, action) { identities: {}, unapprovedTxs: {}, noActiveNotices: true, - lastUnreadNotice: undefined, + nextUnreadNotice: undefined, frequentRpcList: [], addressBook: [], selectedTokenAddress: null, @@ -65,7 +65,7 @@ function reduceMetamask (state, action) { case actions.SHOW_NOTICE: return extend(metamaskState, { noActiveNotices: false, - lastUnreadNotice: action.value, + nextUnreadNotice: action.value, }) case actions.CLEAR_NOTICES: -- cgit From bb855707efbcb754f5e4ee4e124f69308bca037d Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 14 Jun 2018 11:25:55 -0230 Subject: ENS input in send form shows distinct errors for invalid addresses and non-existent addresses. --- ui/app/components/ens-input.js | 30 +++++++++++++++------- .../send-to-row/send-to-row.component.js | 6 ++--- .../send-content/send-to-row/send-to-row.utils.js | 6 ++--- .../tests/send-to-row-component.test.js | 12 ++++----- .../send-to-row/tests/send-to-row-utils.test.js | 6 +++++ ui/app/util.js | 5 ++++ 6 files changed, 43 insertions(+), 22 deletions(-) (limited to 'ui') diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 1be6d798a..7873e81e0 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -12,6 +12,7 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' const connect = require('react-redux').connect const ToAutoComplete = require('./send/to-autocomplete') const log = require('loglevel') +const { isValidENSAddress } = require('../util') EnsInput.contextTypes = { t: PropTypes.func, @@ -29,7 +30,7 @@ EnsInput.prototype.onChange = function (recipient) { const network = this.props.network const networkHasEnsSupport = getNetworkEnsSupport(network) - this.props.onChange(recipient) + this.props.onChange({ toAddress: recipient }) if (!networkHasEnsSupport) return @@ -38,6 +39,7 @@ EnsInput.prototype.onChange = function (recipient) { loadingEns: false, ensResolution: null, ensFailure: null, + toError: null, }) } @@ -87,20 +89,28 @@ EnsInput.prototype.lookupEnsName = function (recipient) { nickname: recipient.trim(), hoverText: address + '\n' + this.context.t('clickCopy'), ensFailure: false, + toError: null, }) } }) .catch((reason) => { // log.error(reason) - if (reason.message !== 'ENS name not defined.') { - log.error(reason) - } - return this.setState({ + const setStateObj = { loadingEns: false, ensResolution: recipient, ensFailure: true, - hoverText: reason.message, - }) + toError: null, + } + if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') { + setStateObj.hoverText = this.context.t('ensNameNotFound') + setStateObj.toError = 'ensNameNotFound' + setStateObj.ensFailure = false + } else { + log.error(reason) + setStateObj.hoverText = reason.message + } + + return this.setState(setStateObj) }) } @@ -117,7 +127,7 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) { } if (prevState && ensResolution && this.props.onChange && ensResolution !== prevState.ensResolution) { - this.props.onChange(ensResolution, nickname) + this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError }) } } @@ -134,7 +144,9 @@ EnsInput.prototype.ensIcon = function (recipient) { } EnsInput.prototype.ensIconContents = function (recipient) { - const { loadingEns, ensFailure, ensResolution } = this.state || { ensResolution: ZERO_ADDRESS} + const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS } + + if (toError) return if (loadingEns) { return h('img', { diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js index 0a83186a5..1c2ecdf9c 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js @@ -19,9 +19,9 @@ export default class SendToRow extends Component { updateSendToError: PropTypes.func, }; - handleToChange (to, nickname = '') { + handleToChange (to, nickname = '', toError) { const { updateSendTo, updateSendToError, updateGas } = this.props - const toErrorObject = getToErrorObject(to) + const toErrorObject = getToErrorObject(to, toError) updateSendTo(to, nickname) updateSendToError(toErrorObject) if (toErrorObject.to === null) { @@ -53,7 +53,7 @@ export default class SendToRow extends Component { inError={inError} name={'address'} network={network} - onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)} + onChange={({ toAddress, nickname, toError }) => this.handleToChange(toAddress, nickname, toError)} openDropdown={() => openToDropdown()} placeholder={this.context.t('recipientAddress')} to={to} diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js index cea51ee20..6b90a9f09 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js @@ -4,12 +4,10 @@ const { } = require('../../send.constants') const { isValidAddress } = require('../../../../util') -function getToErrorObject (to) { - let toError = null - +function getToErrorObject (to, toError = null) { if (!to) { toError = REQUIRED_ERROR - } else if (!isValidAddress(to)) { + } else if (!isValidAddress(to) && !toError) { toError = INVALID_RECIPIENT_ADDRESS_ERROR } diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js index 58fe51dcf..781371004 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js @@ -6,8 +6,8 @@ import proxyquire from 'proxyquire' const SendToRow = proxyquire('../send-to-row.component.js', { './send-to-row.utils.js': { - getToErrorObject: (to) => ({ - to: to === false ? null : `mockToErrorObject:${to}`, + getToErrorObject: (to, toError) => ({ + to: to === false ? null : `mockToErrorObject:${to}${toError}`, }), }, }).default @@ -67,11 +67,11 @@ describe('SendToRow Component', function () { it('should call updateSendToError', () => { assert.equal(propsMethodSpies.updateSendToError.callCount, 0) - instance.handleToChange('mockTo2') + instance.handleToChange('mockTo2', '', 'mockToError') assert.equal(propsMethodSpies.updateSendToError.callCount, 1) assert.deepEqual( propsMethodSpies.updateSendToError.getCall(0).args, - [{ to: 'mockToErrorObject:mockTo2' }] + [{ to: 'mockToErrorObject:mockTo2mockToError' }] ) }) @@ -138,11 +138,11 @@ describe('SendToRow Component', function () { openDropdown() assert.equal(propsMethodSpies.openToDropdown.callCount, 1) assert.equal(SendToRow.prototype.handleToChange.callCount, 0) - onChange('mockNewTo', 'mockNewNickname') + onChange({ toAddress: 'mockNewTo', nickname: 'mockNewNickname', toError: 'mockToError' }) assert.equal(SendToRow.prototype.handleToChange.callCount, 1) assert.deepEqual( SendToRow.prototype.handleToChange.getCall(0).args, - ['mockNewTo', 'mockNewNickname'] + ['mockNewTo', 'mockNewNickname', 'mockToError'] ) }) }) diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js index 615c9581b..4d2447c32 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js @@ -40,6 +40,12 @@ describe('send-to-row utils', () => { to: null, }) }) + + it('should return the passed error if to is truthy but invalid if to is truthy and valid', () => { + assert.deepEqual(getToErrorObject('invalid #$ 345878', 'someExplicitError'), { + to: 'someExplicitError', + }) + }) }) }) diff --git a/ui/app/util.js b/ui/app/util.js index 1ccd17ba7..8c85c5926 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -36,6 +36,7 @@ module.exports = { miniAddressSummary: miniAddressSummary, isAllOneCase: isAllOneCase, isValidAddress: isValidAddress, + isValidENSAddress, numericBalance: numericBalance, parseBalance: parseBalance, formatBalance: formatBalance, @@ -87,6 +88,10 @@ function isValidAddress (address) { return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) } +function isValidENSAddress (address) { + return address.match(/^.{7,}\.(eth|test)$/) +} + function isInvalidChecksumAddress (address) { var prefixed = ethUtil.addHexPrefix(address) if (address === '0x0000000000000000000000000000000000000000') return false -- cgit From 1b9ed237527b9970c3dfcbb0234cfdb2ffa8c807 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 14 Jun 2018 14:04:14 -0700 Subject: Add hex-prefix to gas estimate result --- ui/app/components/send_/send.utils.js | 4 ++-- ui/app/components/send_/tests/send-utils.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 67699be77..04a41456c 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -201,12 +201,12 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, err.message.includes('gas required exceeds allowance or always failing transaction') ) if (simulationFailed) { - return resolve(paramsForGasEstimate.gas) + return resolve(ethUtil.addHexPrefix(paramsForGasEstimate.gas)) } else { return reject(err) } } - return resolve(estimatedGas.toString(16)) + return resolve(ethUtil.addHexPrefix(estimatedGas.toString(16))) }) }) } diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index b3f6372ef..6db875342 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -285,7 +285,7 @@ describe('send utils', () => { baseMockParams.estimateGasMethod.getCall(0).args[0], Object.assign({ gasPrice: undefined, value: undefined }, baseExpectedCall) ) - assert.equal(result, 'mockToString:16') + assert.equal(result, '0xmockToString:16') }) it('should call ethQuery.estimateGas with a value of 0x0 and the expected data and to if passed a selectedToken', async () => { @@ -300,7 +300,7 @@ describe('send utils', () => { to: 'mockAddress', }) ) - assert.equal(result, 'mockToString:16') + assert.equal(result, '0xmockToString:16') }) it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async () => { -- cgit From b9b6cbf561a2efc983680c30064ee3beb64402c4 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 14 Jun 2018 22:32:03 -0230 Subject: Add a buffer to new ui gas estimates --- ui/app/components/send_/send.utils.js | 43 +++++++++++++++++++++++++++++++++-- ui/app/conversion-util.js | 11 +++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 04a41456c..3df1506dc 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -4,6 +4,7 @@ const { conversionGTE, multiplyCurrencies, conversionGreaterThan, + conversionLessThan, } = require('../../conversion-util') const { calcTokenAmount, @@ -201,16 +202,54 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, err.message.includes('gas required exceeds allowance or always failing transaction') ) if (simulationFailed) { - return resolve(ethUtil.addHexPrefix(paramsForGasEstimate.gas)) + const estimateWithBuffer = addGasBuffer( + paramsForGasEstimate.gas, + blockGasLimit, + selectedToken ? 2 : 1.5 + ) + return resolve(ethUtil.addHexPrefix(estimateWithBuffer)) } else { return reject(err) } } - return resolve(ethUtil.addHexPrefix(estimatedGas.toString(16))) + const estimateWithBuffer = addGasBuffer( + estimatedGas.toString(16), + blockGasLimit, + selectedToken ? 2 : 1.5 + ) + return resolve(ethUtil.addHexPrefix(estimateWithBuffer)) }) }) } +function addGasBuffer (initialGasLimitHex, blockGasLimitHex, bufferMultiplier = 1.5) { + const upperGasLimit = multiplyCurrencies(blockGasLimitHex, 0.9, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 10, + numberOfDecimals: '0', + }) + const bufferedGasLimit = multiplyCurrencies(initialGasLimitHex, bufferMultiplier, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 10, + numberOfDecimals: '0', + }) + + // if initialGasLimit is above blockGasLimit, dont modify it + if (conversionGreaterThan( + { value: initialGasLimitHex, fromNumericBase: 'hex' }, + { value: upperGasLimit, fromNumericBase: 'hex' }, + )) return initialGasLimitHex + // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit + if (conversionLessThan( + { value: bufferedGasLimit, fromNumericBase: 'hex' }, + { value: upperGasLimit, fromNumericBase: 'hex' }, + )) return bufferedGasLimit + // otherwise use blockGasLimit + return upperGasLimit +} + function generateTokenTransferData ({ toAddress = '0x0', amount = '0x0', selectedToken }) { if (!selectedToken) return return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call( diff --git a/ui/app/conversion-util.js b/ui/app/conversion-util.js index 100402d95..337763067 100644 --- a/ui/app/conversion-util.js +++ b/ui/app/conversion-util.js @@ -190,6 +190,16 @@ const conversionGreaterThan = ( return firstValue.gt(secondValue) } +const conversionLessThan = ( + { ...firstProps }, + { ...secondProps }, +) => { + const firstValue = converter({ ...firstProps }) + const secondValue = converter({ ...secondProps }) + + return firstValue.lt(secondValue) +} + const conversionMax = ( { ...firstProps }, { ...secondProps }, @@ -229,6 +239,7 @@ module.exports = { addCurrencies, multiplyCurrencies, conversionGreaterThan, + conversionLessThan, conversionGTE, conversionLTE, conversionMax, -- cgit From e4d3bdba125964042480e35847747903f3de7ac3 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 14 Jun 2018 23:54:48 -0230 Subject: Update send.utils.js estimateGas tests. --- ui/app/components/send_/send.utils.js | 1 + ui/app/components/send_/tests/send-utils.test.js | 24 +++++++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 3df1506dc..8772d464b 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -21,6 +21,7 @@ const abi = require('ethereumjs-abi') const ethUtil = require('ethereumjs-util') module.exports = { + addGasBuffer, calcGasTotal, calcTokenBalance, doesAmountErrorRequireUpdate, diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index 6db875342..00804d074 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -18,10 +18,12 @@ const { const stubs = { addCurrencies: sinon.stub().callsFake((a, b, obj) => a + b), conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)), - conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value > obj2.value), + conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value >= obj2.value), multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`), calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d), rawEncode: sinon.stub().returns([16, 1100]), + conversionGreaterThan: sinon.stub().callsFake((obj1, obj2) => obj1.value > obj2.value), + conversionLessThan: sinon.stub().callsFake((obj1, obj2) => obj1.value < obj2.value), } const sendUtils = proxyquire('../send.utils.js', { @@ -30,6 +32,8 @@ const sendUtils = proxyquire('../send.utils.js', { conversionUtil: stubs.conversionUtil, conversionGTE: stubs.conversionGTE, multiplyCurrencies: stubs.multiplyCurrencies, + conversionGreaterThan: stubs.conversionGreaterThan, + conversionLessThan: stubs.conversionLessThan, }, '../../token-util': { calcTokenAmount: stubs.calcTokenAmount }, 'ethereumjs-abi': { @@ -255,7 +259,7 @@ describe('send utils', () => { estimateGasMethod: sinon.stub().callsFake( (data, cb) => cb( data.to.match(/willFailBecauseOf:/) ? { message: data.to.match(/:(.+)$/)[1] } : null, - { toString: (n) => `mockToString:${n}` } + { toString: (n) => `0xabc${n}` } ) ), } @@ -279,13 +283,23 @@ describe('send utils', () => { }) it('should call ethQuery.estimateGas with the expected params', async () => { - const result = await estimateGas(baseMockParams) + const result = await sendUtils.estimateGas(baseMockParams) assert.equal(baseMockParams.estimateGasMethod.callCount, 1) assert.deepEqual( baseMockParams.estimateGasMethod.getCall(0).args[0], Object.assign({ gasPrice: undefined, value: undefined }, baseExpectedCall) ) - assert.equal(result, '0xmockToString:16') + assert.equal(result, '0xabc16') + }) + + it('should call ethQuery.estimateGas with the expected params when initialGasLimitHex is lower than the upperGasLimit', async () => { + const result = await estimateGas(Object.assign({}, baseMockParams, { blockGasLimit: '0xbcd' })) + assert.equal(baseMockParams.estimateGasMethod.callCount, 1) + assert.deepEqual( + baseMockParams.estimateGasMethod.getCall(0).args[0], + Object.assign({ gasPrice: undefined, value: undefined }, baseExpectedCall, { gas: '0xbcdx0.95' }) + ) + assert.equal(result, '0xabc16x1.5') }) it('should call ethQuery.estimateGas with a value of 0x0 and the expected data and to if passed a selectedToken', async () => { @@ -300,7 +314,7 @@ describe('send utils', () => { to: 'mockAddress', }) ) - assert.equal(result, '0xmockToString:16') + assert.equal(result, '0xabc16') }) it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async () => { -- cgit From 5685c4bafed0458e350b401791e108b8162a88e0 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 15 Jun 2018 14:36:52 -0700 Subject: Estimate gas limit when the token amount changes. Fix amount input --- .../send-amount-row/send-amount-row.component.js | 16 +++++++++++++--- .../send_/send-content/send-content.component.js | 2 +- ui/app/components/send_/send.component.js | 4 ++-- ui/app/components/send_/send.utils.js | 12 ++---------- 4 files changed, 18 insertions(+), 16 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index 8aefeed4a..e6cb405e9 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -23,6 +23,7 @@ export default class SendAmountRow extends Component { tokenBalance: PropTypes.string, updateSendAmount: PropTypes.func, updateSendAmountError: PropTypes.func, + updateGas: PropTypes.func, } validateAmount (amount) { @@ -54,6 +55,15 @@ export default class SendAmountRow extends Component { setMaxModeTo(false) updateSendAmount(amount) + this.validateAmount(amount) + } + + updateGas (amount) { + const { selectedToken, updateGas } = this.props + + if (selectedToken) { + updateGas({ amount }) + } } render () { @@ -77,12 +87,12 @@ export default class SendAmountRow extends Component { this.updateAmount(newAmount)} - onChange={newAmount => this.validateAmount(newAmount)} + onBlur={newAmount => this.updateGas(newAmount)} + onChange={newAmount => this.updateAmount(newAmount)} inError={inError} primaryCurrency={primaryCurrency || 'ETH'} selectedToken={selectedToken} - value={amount || '0x0'} + value={amount} /> ) diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js index 3a14054eb..adc114c0e 100644 --- a/ui/app/components/send_/send-content/send-content.component.js +++ b/ui/app/components/send_/send-content/send-content.component.js @@ -18,7 +18,7 @@ export default class SendContent extends Component {
this.props.updateGas(updateData)} /> - + this.props.updateGas(updateData)} />
diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index 516251e22..38da4910b 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -38,7 +38,7 @@ export default class SendTransactionScreen extends PersistentForm { updateSendTokenBalance: PropTypes.func, }; - updateGas ({ to } = {}) { + updateGas ({ to, amount: value } = {}) { const { amount, blockGasLimit, @@ -60,7 +60,7 @@ export default class SendTransactionScreen extends PersistentForm { selectedAddress, selectedToken, to: to && to.toLowerCase(), - value: amount, + value: value || amount, }) } diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 8772d464b..3d8e1a882 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -203,21 +203,13 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, err.message.includes('gas required exceeds allowance or always failing transaction') ) if (simulationFailed) { - const estimateWithBuffer = addGasBuffer( - paramsForGasEstimate.gas, - blockGasLimit, - selectedToken ? 2 : 1.5 - ) + const estimateWithBuffer = addGasBuffer(paramsForGasEstimate.gas, blockGasLimit, 1.5) return resolve(ethUtil.addHexPrefix(estimateWithBuffer)) } else { return reject(err) } } - const estimateWithBuffer = addGasBuffer( - estimatedGas.toString(16), - blockGasLimit, - selectedToken ? 2 : 1.5 - ) + const estimateWithBuffer = addGasBuffer(estimatedGas.toString(16), blockGasLimit, 1.5) return resolve(ethUtil.addHexPrefix(estimateWithBuffer)) }) }) -- cgit From adb71073c8c3d2e3f76dd7591e87d4b64074c24e Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 15 Jun 2018 15:31:16 -0700 Subject: Fix unit tests --- .../send-amount-row/tests/send-amount-row-component.test.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js index 2205579ca..6e6c80890 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js @@ -12,10 +12,12 @@ const propsMethodSpies = { setMaxModeTo: sinon.spy(), updateSendAmount: sinon.spy(), updateSendAmountError: sinon.spy(), + updateGas: sinon.spy(), } sinon.spy(SendAmountRow.prototype, 'updateAmount') sinon.spy(SendAmountRow.prototype, 'validateAmount') +sinon.spy(SendAmountRow.prototype, 'updateGas') describe('SendAmountRow Component', function () { let wrapper @@ -36,6 +38,7 @@ describe('SendAmountRow Component', function () { tokenBalance={'mockTokenBalance'} updateSendAmount={propsMethodSpies.updateSendAmount} updateSendAmountError={propsMethodSpies.updateSendAmountError} + updateGas={propsMethodSpies.updateGas} />, { context: { t: str => str + '_t' } }) instance = wrapper.instance() }) @@ -139,15 +142,17 @@ describe('SendAmountRow Component', function () { assert.equal(primaryCurrency, 'mockPrimaryCurrency') assert.deepEqual(selectedToken, { address: 'mockTokenAddress' }) assert.equal(value, 'mockAmount') - assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0) + assert.equal(SendAmountRow.prototype.updateGas.callCount, 0) onBlur('mockNewAmount') - assert.equal(SendAmountRow.prototype.updateAmount.callCount, 1) + assert.equal(SendAmountRow.prototype.updateGas.callCount, 1) assert.deepEqual( - SendAmountRow.prototype.updateAmount.getCall(0).args, + SendAmountRow.prototype.updateGas.getCall(0).args, ['mockNewAmount'] ) + assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0) assert.equal(SendAmountRow.prototype.validateAmount.callCount, 0) onChange('mockNewAmount') + assert.equal(SendAmountRow.prototype.updateAmount.callCount, 1) assert.equal(SendAmountRow.prototype.validateAmount.callCount, 1) assert.deepEqual( SendAmountRow.prototype.validateAmount.getCall(0).args, -- cgit From 347d1984cf01b7a1a6af0415c8ec6b1eb4c7a7be Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 18 Jun 2018 13:47:14 -0230 Subject: Revert to updating amount on blur of input change; update gas on blur of amount input change; validate onchange. --- .../send-content/send-amount-row/send-amount-row.component.js | 8 +++++--- .../send-amount-row/tests/send-amount-row-component.test.js | 8 ++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index e6cb405e9..8da36d3b7 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -55,7 +55,6 @@ export default class SendAmountRow extends Component { setMaxModeTo(false) updateSendAmount(amount) - this.validateAmount(amount) } updateGas (amount) { @@ -87,8 +86,11 @@ export default class SendAmountRow extends Component { this.updateGas(newAmount)} - onChange={newAmount => this.updateAmount(newAmount)} + onBlur={newAmount => { + this.updateGas(newAmount) + this.updateAmount(newAmount) + }} + onChange={newAmount => this.validateAmount(newAmount)} inError={inError} primaryCurrency={primaryCurrency || 'ETH'} selectedToken={selectedToken} diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js index 6e6c80890..579e18585 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js @@ -143,16 +143,20 @@ describe('SendAmountRow Component', function () { assert.deepEqual(selectedToken, { address: 'mockTokenAddress' }) assert.equal(value, 'mockAmount') assert.equal(SendAmountRow.prototype.updateGas.callCount, 0) + assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0) onBlur('mockNewAmount') assert.equal(SendAmountRow.prototype.updateGas.callCount, 1) assert.deepEqual( SendAmountRow.prototype.updateGas.getCall(0).args, ['mockNewAmount'] ) - assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0) + assert.equal(SendAmountRow.prototype.updateAmount.callCount, 1) + assert.deepEqual( + SendAmountRow.prototype.updateAmount.getCall(0).args, + ['mockNewAmount'] + ) assert.equal(SendAmountRow.prototype.validateAmount.callCount, 0) onChange('mockNewAmount') - assert.equal(SendAmountRow.prototype.updateAmount.callCount, 1) assert.equal(SendAmountRow.prototype.validateAmount.callCount, 1) assert.deepEqual( SendAmountRow.prototype.validateAmount.getCall(0).args, -- cgit From 36de213c6c5eda192d4160d650004523d303379c Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 18 Jun 2018 13:49:21 -0230 Subject: Fix currency-display.js rendering of token amounts. --- ui/app/components/send/currency-display.js | 1 + 1 file changed, 1 insertion(+) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 3bc9ad226..9866393d8 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -57,6 +57,7 @@ CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversi return selectedToken ? conversionUtil(ethUtil.addHexPrefix(value), { fromNumericBase: 'hex', + toNumericBase: 'dec', toCurrency: symbol, conversionRate: multiplier, invertConversionRate: true, -- cgit From 6c9302f9853d931ecf2f4385fa77b78d367f89e2 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 18 Jun 2018 13:50:21 -0230 Subject: Remove leading zeroes from send screen amount row input. --- ui/app/components/send/currency-display.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 9866393d8..e410bc070 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -92,8 +92,12 @@ CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValu : convertedValue } +function removeLeadingZeroes (str) { + return str.replace(/^0*(?=\d)/, '') +} + CurrencyDisplay.prototype.handleChange = function (newVal) { - this.setState({ valueToRender: newVal }) + this.setState({ valueToRender: removeLeadingZeroes(newVal) }) this.props.onChange(this.getAmount(newVal)) } -- cgit From 70abe54c94d8c08aa1b73fd63f34b65bc3dff117 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 18 Jun 2018 13:55:20 -0230 Subject: Send screen returns simple gas cost if no to address specified when not sending token. --- ui/app/components/send_/send.utils.js | 5 ++--- ui/app/components/send_/tests/send-utils.test.js | 6 ++++++ 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 3d8e1a882..9b26b4e32 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -177,9 +177,8 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, } // if recipient has no code, gas is 21k max: - const hasRecipient = Boolean(to) - if (hasRecipient && !selectedToken) { - const code = await global.eth.getCode(to) + if (!selectedToken) { + const code = Boolean(to) && await global.eth.getCode(to) if (!code || code === '0x') { return SIMPLE_GAS_COST } diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index 00804d074..facc0e518 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -323,6 +323,12 @@ describe('send utils', () => { assert.equal(result, SIMPLE_GAS_COST) }) + it(`should return ${SIMPLE_GAS_COST} if not passed a selectedToken or truthy to address`, async () => { + assert.equal(baseMockParams.estimateGasMethod.callCount, 0) + const result = await estimateGas(Object.assign({}, baseMockParams, { to: null })) + assert.equal(result, SIMPLE_GAS_COST) + }) + it(`should not return ${SIMPLE_GAS_COST} if passed a selectedToken`, async () => { assert.equal(baseMockParams.estimateGasMethod.callCount, 0) const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123', selectedToken: { address: '' } })) -- cgit From ac7c0277b503c7660d6894a9039d35c8713f52ab Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 18 Jun 2018 14:07:01 -0230 Subject: On send screen amount change, updateGas call now includes current to address. --- ui/app/components/send_/send.component.js | 6 ++++-- ui/app/components/send_/send.container.js | 2 ++ ui/app/components/send_/send.utils.js | 5 +++++ ui/app/components/send_/tests/send-component.test.js | 14 ++++++++++++-- ui/app/components/send_/tests/send-container.test.js | 2 ++ ui/app/components/send_/tests/send-utils.test.js | 12 ++++++++++++ 6 files changed, 37 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index 38da4910b..219b362f2 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types' import PersistentForm from '../../../lib/persistent-form' import { getAmountErrorObject, + getToAddressForGasUpdate, doesAmountErrorRequireUpdate, } from './send.utils' @@ -38,7 +39,7 @@ export default class SendTransactionScreen extends PersistentForm { updateSendTokenBalance: PropTypes.func, }; - updateGas ({ to, amount: value } = {}) { + updateGas ({ to: updatedToAddress, amount: value } = {}) { const { amount, blockGasLimit, @@ -48,6 +49,7 @@ export default class SendTransactionScreen extends PersistentForm { recentBlocks, selectedAddress, selectedToken = {}, + to: currentToAddress, updateAndSetGasTotal, } = this.props @@ -59,7 +61,7 @@ export default class SendTransactionScreen extends PersistentForm { recentBlocks, selectedAddress, selectedToken, - to: to && to.toLowerCase(), + to: getToAddressForGasUpdate(updatedToAddress, currentToAddress), value: value || amount, }) } diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js index 1fd96d61f..185653c5f 100644 --- a/ui/app/components/send_/send.container.js +++ b/ui/app/components/send_/send.container.js @@ -19,6 +19,7 @@ import { getSendAmount, getSendEditingTransactionId, getSendFromObject, + getSendTo, getTokenBalance, } from './send.selectors' import { @@ -54,6 +55,7 @@ function mapStateToProps (state) { recentBlocks: getRecentBlocks(state), selectedAddress: getSelectedAddress(state), selectedToken: getSelectedToken(state), + to: getSendTo(state), tokenBalance: getTokenBalance(state), tokenContract: getSelectedTokenContract(state), tokenToFiatRate: getSelectedTokenToFiatRate(state), diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 9b26b4e32..dfd459731 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -29,6 +29,7 @@ module.exports = { estimateGasPriceFromRecentBlocks, generateTokenTransferData, getAmountErrorObject, + getToAddressForGasUpdate, isBalanceSufficient, isTokenBalanceSufficient, } @@ -268,3 +269,7 @@ function estimateGasPriceFromRecentBlocks (recentBlocks) { return lowestPrices[Math.floor(lowestPrices.length / 2)] } + +function getToAddressForGasUpdate (...addresses) { + return [...addresses, ''].find(str => str !== undefined && str !== null).toLowerCase() +} diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js index 4e33d8f63..4ba9b226d 100644 --- a/ui/app/components/send_/tests/send-component.test.js +++ b/ui/app/components/send_/tests/send-component.test.js @@ -201,7 +201,7 @@ describe('Send Component', function () { }) describe('updateGas', () => { - it('should call updateAndSetGasTotal with the correct params', () => { + it('should call updateAndSetGasTotal with the correct params if no to prop is passed', () => { propsMethodSpies.updateAndSetGasTotal.resetHistory() wrapper.instance().updateGas() assert.equal(propsMethodSpies.updateAndSetGasTotal.callCount, 1) @@ -215,12 +215,22 @@ describe('Send Component', function () { recentBlocks: ['mockBlock'], selectedAddress: 'mockSelectedAddress', selectedToken: 'mockSelectedToken', - to: undefined, + to: '', value: 'mockAmount', } ) }) + it('should call updateAndSetGasTotal with the correct params if a to prop is passed', () => { + propsMethodSpies.updateAndSetGasTotal.resetHistory() + wrapper.setProps({ to: 'someAddress' }) + wrapper.instance().updateGas() + assert.equal( + propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0].to, + 'someaddress', + ) + }) + it('should call updateAndSetGasTotal with to set to lowercase if passed', () => { propsMethodSpies.updateAndSetGasTotal.resetHistory() wrapper.instance().updateGas({ to: '0xABC' }) diff --git a/ui/app/components/send_/tests/send-container.test.js b/ui/app/components/send_/tests/send-container.test.js index 056aad148..91484f4d8 100644 --- a/ui/app/components/send_/tests/send-container.test.js +++ b/ui/app/components/send_/tests/send-container.test.js @@ -39,6 +39,7 @@ proxyquire('../send.container.js', { getSelectedTokenContract: (s) => `mockTokenContract:${s}`, getSelectedTokenToFiatRate: (s) => `mockTokenToFiatRate:${s}`, getSendAmount: (s) => `mockAmount:${s}`, + getSendTo: (s) => `mockTo:${s}`, getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`, getSendFromObject: (s) => `mockFrom:${s}`, getTokenBalance: (s) => `mockTokenBalance:${s}`, @@ -70,6 +71,7 @@ describe('send container', () => { recentBlocks: 'mockRecentBlocks:mockState', selectedAddress: 'mockSelectedAddress:mockState', selectedToken: 'mockSelectedToken:mockState', + to: 'mockTo:mockState', tokenBalance: 'mockTokenBalance:mockState', tokenContract: 'mockTokenContract:mockState', tokenToFiatRate: 'mockTokenToFiatRate:mockState', diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index facc0e518..f3d5674b7 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -48,6 +48,7 @@ const { estimateGasPriceFromRecentBlocks, generateTokenTransferData, getAmountErrorObject, + getToAddressForGasUpdate, calcTokenBalance, isBalanceSufficient, isTokenBalanceSufficient, @@ -421,4 +422,15 @@ describe('send utils', () => { assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), '0x5') }) }) + + describe('getToAddressForGasUpdate()', () => { + it('should return empty string if all params are undefined or null', () => { + assert.equal(getToAddressForGasUpdate(undefined, null), '') + }) + + it('should return the first string that is not defined or null in lower case', () => { + assert.equal(getToAddressForGasUpdate('A', null), 'a') + assert.equal(getToAddressForGasUpdate(undefined, 'B'), 'b') + }) + }) }) -- cgit From 8aab4dd1fa5db1de54af7a029f0a758154572b49 Mon Sep 17 00:00:00 2001 From: Csaba S Date: Mon, 18 Jun 2018 19:29:09 +0200 Subject: View address of an added token (#4591) * adding menu actions for tokens * apply common style --- ui/app/components/dropdowns/token-menu-dropdown.js | 53 +++++++++++++++------- ui/app/css/itcss/components/token-list.scss | 6 +-- 2 files changed, 37 insertions(+), 22 deletions(-) (limited to 'ui') diff --git a/ui/app/components/dropdowns/token-menu-dropdown.js b/ui/app/components/dropdowns/token-menu-dropdown.js index b70d0b893..fac7c451b 100644 --- a/ui/app/components/dropdowns/token-menu-dropdown.js +++ b/ui/app/components/dropdowns/token-menu-dropdown.js @@ -4,14 +4,21 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../actions') - +const genAccountLink = require('etherscan-link').createAccountLink +const copyToClipboard = require('copy-to-clipboard') +const { Menu, Item, CloseArea } = require('./components/menu') TokenMenuDropdown.contextTypes = { t: PropTypes.func, } -module.exports = connect(null, mapDispatchToProps)(TokenMenuDropdown) +module.exports = connect(mapStateToProps, mapDispatchToProps)(TokenMenuDropdown) +function mapStateToProps (state) { + return { + network: state.metamask.network, + } +} function mapDispatchToProps (dispatch) { return { @@ -37,22 +44,34 @@ TokenMenuDropdown.prototype.onClose = function (e) { TokenMenuDropdown.prototype.render = function () { const { showHideTokenConfirmationModal } = this.props - return h('div.token-menu-dropdown', {}, [ - h('div.token-menu-dropdown__close-area', { + return h(Menu, { className: 'token-menu-dropdown', isShowing: true }, [ + h(CloseArea, { onClick: this.onClose, }), - h('div.token-menu-dropdown__container', {}, [ - h('div.token-menu-dropdown__options', {}, [ - - h('div.token-menu-dropdown__option', { - onClick: (e) => { - e.stopPropagation() - showHideTokenConfirmationModal(this.props.token) - this.props.onClose() - }, - }, this.context.t('hideToken')), - - ]), - ]), + h(Item, { + onClick: (e) => { + e.stopPropagation() + showHideTokenConfirmationModal(this.props.token) + this.props.onClose() + }, + text: this.context.t('hideToken'), + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + copyToClipboard(this.props.token.address) + this.props.onClose() + }, + text: this.context.t('copyContractAddress'), + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + const url = genAccountLink(this.props.token.address, this.props.network) + global.platform.openWindow({ url }) + this.props.onClose() + }, + text: this.context.t('viewOnEtherscan'), + }), ]) } diff --git a/ui/app/css/itcss/components/token-list.scss b/ui/app/css/itcss/components/token-list.scss index 72fda372f..4b706abce 100644 --- a/ui/app/css/itcss/components/token-list.scss +++ b/ui/app/css/itcss/components/token-list.scss @@ -81,13 +81,9 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and ( } .token-menu-dropdown { - height: 55px; width: 80%; - border-radius: 4px; - background-color: rgba(0, 0, 0, .82); - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5); position: absolute; - top: 60px; + top: 52px; right: 25px; z-index: 2000; -- cgit From 957d729c17fcf49881d5e403afb9bfc9d0fd852a Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 19 Jun 2018 13:23:33 -0230 Subject: Only show the customize gas modal if the estimateGas call is not currently in flight. --- ui/app/actions.js | 21 ++++++++++- ui/app/components/customize-gas-modal/index.js | 48 +++++++++++++++++++++++--- ui/app/reducers/app.js | 11 ++++++ ui/app/selectors.js | 5 +++ 4 files changed, 79 insertions(+), 6 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 1edf692b6..41fc3c504 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -175,6 +175,8 @@ var actions = { CLEAR_SEND: 'CLEAR_SEND', OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN', CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN', + GAS_LOADING_STARTED: 'GAS_LOADING_STARTED', + GAS_LOADING_FINISHED: 'GAS_LOADING_FINISHED', setGasLimit, setGasPrice, updateGasData, @@ -190,6 +192,8 @@ var actions = { updateSendErrors, clearSend, setSelectedAddress, + gasLoadingStarted, + gasLoadingFinished, // app messages confirmSeedWords: confirmSeedWords, showAccountDetail: showAccountDetail, @@ -740,8 +744,9 @@ function updateGasData ({ to, value, }) { - const estimatedGasPrice = estimateGasPriceFromRecentBlocks(recentBlocks) return (dispatch) => { + dispatch(actions.gasLoadingStarted()) + const estimatedGasPrice = estimateGasPriceFromRecentBlocks(recentBlocks) return Promise.all([ Promise.resolve(estimatedGasPrice), estimateGas({ @@ -762,14 +767,28 @@ function updateGasData ({ .then((gasEstimate) => { dispatch(actions.setGasTotal(gasEstimate)) dispatch(updateSendErrors({ gasLoadingError: null })) + dispatch(actions.gasLoadingFinished()) }) .catch(err => { log.error(err) dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' })) + dispatch(actions.gasLoadingFinished()) }) } } +function gasLoadingStarted () { + return { + type: actions.GAS_LOADING_STARTED, + } +} + +function gasLoadingFinished () { + return { + type: actions.GAS_LOADING_FINISHED, + } +} + function updateSendTokenBalance ({ selectedToken, tokenContract, diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index c8522a3c7..cd8f76ed5 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -33,6 +33,7 @@ const { const { getGasPrice, getGasLimit, + getGasIsLoading, getForceGasMin, conversionRateSelector, getSendAmount, @@ -51,6 +52,7 @@ function mapStateToProps (state) { return { gasPrice: getGasPrice(state), gasLimit: getGasLimit(state), + gasIsLoading: getGasIsLoading(state), forceGasMin: getForceGasMin(state), conversionRate, amount: getSendAmount(state), @@ -73,7 +75,7 @@ function mapDispatchToProps (dispatch) { } } -function getOriginalState (props) { +function getFreshState (props) { const gasPrice = props.gasPrice || MIN_GAS_PRICE_DEC const gasLimit = props.gasLimit || MIN_GAS_LIMIT_DEC @@ -97,7 +99,11 @@ inherits(CustomizeGasModal, Component) function CustomizeGasModal (props) { Component.call(this) - this.state = getOriginalState(props) + const originalState = getFreshState(props) + this.state = { + ...originalState, + originalState, + } } CustomizeGasModal.contextTypes = { @@ -106,6 +112,36 @@ CustomizeGasModal.contextTypes = { module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal) +CustomizeGasModal.prototype.componentWillReceiveProps = function (nextProps) { + const currentState = getFreshState(this.props) + const { + gasPrice: currentGasPrice, + gasLimit: currentGasLimit, + } = currentState + const newState = getFreshState(nextProps) + const { + gasPrice: newGasPrice, + gasLimit: newGasLimit, + gasTotal: newGasTotal, + } = newState + const gasPriceChanged = currentGasPrice !== newGasPrice + const gasLimitChanged = currentGasLimit !== newGasLimit + + if (gasPriceChanged) { + this.setState({ + gasPrice: newGasPrice, + gasTotal: newGasTotal, + priceSigZeros: '', + priceSigDec: '', + }) + } + if (gasLimitChanged) { + this.setState({ gasLimit: newGasLimit, gasTotal: newGasTotal }) + } + if (gasLimitChanged || gasPriceChanged) { + this.validate({ gasLimit: newGasLimit, gasTotal: newGasTotal }) + } +} CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { const { @@ -137,7 +173,7 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { } CustomizeGasModal.prototype.revert = function () { - this.setState(getOriginalState(this.props)) + this.setState(this.state.originalState) } CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) { @@ -233,7 +269,7 @@ CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) { } CustomizeGasModal.prototype.render = function () { - const { hideModal, forceGasMin } = this.props + const { hideModal, forceGasMin, gasIsLoading } = this.props const { gasPrice, gasLimit, gasTotal, error, priceSigZeros, priceSigDec } = this.state let convertedGasPrice = conversionUtil(gasPrice, { @@ -266,7 +302,7 @@ CustomizeGasModal.prototype.render = function () { toNumericBase: 'dec', }) - return h('div.send-v2__customize-gas', {}, [ + return !gasIsLoading && h('div.send-v2__customize-gas', {}, [ h('div.send-v2__customize-gas__content', { }, [ h('div.send-v2__customize-gas__header', {}, [ @@ -288,6 +324,7 @@ CustomizeGasModal.prototype.render = function () { onChange: value => this.convertAndSetGasPrice(value), title: this.context.t('gasPrice'), copy: this.context.t('gasPriceCalculation'), + gasIsLoading, }), h(GasModalCard, { @@ -297,6 +334,7 @@ CustomizeGasModal.prototype.render = function () { onChange: value => this.convertAndSetGasLimit(value), title: this.context.t('gasLimit'), copy: this.context.t('gasLimitCalculation'), + gasIsLoading, }), ]), diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 4e9d0848c..9cacf5fe7 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -62,6 +62,7 @@ function reduceApp (state, action) { warning: null, buyView: {}, isMouseUser: false, + gasIsLoading: false, }, state.appState) switch (action.type) { @@ -675,6 +676,16 @@ function reduceApp (state, action) { isMouseUser: action.value, }) + case actions.GAS_LOADING_STARTED: + return extend(appState, { + gasIsLoading: true, + }) + + case actions.GAS_LOADING_FINISHED: + return extend(appState, { + gasIsLoading: false, + }) + default: return appState } diff --git a/ui/app/selectors.js b/ui/app/selectors.js index a29294b86..cf0affe9c 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -16,6 +16,7 @@ const selectors = { transactionsSelector, accountsWithSendEtherInfoSelector, getCurrentAccountWithSendEtherInfo, + getGasIsLoading, getGasPrice, getGasLimit, getForceGasMin, @@ -117,6 +118,10 @@ function transactionsSelector (state) { .sort((a, b) => b.time - a.time) } +function getGasIsLoading (state) { + return state.appState.gasIsLoading +} + function getGasPrice (state) { return state.metamask.send.gasPrice } -- cgit From 1f6f4977deb19543e4a4bd22d3f5126db931c4a2 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 18 Jun 2018 21:00:44 -0230 Subject: Handle large token balances in tx list hero --- ui/app/components/identicon.js | 1 + ui/app/components/token-balance.js | 2 +- ui/app/css/itcss/components/hero-balance.scss | 15 ++++++++++++++- ui/app/css/itcss/components/newui-sections.scss | 8 +++++--- 4 files changed, 21 insertions(+), 5 deletions(-) (limited to 'ui') diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js index dce9b0449..424048745 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -36,6 +36,7 @@ IdenticonComponent.prototype.render = function () { key: 'identicon-' + address, style: { display: 'flex', + flexShrink: 0, alignItems: 'center', justifyContent: 'center', height: diameter, diff --git a/ui/app/components/token-balance.js b/ui/app/components/token-balance.js index 1900ccec7..df3bd59bb 100644 --- a/ui/app/components/token-balance.js +++ b/ui/app/components/token-balance.js @@ -34,7 +34,7 @@ TokenBalance.prototype.render = function () { return isLoading ? h('span', '') : h('span.token-balance', [ - h('span.token-balance__amount', string), + h('span.hide-text-overflow.token-balance__amount', string), !balanceOnly && h('span.token-balance__symbol', symbol), ]) } diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss index 09d66aedd..eba93ecb4 100644 --- a/ui/app/css/itcss/components/hero-balance.scss +++ b/ui/app/css/itcss/components/hero-balance.scss @@ -27,25 +27,37 @@ @media screen and (max-width: $break-small) { flex-direction: column; flex: 0 0 auto; + max-width: 100%; } @media screen and (min-width: $break-large) { flex-direction: row; flex-grow: 3; + min-width: 0; } } .balance-display { .token-amount { color: $black; + max-width: 100%; + + .token-balance { + display: flex; + } } @media screen and (max-width: $break-small) { + max-width: 100%; text-align: center; .token-amount { font-size: 1.75rem; margin-top: 1rem; + + .token-balance { + flex-direction: column; + } } .fiat-amount { @@ -56,9 +68,10 @@ } @media screen and (min-width: $break-large) { - margin-left: .8em; + margin: 0 .8em; justify-content: flex-start; align-items: flex-start; + min-width: 0; .token-amount { font-size: 1.5rem; diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index bbe0ee661..667e45ba2 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -26,14 +26,16 @@ $wallet-view-bg: $alabaster; //Account and transaction details .account-and-transaction-details { display: flex; - flex: 1 0 auto; + flex: 1 1 auto; + min-width: 0; } // tx view .tx-view { - flex: 63.5 0 66.5%; + flex: 1 1 66.5%; background: $tx-view-bg; + min-width: 0; // No title on mobile @media screen and (max-width: 575px) { @@ -286,7 +288,7 @@ $wallet-view-bg: $alabaster; } .token-balance__amount { - padding-right: 6px; + padding: 0 6px; } // first time -- cgit From 312307365981d032091e909b3194c4e20cd55e17 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 18 Jun 2018 22:08:17 -0230 Subject: Scroll large amounts in the send component --- ui/app/components/send/currency-display.js | 2 +- ui/app/css/itcss/components/currency-display.scss | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index e410bc070..9c8ce0044 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -149,7 +149,7 @@ CurrencyDisplay.prototype.render = function () { } : {}), ref: input => { this.currencyInput = input }, style: { - width: this.getInputWidth(valueToRender, readOnly), + minWidth: this.getInputWidth(valueToRender, readOnly), }, min: 0, }), diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss index 3560b0b0c..3614aa868 100644 --- a/ui/app/css/itcss/components/currency-display.scss +++ b/ui/app/css/itcss/components/currency-display.scss @@ -1,6 +1,5 @@ .currency-display { height: 54px; - width: 100%ß; border: 1px solid $alto; border-radius: 4px; background-color: $white; @@ -21,7 +20,7 @@ line-height: 22px; border: none; outline: 0 !important; - max-width: 100%; + max-width: 22ch; } &__primary-currency { @@ -47,6 +46,9 @@ &__input-wrapper { position: relative; display: flex; + flex: 1; + max-width: 100%; + overflow-x: scroll; input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; @@ -75,4 +77,4 @@ display: none; } } -} \ No newline at end of file +} -- cgit From c27dfb01bbf0d84c6f4ac37c17a6d7d563d21c76 Mon Sep 17 00:00:00 2001 From: trejgun Date: Thu, 21 Jun 2018 14:43:48 +0800 Subject: refactor gas-fee-display component #4622 --- ui/app/components/pending-tx/confirm-send-ether.js | 2 +- ui/app/components/pending-tx/confirm-send-token.js | 2 +- ui/app/components/send/gas-fee-display-v2.js | 53 --------------------- .../gas-fee-display/gas-fee-display.component.js | 51 ++++++++++++++++++++ .../send-gas-row/gas-fee-display/index.js | 1 + .../test/gas-fee-display.component.test.js | 55 ++++++++++++++++++++++ .../send-gas-row/send-gas-row.component.js | 2 +- .../tests/send-gas-row-component.test.js | 2 +- 8 files changed, 111 insertions(+), 57 deletions(-) delete mode 100644 ui/app/components/send/gas-fee-display-v2.js create mode 100644 ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js create mode 100644 ui/app/components/send_/send-content/send-gas-row/gas-fee-display/index.js create mode 100644 ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js (limited to 'ui') diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index bbf5683f0..bd0f4f71d 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -20,7 +20,7 @@ const { calcGasTotal, isBalanceSufficient, } = require('../send_/send.utils') -const GasFeeDisplay = require('../send/gas-fee-display-v2') +const GasFeeDisplay = require('../send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component') const SenderToRecipient = require('../sender-to-recipient') const NetworkDisplay = require('../network-display') const currencyFormatter = require('currency-formatter') diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index ee066b8f4..f3985d284 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -11,7 +11,7 @@ abiDecoder.addABI(tokenAbi) const actions = require('../../actions') const clone = require('clone') const Identicon = require('../identicon') -const GasFeeDisplay = require('../send/gas-fee-display-v2.js') +const GasFeeDisplay = require('../send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js') const NetworkDisplay = require('../network-display') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN diff --git a/ui/app/components/send/gas-fee-display-v2.js b/ui/app/components/send/gas-fee-display-v2.js deleted file mode 100644 index 1423aa84d..000000000 --- a/ui/app/components/send/gas-fee-display-v2.js +++ /dev/null @@ -1,53 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const inherits = require('util').inherits -const CurrencyDisplay = require('./currency-display') -const connect = require('react-redux').connect - -GasFeeDisplay.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect()(GasFeeDisplay) - - -inherits(GasFeeDisplay, Component) -function GasFeeDisplay () { - Component.call(this) -} - -GasFeeDisplay.prototype.render = function () { - const { - conversionRate, - gasTotal, - onClick, - primaryCurrency = 'ETH', - convertedCurrency, - gasLoadingError, - } = this.props - - return h('div.send-v2__gas-fee-display', [ - - gasTotal - ? h(CurrencyDisplay, { - primaryCurrency, - convertedCurrency, - value: gasTotal, - conversionRate, - convertedPrefix: '$', - readOnly: true, - }) - : gasLoadingError - ? h('div.currency-display.currency-display--message', this.context.t('setGasPrice')) - : h('div.currency-display', this.context.t('loading')), - - h('button.sliders-icon-container', { - onClick, - disabled: !gasTotal && !gasLoadingError, - }, [ - h('i.fa.fa-sliders.sliders-icon'), - ]), - - ]) -} diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js new file mode 100644 index 000000000..99286a139 --- /dev/null +++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js @@ -0,0 +1,51 @@ +import React, {Component} from 'react' +import PropTypes from 'prop-types' +import CurrencyDisplay from '../../../../send/currency-display' + + +export default class GasFeeDisplay extends Component { + render() { + const { + conversionRate, + gasTotal, + onClick, + primaryCurrency = 'ETH', + convertedCurrency, + gasLoadingError, + } = this.props + + return ( +
+ {gasTotal + ? + : gasLoadingError + ?
+ {this.context.t('setGasPrice')} +
+ :
+ {this.context.t('loading')} +
+ } + +
+ ) + } +} + +GasFeeDisplay.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/index.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/index.js new file mode 100644 index 000000000..dba0edb7b --- /dev/null +++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/index.js @@ -0,0 +1 @@ +export { default } from './gas-fee-display.component' diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js new file mode 100644 index 000000000..630781515 --- /dev/null +++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js @@ -0,0 +1,55 @@ +import React from 'react' +import assert from 'assert' +import {shallow} from 'enzyme' +import GasFeeDisplay from '../gas-fee-display.component' +import CurrencyDisplay from '../../../../../send/currency-display' +import sinon from "sinon"; + + +const propsMethodSpies = { + showCustomizeGasModal: sinon.spy(), +} + +describe.only('SendGasRow Component', function() { + let wrapper + + beforeEach(() => { + wrapper = shallow(, {context: {t: str => str + '_t'}}) + }) + + afterEach(() => { + propsMethodSpies.showCustomizeGasModal.resetHistory() + }) + + describe('render', () => { + it('should render a CurrencyDisplay component', () => { + assert.equal(wrapper.find(CurrencyDisplay).length, 1) + }) + + it('should render the CurrencyDisplay with the correct props', () => { + const { + conversionRate, + convertedCurrency, + value + } = wrapper.find(CurrencyDisplay).props() + assert.equal(conversionRate, 20) + assert.equal(convertedCurrency, 'mockConvertedCurrency') + assert.equal(value, 'mockGasTotal') + }) + + it('should render the Button with the correct props', () => { + const { + onClick, + } = wrapper.find("button").props() + assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 0) + onClick() + assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 1) + }) + }) +}) diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js index c80d8c0bb..17cea3d4e 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SendRowWrapper from '../send-row-wrapper/' -import GasFeeDisplay from '../../../send/gas-fee-display-v2' +import GasFeeDisplay from './gas-fee-display/gas-fee-display.component' export default class SendGasRow extends Component { diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js index e4f05d708..db37f18be 100644 --- a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js +++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js @@ -5,7 +5,7 @@ import sinon from 'sinon' import SendGasRow from '../send-gas-row.component.js' import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' -import GasFeeDisplay from '../../../../send/gas-fee-display-v2' +import GasFeeDisplay from '../gas-fee-display/gas-fee-display.component' const propsMethodSpies = { showCustomizeGasModal: sinon.spy(), -- cgit From 778aec8c754a2bbd2b9f35aabfb57a99a9608e54 Mon Sep 17 00:00:00 2001 From: trejgun Date: Thu, 21 Jun 2018 15:03:19 +0800 Subject: lint --- .../send-gas-row/gas-fee-display/gas-fee-display.component.js | 10 ++++++++++ .../gas-fee-display/test/gas-fee-display.component.test.js | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js index 99286a139..b1fd67412 100644 --- a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js @@ -4,6 +4,16 @@ import CurrencyDisplay from '../../../../send/currency-display' export default class GasFeeDisplay extends Component { + + static propTypes = { + conversionRate: PropTypes.number, + primaryCurrency: PropTypes.string, + convertedCurrency: PropTypes.string, + gasLoadingError: PropTypes.bool, + gasTotal: PropTypes.string, + onClick: PropTypes.func, + }; + render() { const { conversionRate, diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js index 630781515..9f2f1a0cf 100644 --- a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js +++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js @@ -3,7 +3,7 @@ import assert from 'assert' import {shallow} from 'enzyme' import GasFeeDisplay from '../gas-fee-display.component' import CurrencyDisplay from '../../../../../send/currency-display' -import sinon from "sinon"; +import sinon from 'sinon' const propsMethodSpies = { @@ -36,7 +36,7 @@ describe.only('SendGasRow Component', function() { const { conversionRate, convertedCurrency, - value + value, } = wrapper.find(CurrencyDisplay).props() assert.equal(conversionRate, 20) assert.equal(convertedCurrency, 'mockConvertedCurrency') @@ -46,7 +46,7 @@ describe.only('SendGasRow Component', function() { it('should render the Button with the correct props', () => { const { onClick, - } = wrapper.find("button").props() + } = wrapper.find('button').props() assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 0) onClick() assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 1) -- cgit From 97a7bc48947000163990be081058c6a6a3cddcad Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 21 Jun 2018 15:01:13 -0230 Subject: Remove commented out code in ens-input. --- ui/app/components/ens-input.js | 1 - 1 file changed, 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 7873e81e0..292dcdde6 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -94,7 +94,6 @@ EnsInput.prototype.lookupEnsName = function (recipient) { } }) .catch((reason) => { - // log.error(reason) const setStateObj = { loadingEns: false, ensResolution: recipient, -- cgit From e8fa18779d72a74fa1c003415c73f715177a64d8 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 21 Jun 2018 15:04:24 -0230 Subject: Fix style of shapeshift screen in deposit modal. --- ui/app/components/shapeshift-form.js | 2 +- ui/app/css/itcss/components/modal.scss | 31 +++++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) (limited to 'ui') diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 93d2023b5..2c4ba40bf 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -181,7 +181,7 @@ ShapeshiftForm.prototype.render = function () { return h('div.shapeshift-form-wrapper', [ showQrCode ? this.renderQrCode() - : h('div.shapeshift-form', [ + : h('div.modal-shapeshift-form', [ h('div.shapeshift-form__selectors', [ h('div.shapeshift-form__selector', [ diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index 74658f656..42ef7ae0a 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -642,10 +642,31 @@ display: flex; flex-flow: column nowrap; flex: 1; + align-items: center; @media screen and (max-width: 575px) { height: 0; } + + .shapeshift-form-wrapper { + display: flex; + flex-flow: column; + justify-content: center; + align-items: center; + flex: 1 0 auto; + + .shapeshift-form, .modal-shapeshift-form { + border-radius: 8px; + background-color: rgba(0, 0, 0, .05); + padding: 17px 15px; + margin-bottom: 10px; + + &__caret { + width: auto; + flex: 1; + } + } + } } &__logo { @@ -773,17 +794,15 @@ margin-top: 28px; flex: 1 0 auto; - .shapeshift-form { - width: auto; + .shapeshift-form, .modal-shapeshift-form { + border-radius: 8px; + background-color: rgba(0, 0, 0, .05); + padding: 17px 15px; &__caret { width: auto; flex: 1; } - - @media screen and (max-width: 575px) { - width: auto; - } } } -- cgit From 1ff29c5b4aa305c6dc59736791a1dce642ae3991 Mon Sep 17 00:00:00 2001 From: Koh Wei Jie Date: Fri, 22 Jun 2018 16:38:54 +0800 Subject: fixed blank boolean field for typed signing for new UI --- ui/app/components/signature-request.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ui') diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js index 2a3e929fe..79f2f42d1 100644 --- a/ui/app/components/signature-request.js +++ b/ui/app/components/signature-request.js @@ -204,6 +204,10 @@ SignatureRequest.prototype.renderBody = function () { h('div.request-signature__rows', [ ...rows.map(({ name, value }) => { + console.log(value) + if (typeof value === 'boolean') { + value = value.toString() + } return h('div.request-signature__row', [ h('div.request-signature__row-title', [`${name}:`]), h('div.request-signature__row-value', value), -- cgit From cd90e8a869eca9edc183b142e9a7d811e7c46e57 Mon Sep 17 00:00:00 2001 From: Koh Wei Jie Date: Fri, 22 Jun 2018 16:46:00 +0800 Subject: removed stray console.log --- ui/app/components/signature-request.js | 1 - 1 file changed, 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js index 79f2f42d1..bbb340fcf 100644 --- a/ui/app/components/signature-request.js +++ b/ui/app/components/signature-request.js @@ -204,7 +204,6 @@ SignatureRequest.prototype.renderBody = function () { h('div.request-signature__rows', [ ...rows.map(({ name, value }) => { - console.log(value) if (typeof value === 'boolean') { value = value.toString() } -- cgit From e05eeb56da51e89e94267bcfe0b4b80ace55d09e Mon Sep 17 00:00:00 2001 From: trejgun Date: Sat, 23 Jun 2018 07:41:11 +0800 Subject: fix default import --- ui/app/components/pending-tx/confirm-send-ether.js | 2 +- ui/app/components/pending-tx/confirm-send-token.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index bd0f4f71d..22b2670d8 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -20,7 +20,7 @@ const { calcGasTotal, isBalanceSufficient, } = require('../send_/send.utils') -const GasFeeDisplay = require('../send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component') +const GasFeeDisplay = require('../send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component').default const SenderToRecipient = require('../sender-to-recipient') const NetworkDisplay = require('../network-display') const currencyFormatter = require('currency-formatter') diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index f3985d284..535347cee 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -11,7 +11,7 @@ abiDecoder.addABI(tokenAbi) const actions = require('../../actions') const clone = require('clone') const Identicon = require('../identicon') -const GasFeeDisplay = require('../send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js') +const GasFeeDisplay = require('../send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js').default const NetworkDisplay = require('../network-display') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN -- cgit From 5d034006e8a73c7d64dee22c856c3733643454f8 Mon Sep 17 00:00:00 2001 From: trejgun Date: Sun, 24 Jun 2018 17:48:02 +0800 Subject: fixes #4307 BigNumber casting issue --- ui/app/conversion-util.js | 2 +- ui/app/conversion-util.test.js | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 ui/app/conversion-util.test.js (limited to 'ui') diff --git a/ui/app/conversion-util.js b/ui/app/conversion-util.js index 100402d95..90c2226e7 100644 --- a/ui/app/conversion-util.js +++ b/ui/app/conversion-util.js @@ -140,7 +140,7 @@ const addCurrencies = (a, b, options = {}) => { bBase, ...conversionOptions } = options - const value = (new BigNumber(a, aBase)).add(b, bBase) + const value = (new BigNumber(a.toString(), aBase)).add(b.toString(), bBase) return converter({ value, diff --git a/ui/app/conversion-util.test.js b/ui/app/conversion-util.test.js new file mode 100644 index 000000000..c37264855 --- /dev/null +++ b/ui/app/conversion-util.test.js @@ -0,0 +1,23 @@ +import assert from 'assert' +import {addCurrencies} from './conversion-util' + + + +describe('conversion utils', () => { + describe.only('addCurrencies()', () => { + it('add whole numbers', () => { + const result = addCurrencies(3, 5) + assert.equal(result.toNumber(), 8) + }) + + it('add decimals', () => { + const result = addCurrencies(1.3, 1.5) + assert.equal(result.toNumber(), 2.8) + }) + + it('add repeating decimals', () => { + const result = addCurrencies(1/3, 1/7) + assert.equal(result.toNumber(), 0.47619047619047616) + }) + }) +}) -- cgit From 0d0120746d93c9b329bcd57f3819fd39903e6ece Mon Sep 17 00:00:00 2001 From: trejgun Date: Sun, 24 Jun 2018 17:51:31 +0800 Subject: remove .only --- ui/app/conversion-util.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/conversion-util.test.js b/ui/app/conversion-util.test.js index c37264855..51dcd1ece 100644 --- a/ui/app/conversion-util.test.js +++ b/ui/app/conversion-util.test.js @@ -4,7 +4,7 @@ import {addCurrencies} from './conversion-util' describe('conversion utils', () => { - describe.only('addCurrencies()', () => { + describe('addCurrencies()', () => { it('add whole numbers', () => { const result = addCurrencies(3, 5) assert.equal(result.toNumber(), 8) -- cgit From 74080e41f11b7bc2668ffdb42404d940139b68a8 Mon Sep 17 00:00:00 2001 From: trejgun Date: Sun, 24 Jun 2018 17:56:07 +0800 Subject: change numbers --- ui/app/conversion-util.test.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'ui') diff --git a/ui/app/conversion-util.test.js b/ui/app/conversion-util.test.js index 51dcd1ece..d956998a1 100644 --- a/ui/app/conversion-util.test.js +++ b/ui/app/conversion-util.test.js @@ -2,22 +2,21 @@ import assert from 'assert' import {addCurrencies} from './conversion-util' - describe('conversion utils', () => { describe('addCurrencies()', () => { it('add whole numbers', () => { - const result = addCurrencies(3, 5) - assert.equal(result.toNumber(), 8) + const result = addCurrencies(3, 9) + assert.equal(result.toNumber(), 12) }) it('add decimals', () => { - const result = addCurrencies(1.3, 1.5) - assert.equal(result.toNumber(), 2.8) + const result = addCurrencies(1.3, 1.9) + assert.equal(result.toNumber(), 3.2) }) it('add repeating decimals', () => { - const result = addCurrencies(1/3, 1/7) - assert.equal(result.toNumber(), 0.47619047619047616) + const result = addCurrencies(1/3, 1/9) + assert.equal(result.toNumber(), 0.4444444444444444) }) }) }) -- cgit From fb02e71e2245d746661e5db42f2cf8000c82c001 Mon Sep 17 00:00:00 2001 From: tmashuang Date: Tue, 26 Jun 2018 18:14:21 -0700 Subject: Remove .only on test --- .../send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js index 9f2f1a0cf..66f3a94df 100644 --- a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js +++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js @@ -10,7 +10,7 @@ const propsMethodSpies = { showCustomizeGasModal: sinon.spy(), } -describe.only('SendGasRow Component', function() { +describe('SendGasRow Component', function() { let wrapper beforeEach(() => { -- cgit From a2426688728e10ba660dea7ce1833794d7e9c90c Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 19 Jun 2018 13:23:33 -0230 Subject: Only show the customize gas modal if the estimateGas call is not currently in flight. --- ui/app/actions.js | 21 ++++++++++- ui/app/components/customize-gas-modal/index.js | 48 +++++++++++++++++++++++--- ui/app/reducers/app.js | 11 ++++++ ui/app/selectors.js | 5 +++ 4 files changed, 79 insertions(+), 6 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 1edf692b6..41fc3c504 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -175,6 +175,8 @@ var actions = { CLEAR_SEND: 'CLEAR_SEND', OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN', CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN', + GAS_LOADING_STARTED: 'GAS_LOADING_STARTED', + GAS_LOADING_FINISHED: 'GAS_LOADING_FINISHED', setGasLimit, setGasPrice, updateGasData, @@ -190,6 +192,8 @@ var actions = { updateSendErrors, clearSend, setSelectedAddress, + gasLoadingStarted, + gasLoadingFinished, // app messages confirmSeedWords: confirmSeedWords, showAccountDetail: showAccountDetail, @@ -740,8 +744,9 @@ function updateGasData ({ to, value, }) { - const estimatedGasPrice = estimateGasPriceFromRecentBlocks(recentBlocks) return (dispatch) => { + dispatch(actions.gasLoadingStarted()) + const estimatedGasPrice = estimateGasPriceFromRecentBlocks(recentBlocks) return Promise.all([ Promise.resolve(estimatedGasPrice), estimateGas({ @@ -762,14 +767,28 @@ function updateGasData ({ .then((gasEstimate) => { dispatch(actions.setGasTotal(gasEstimate)) dispatch(updateSendErrors({ gasLoadingError: null })) + dispatch(actions.gasLoadingFinished()) }) .catch(err => { log.error(err) dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' })) + dispatch(actions.gasLoadingFinished()) }) } } +function gasLoadingStarted () { + return { + type: actions.GAS_LOADING_STARTED, + } +} + +function gasLoadingFinished () { + return { + type: actions.GAS_LOADING_FINISHED, + } +} + function updateSendTokenBalance ({ selectedToken, tokenContract, diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index c8522a3c7..cd8f76ed5 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -33,6 +33,7 @@ const { const { getGasPrice, getGasLimit, + getGasIsLoading, getForceGasMin, conversionRateSelector, getSendAmount, @@ -51,6 +52,7 @@ function mapStateToProps (state) { return { gasPrice: getGasPrice(state), gasLimit: getGasLimit(state), + gasIsLoading: getGasIsLoading(state), forceGasMin: getForceGasMin(state), conversionRate, amount: getSendAmount(state), @@ -73,7 +75,7 @@ function mapDispatchToProps (dispatch) { } } -function getOriginalState (props) { +function getFreshState (props) { const gasPrice = props.gasPrice || MIN_GAS_PRICE_DEC const gasLimit = props.gasLimit || MIN_GAS_LIMIT_DEC @@ -97,7 +99,11 @@ inherits(CustomizeGasModal, Component) function CustomizeGasModal (props) { Component.call(this) - this.state = getOriginalState(props) + const originalState = getFreshState(props) + this.state = { + ...originalState, + originalState, + } } CustomizeGasModal.contextTypes = { @@ -106,6 +112,36 @@ CustomizeGasModal.contextTypes = { module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal) +CustomizeGasModal.prototype.componentWillReceiveProps = function (nextProps) { + const currentState = getFreshState(this.props) + const { + gasPrice: currentGasPrice, + gasLimit: currentGasLimit, + } = currentState + const newState = getFreshState(nextProps) + const { + gasPrice: newGasPrice, + gasLimit: newGasLimit, + gasTotal: newGasTotal, + } = newState + const gasPriceChanged = currentGasPrice !== newGasPrice + const gasLimitChanged = currentGasLimit !== newGasLimit + + if (gasPriceChanged) { + this.setState({ + gasPrice: newGasPrice, + gasTotal: newGasTotal, + priceSigZeros: '', + priceSigDec: '', + }) + } + if (gasLimitChanged) { + this.setState({ gasLimit: newGasLimit, gasTotal: newGasTotal }) + } + if (gasLimitChanged || gasPriceChanged) { + this.validate({ gasLimit: newGasLimit, gasTotal: newGasTotal }) + } +} CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { const { @@ -137,7 +173,7 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { } CustomizeGasModal.prototype.revert = function () { - this.setState(getOriginalState(this.props)) + this.setState(this.state.originalState) } CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) { @@ -233,7 +269,7 @@ CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) { } CustomizeGasModal.prototype.render = function () { - const { hideModal, forceGasMin } = this.props + const { hideModal, forceGasMin, gasIsLoading } = this.props const { gasPrice, gasLimit, gasTotal, error, priceSigZeros, priceSigDec } = this.state let convertedGasPrice = conversionUtil(gasPrice, { @@ -266,7 +302,7 @@ CustomizeGasModal.prototype.render = function () { toNumericBase: 'dec', }) - return h('div.send-v2__customize-gas', {}, [ + return !gasIsLoading && h('div.send-v2__customize-gas', {}, [ h('div.send-v2__customize-gas__content', { }, [ h('div.send-v2__customize-gas__header', {}, [ @@ -288,6 +324,7 @@ CustomizeGasModal.prototype.render = function () { onChange: value => this.convertAndSetGasPrice(value), title: this.context.t('gasPrice'), copy: this.context.t('gasPriceCalculation'), + gasIsLoading, }), h(GasModalCard, { @@ -297,6 +334,7 @@ CustomizeGasModal.prototype.render = function () { onChange: value => this.convertAndSetGasLimit(value), title: this.context.t('gasLimit'), copy: this.context.t('gasLimitCalculation'), + gasIsLoading, }), ]), diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 4e9d0848c..9cacf5fe7 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -62,6 +62,7 @@ function reduceApp (state, action) { warning: null, buyView: {}, isMouseUser: false, + gasIsLoading: false, }, state.appState) switch (action.type) { @@ -675,6 +676,16 @@ function reduceApp (state, action) { isMouseUser: action.value, }) + case actions.GAS_LOADING_STARTED: + return extend(appState, { + gasIsLoading: true, + }) + + case actions.GAS_LOADING_FINISHED: + return extend(appState, { + gasIsLoading: false, + }) + default: return appState } diff --git a/ui/app/selectors.js b/ui/app/selectors.js index a29294b86..cf0affe9c 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -16,6 +16,7 @@ const selectors = { transactionsSelector, accountsWithSendEtherInfoSelector, getCurrentAccountWithSendEtherInfo, + getGasIsLoading, getGasPrice, getGasLimit, getForceGasMin, @@ -117,6 +118,10 @@ function transactionsSelector (state) { .sort((a, b) => b.time - a.time) } +function getGasIsLoading (state) { + return state.appState.gasIsLoading +} + function getGasPrice (state) { return state.metamask.send.gasPrice } -- cgit From f5d43404dc35a849dcb9dc9f5d87f183be0aff0b Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 21 Jun 2018 13:59:20 -0230 Subject: Fix send token tests in beta ui e2e tests. --- ui/app/components/input-number.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/input-number.js b/ui/app/components/input-number.js index de5fcca54..59c6842ef 100644 --- a/ui/app/components/input-number.js +++ b/ui/app/components/input-number.js @@ -22,12 +22,16 @@ function isValidInput (text) { return re.test(text) } +function removeLeadingZeroes (str) { + return str.replace(/^0*(?=\d)/, '') +} + InputNumber.prototype.setValue = function (newValue) { + newValue = removeLeadingZeroes(newValue) if (newValue && !isValidInput(newValue)) return const { fixed, min = -1, max = Infinity, onChange } = this.props newValue = fixed ? newValue.toFixed(4) : newValue - const newValueGreaterThanMin = conversionGTE( { value: newValue || '0', fromNumericBase: 'dec' }, { value: min, fromNumericBase: 'hex' }, @@ -47,7 +51,7 @@ InputNumber.prototype.setValue = function (newValue) { } InputNumber.prototype.render = function () { - const { unitLabel, step = 1, placeholder, value = 0 } = this.props + const { unitLabel, step = 1, placeholder, value } = this.props return h('div.customize-gas-input-wrapper', {}, [ h('input', { @@ -63,11 +67,11 @@ InputNumber.prototype.render = function () { h('span.gas-tooltip-input-detail', {}, [unitLabel]), h('div.gas-tooltip-input-arrows', {}, [ h('i.fa.fa-angle-up', { - onClick: () => this.setValue(addCurrencies(value, step)), + onClick: () => this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' })), }), h('i.fa.fa-angle-down', { style: { cursor: 'pointer' }, - onClick: () => this.setValue(subtractCurrencies(value, step)), + onClick: () => this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' })), }), ]), ]) -- cgit From 696d6261338e96a3a7786e08940ab51cad98cb22 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 28 Jun 2018 21:03:09 -0230 Subject: Set based estimate for token sends to be updated once recipient address specified. --- ui/app/components/send_/send.constants.js | 2 ++ ui/app/components/send_/send.utils.js | 3 +++ ui/app/components/send_/tests/send-utils.test.js | 6 ++++++ 3 files changed, 11 insertions(+) (limited to 'ui') diff --git a/ui/app/components/send_/send.constants.js b/ui/app/components/send_/send.constants.js index df5dee371..8acdf0641 100644 --- a/ui/app/components/send_/send.constants.js +++ b/ui/app/components/send_/send.constants.js @@ -36,6 +36,7 @@ const ONE_GWEI_IN_WEI_HEX = ethUtil.addHexPrefix(conversionUtil('0x1', { })) const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send. +const BASE_TOKEN_GAS_COST = '0x186a0' // Hex for 100000, a base estimate for token transfers. module.exports = { INSUFFICIENT_FUNDS_ERROR, @@ -52,4 +53,5 @@ module.exports = { REQUIRED_ERROR, SIMPLE_GAS_COST, TOKEN_TRANSFER_FUNCTION_SIGNATURE, + BASE_TOKEN_GAS_COST, } diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index dfd459731..872df1d2f 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -10,6 +10,7 @@ const { calcTokenAmount, } = require('../../token-util') const { + BASE_TOKEN_GAS_COST, INSUFFICIENT_FUNDS_ERROR, INSUFFICIENT_TOKENS_ERROR, NEGATIVE_ETH_ERROR, @@ -183,6 +184,8 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, if (!code || code === '0x') { return SIMPLE_GAS_COST } + } else if (selectedToken && !to) { + return BASE_TOKEN_GAS_COST } paramsForGasEstimate.to = selectedToken ? selectedToken.address : to diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index f3d5674b7..a518a64e9 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -2,6 +2,7 @@ import assert from 'assert' import sinon from 'sinon' import proxyquire from 'proxyquire' import { + BASE_TOKEN_GAS_COST, ONE_GWEI_IN_WEI_HEX, SIMPLE_GAS_COST, } from '../send.constants' @@ -336,6 +337,11 @@ describe('send utils', () => { assert.notEqual(result, SIMPLE_GAS_COST) }) + it(`should return ${BASE_TOKEN_GAS_COST} if passed a selectedToken but no to address`, async () => { + const result = await estimateGas(Object.assign({}, baseMockParams, { to: null, selectedToken: { address: '' } })) + assert.equal(result, BASE_TOKEN_GAS_COST) + }) + it(`should return the adjusted blockGasLimit if it fails with a 'Transaction execution error.'`, async () => { const result = await estimateGas(Object.assign({}, baseMockParams, { to: 'isContract willFailBecauseOf:Transaction execution error.', -- cgit From d5133fb429c313f7bed24e3740eddb1350810b16 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 22 Jun 2018 13:43:39 -0230 Subject: Fix issues with scrollbar in currency display. --- ui/app/components/send/currency-display.js | 2 +- ui/app/css/itcss/components/currency-display.scss | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 9c8ce0044..e410bc070 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -149,7 +149,7 @@ CurrencyDisplay.prototype.render = function () { } : {}), ref: input => { this.currencyInput = input }, style: { - minWidth: this.getInputWidth(valueToRender, readOnly), + width: this.getInputWidth(valueToRender, readOnly), }, min: 0, }), diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss index 3614aa868..5f380475a 100644 --- a/ui/app/css/itcss/components/currency-display.scss +++ b/ui/app/css/itcss/components/currency-display.scss @@ -48,7 +48,6 @@ display: flex; flex: 1; max-width: 100%; - overflow-x: scroll; input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; -- cgit From d2845afd898acf6224113832f819d2fe9b3061c6 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 25 Jun 2018 09:54:49 -0230 Subject: Remove input arrows for send screen amount row on firefox. --- ui/app/components/send/currency-display.js | 2 ++ .../send-content/send-amount-row/send-amount-row.component.js | 1 + ui/app/css/itcss/components/currency-display.scss | 8 ++++++++ 3 files changed, 11 insertions(+) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index e410bc070..5e2c5fdf6 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -118,6 +118,7 @@ CurrencyDisplay.prototype.render = function () { readOnly = false, inError = false, onBlur, + step, } = this.props const { valueToRender } = this.state @@ -152,6 +153,7 @@ CurrencyDisplay.prototype.render = function () { width: this.getInputWidth(valueToRender, readOnly), }, min: 0, + step, }), h('span.currency-display__currency-symbol', primaryCurrency), diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index 8da36d3b7..6c6189cf9 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -95,6 +95,7 @@ export default class SendAmountRow extends Component { primaryCurrency={primaryCurrency || 'ETH'} selectedToken={selectedToken} value={amount} + step={'any'} /> ) diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss index 5f380475a..b1a74dce2 100644 --- a/ui/app/css/itcss/components/currency-display.scss +++ b/ui/app/css/itcss/components/currency-display.scss @@ -49,13 +49,19 @@ flex: 1; max-width: 100%; + input[type="number"] { + -moz-appearance: textfield; + } + input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; + -moz-appearance: none; display: none; } input[type="number"]:hover::-webkit-inner-spin-button { -webkit-appearance: none; + -moz-appearance: none; display: none; } } @@ -68,11 +74,13 @@ .react-numeric-input { input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; + -moz-appearance: none; display: none; } input[type="number"]:hover::-webkit-inner-spin-button { -webkit-appearance: none; + -moz-appearance: none; display: none; } } -- cgit From 75581ceebe719e102b177dc20f3b9e232c48e8a4 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 21 Jun 2018 14:57:45 -0230 Subject: Show all errors on account creation screen. --- ui/app/components/pages/create-account/import-account/json.js | 2 +- ui/app/components/pages/create-account/import-account/private-key.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js index 1dc2ba534..955b45d74 100644 --- a/ui/app/components/pages/create-account/import-account/json.js +++ b/ui/app/components/pages/create-account/import-account/json.js @@ -114,7 +114,7 @@ class JsonImportSubview extends Component { setSelectedAddress(firstAddress) } }) - .catch(err => displayWarning(err)) + .catch(err => displayWarning(err.message || err)) } } diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js index 5df3777da..0d8ff0db6 100644 --- a/ui/app/components/pages/create-account/import-account/private-key.js +++ b/ui/app/components/pages/create-account/import-account/private-key.js @@ -104,5 +104,5 @@ PrivateKeyImportView.prototype.createNewKeychain = function () { setSelectedAddress(firstAddress) } }) - .catch(err => displayWarning(err)) + .catch(err => displayWarning(err.message || err)) } -- cgit From 03f17d910a17abe3fe8c8dae2ef38a66bb86f222 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 21 Jun 2018 15:53:26 -0230 Subject: Check that error is defined in import-account error catch. --- ui/app/components/pages/create-account/import-account/json.js | 2 +- ui/app/components/pages/create-account/import-account/private-key.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js index 955b45d74..c9d14be5f 100644 --- a/ui/app/components/pages/create-account/import-account/json.js +++ b/ui/app/components/pages/create-account/import-account/json.js @@ -114,7 +114,7 @@ class JsonImportSubview extends Component { setSelectedAddress(firstAddress) } }) - .catch(err => displayWarning(err.message || err)) + .catch(err => err && displayWarning(err.message || err)) } } diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js index 0d8ff0db6..c38c39206 100644 --- a/ui/app/components/pages/create-account/import-account/private-key.js +++ b/ui/app/components/pages/create-account/import-account/private-key.js @@ -104,5 +104,5 @@ PrivateKeyImportView.prototype.createNewKeychain = function () { setSelectedAddress(firstAddress) } }) - .catch(err => displayWarning(err.message || err)) + .catch(err => err && displayWarning(err.message || err)) } -- cgit From a6f1734eddb934ce63384951305f86697ba88983 Mon Sep 17 00:00:00 2001 From: trejgun Date: Fri, 29 Jun 2018 16:19:45 +0800 Subject: lint --- ui/app/conversion-util.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/conversion-util.test.js b/ui/app/conversion-util.test.js index d956998a1..368ce3bba 100644 --- a/ui/app/conversion-util.test.js +++ b/ui/app/conversion-util.test.js @@ -15,7 +15,7 @@ describe('conversion utils', () => { }) it('add repeating decimals', () => { - const result = addCurrencies(1/3, 1/9) + const result = addCurrencies(1 / 3, 1 / 9) assert.equal(result.toNumber(), 0.4444444444444444) }) }) -- cgit From 464b97890dd35acb7428899aeb214a0e506d4507 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Fri, 29 Jun 2018 15:44:30 -0230 Subject: Improve attribute syntax in send-amount-row.component.js --- .../send_/send-content/send-amount-row/send-amount-row.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index 6c6189cf9..196538c11 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -95,7 +95,7 @@ export default class SendAmountRow extends Component { primaryCurrency={primaryCurrency || 'ETH'} selectedToken={selectedToken} value={amount} - step={'any'} + step="any" /> ) -- cgit From ad6c454fd5d59c7f47edf515f0eb8aec549a8683 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Fri, 22 Jun 2018 15:05:50 -0700 Subject: fixes #4623 --- .../send_/account-list-item/account-list-item.container.js | 4 ++-- .../tests/account-list-item-container.test.js | 2 +- .../send-content/send-amount-row/send-amount-row.container.js | 4 ++-- .../send-amount-row/tests/send-amount-row-container.test.js | 2 +- .../send_/send-content/send-gas-row/send-gas-row.container.js | 4 ++-- .../send-gas-row/tests/send-gas-row-container.test.js | 2 +- ui/app/components/send_/send.selectors.js | 5 ----- ui/app/components/send_/tests/send-selectors.test.js | 10 ---------- 8 files changed, 9 insertions(+), 24 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send_/account-list-item/account-list-item.container.js b/ui/app/components/send_/account-list-item/account-list-item.container.js index 3151b1f1d..4b4519288 100644 --- a/ui/app/components/send_/account-list-item/account-list-item.container.js +++ b/ui/app/components/send_/account-list-item/account-list-item.container.js @@ -1,7 +1,7 @@ import { connect } from 'react-redux' import { getConversionRate, - getConvertedCurrency, + getCurrentCurrency, } from '../send.selectors.js' import AccountListItem from './account-list-item.component' @@ -10,6 +10,6 @@ export default connect(mapStateToProps)(AccountListItem) function mapStateToProps (state) { return { conversionRate: getConversionRate(state), - currentCurrency: getConvertedCurrency(state), + currentCurrency: getCurrentCurrency(state), } } diff --git a/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js b/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js index 49da920e6..af0859117 100644 --- a/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js +++ b/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js @@ -12,7 +12,7 @@ proxyquire('../account-list-item.container.js', { }, '../send.selectors.js': { getConversionRate: (s) => `mockConversionRate:${s}`, - getConvertedCurrency: (s) => `mockCurrentCurrency:${s}`, + getCurrentCurrency: (s) => `mockCurrentCurrency:${s}`, }, }) diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js index bbbf56971..b816d948f 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux' import { getAmountConversionRate, getConversionRate, - getConvertedCurrency, + getCurrentCurrency, getGasTotal, getPrimaryCurrency, getSelectedToken, @@ -31,7 +31,7 @@ function mapStateToProps (state) { amountConversionRate: getAmountConversionRate(state), balance: getSendFromBalance(state), conversionRate: getConversionRate(state), - convertedCurrency: getConvertedCurrency(state), + convertedCurrency: getCurrentCurrency(state), gasTotal: getGasTotal(state), inError: sendAmountIsInError(state), primaryCurrency: getPrimaryCurrency(state), diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js index e4c913c69..94d9918a7 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js @@ -24,7 +24,7 @@ proxyquire('../send-amount-row.container.js', { '../../send.selectors': { getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`, getConversionRate: (s) => `mockConversionRate:${s}`, - getConvertedCurrency: (s) => `mockConvertedCurrency:${s}`, + getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`, getGasTotal: (s) => `mockGasTotal:${s}`, getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`, getSelectedToken: (s) => `mockSelectedToken:${s}`, diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js index 20d3daa59..6e6fbc8a8 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js @@ -1,7 +1,7 @@ import { connect } from 'react-redux' import { getConversionRate, - getConvertedCurrency, + getCurrentCurrency, getGasTotal, } from '../../send.selectors.js' import { sendGasIsInError } from './send-gas-row.selectors.js' @@ -13,7 +13,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow) function mapStateToProps (state) { return { conversionRate: getConversionRate(state), - convertedCurrency: getConvertedCurrency(state), + convertedCurrency: getCurrentCurrency(state), gasTotal: getGasTotal(state), gasLoadingError: sendGasIsInError(state), } diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js index 9135524d1..e928c8aba 100644 --- a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js +++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js @@ -19,7 +19,7 @@ proxyquire('../send-gas-row.container.js', { }, '../../send.selectors.js': { getConversionRate: (s) => `mockConversionRate:${s}`, - getConvertedCurrency: (s) => `mockConvertedCurrency:${s}`, + getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`, getGasTotal: (s) => `mockGasTotal:${s}`, }, './send-gas-row.selectors.js': { sendGasIsInError: (s) => `mockGasLoadingError:${s}` }, diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js index 7e7cfe2e9..f910f7caf 100644 --- a/ui/app/components/send_/send.selectors.js +++ b/ui/app/components/send_/send.selectors.js @@ -14,7 +14,6 @@ const selectors = { getAmountConversionRate, getBlockGasLimit, getConversionRate, - getConvertedCurrency, getCurrentAccountWithSendEtherInfo, getCurrentCurrency, getCurrentNetwork, @@ -98,10 +97,6 @@ function getConversionRate (state) { return state.metamask.conversionRate } -function getConvertedCurrency (state) { - return state.metamask.currentCurrency -} - function getCurrentAccountWithSendEtherInfo (state) { const currentAddress = getSelectedAddress(state) const accounts = accountsWithSendEtherInfoSelector(state) diff --git a/ui/app/components/send_/tests/send-selectors.test.js b/ui/app/components/send_/tests/send-selectors.test.js index 152af8059..218da656b 100644 --- a/ui/app/components/send_/tests/send-selectors.test.js +++ b/ui/app/components/send_/tests/send-selectors.test.js @@ -8,7 +8,6 @@ const { getBlockGasLimit, getAmountConversionRate, getConversionRate, - getConvertedCurrency, getCurrentAccountWithSendEtherInfo, getCurrentCurrency, getCurrentNetwork, @@ -154,15 +153,6 @@ describe('send selectors', () => { }) }) - describe('getConvertedCurrency()', () => { - it('should return the currently selected currency', () => { - assert.equal( - getConvertedCurrency(mockState), - 'USD' - ) - }) - }) - describe('getCurrentAccountWithSendEtherInfo()', () => { it('should return the currently selected account with identity info', () => { assert.deepEqual( -- cgit From 491f2d03cfd790e4246a9fa638a2eecb07a6488a Mon Sep 17 00:00:00 2001 From: trejgun Date: Mon, 2 Jul 2018 09:36:57 +0800 Subject: move removeLeadingZeroes to utils, add test --- ui/app/components/send/currency-display.js | 5 +---- ui/app/components/send_/send.utils.js | 5 +++++ ui/app/components/send_/send.utils.test.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 ui/app/components/send_/send.utils.test.js (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index e410bc070..897ebad84 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -2,6 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') +const { removeLeadingZeroes } = require('../send_/send.utils') const currencyFormatter = require('currency-formatter') const currencies = require('currency-formatter/currencies') const ethUtil = require('ethereumjs-util') @@ -92,10 +93,6 @@ CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValu : convertedValue } -function removeLeadingZeroes (str) { - return str.replace(/^0*(?=\d)/, '') -} - CurrencyDisplay.prototype.handleChange = function (newVal) { this.setState({ valueToRender: removeLeadingZeroes(newVal) }) this.props.onChange(this.getAmount(newVal)) diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index dfd459731..313465fed 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -32,6 +32,7 @@ module.exports = { getToAddressForGasUpdate, isBalanceSufficient, isTokenBalanceSufficient, + removeLeadingZeroes, } function calcGasTotal (gasLimit, gasPrice) { @@ -273,3 +274,7 @@ function estimateGasPriceFromRecentBlocks (recentBlocks) { function getToAddressForGasUpdate (...addresses) { return [...addresses, ''].find(str => str !== undefined && str !== null).toLowerCase() } + +function removeLeadingZeroes (str) { + return str.replace(/^0*(?=\d)/, '') +} diff --git a/ui/app/components/send_/send.utils.test.js b/ui/app/components/send_/send.utils.test.js new file mode 100644 index 000000000..36f3a5c10 --- /dev/null +++ b/ui/app/components/send_/send.utils.test.js @@ -0,0 +1,30 @@ +import assert from 'assert' +import { removeLeadingZeroes } from './send.utils' + + +describe('send utils', () => { + describe('removeLeadingZeroes()', () => { + it('should remove leading zeroes from int when user types', () => { + assert.equal(removeLeadingZeroes('0'), '0') + assert.equal(removeLeadingZeroes('1'), '1') + assert.equal(removeLeadingZeroes('00'), '0') + assert.equal(removeLeadingZeroes('01'), '1') + }) + + it('should remove leading zeroes from int when user copy/paste', () => { + assert.equal(removeLeadingZeroes('001'), '1') + }) + + it('should remove leading zeroes from float when user types', () => { + assert.equal(removeLeadingZeroes('0.'), '0.') + assert.equal(removeLeadingZeroes('0.0'), '0.0') + assert.equal(removeLeadingZeroes('0.00'), '0.00') + assert.equal(removeLeadingZeroes('0.001'), '0.001') + assert.equal(removeLeadingZeroes('0.10'), '0.10') + }) + + it('should remove leading zeroes from float when user copy/paste', () => { + assert.equal(removeLeadingZeroes('00.1'), '0.1') + }) + }) +}) -- cgit From a8f745f9fe74751b87f500af3857b66d4c80f45e Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 2 Jul 2018 18:49:33 -0400 Subject: eslint --fix . --- ui/app/components/dropdowns/token-menu-dropdown.js | 6 +++--- ui/app/components/pages/create-account/index.js | 2 +- ui/app/components/send_/account-list-item/index.js | 2 +- ui/app/components/send_/index.js | 2 +- ui/app/components/send_/send-content/index.js | 2 +- .../send_/send-content/send-amount-row/amount-max-button/index.js | 2 +- ui/app/components/send_/send-content/send-amount-row/index.js | 2 +- ui/app/components/send_/send-content/send-dropdown-list/index.js | 2 +- .../send_/send-content/send-from-row/from-dropdown/index.js | 2 +- ui/app/components/send_/send-content/send-from-row/index.js | 2 +- .../send-gas-row/gas-fee-display/gas-fee-display.component.js | 2 +- .../gas-fee-display/test/gas-fee-display.component.test.js | 2 +- ui/app/components/send_/send-content/send-gas-row/index.js | 2 +- ui/app/components/send_/send-content/send-row-wrapper/index.js | 2 +- .../send-content/send-row-wrapper/send-row-error-message/index.js | 2 +- ui/app/components/send_/send-content/send-to-row/index.js | 2 +- ui/app/components/send_/send-footer/index.js | 2 +- ui/app/components/send_/send-header/index.js | 2 +- ui/app/reducers/app.js | 2 +- ui/app/token-util.js | 2 +- 20 files changed, 22 insertions(+), 22 deletions(-) (limited to 'ui') diff --git a/ui/app/components/dropdowns/token-menu-dropdown.js b/ui/app/components/dropdowns/token-menu-dropdown.js index fac7c451b..5a794c7c1 100644 --- a/ui/app/components/dropdowns/token-menu-dropdown.js +++ b/ui/app/components/dropdowns/token-menu-dropdown.js @@ -54,7 +54,7 @@ TokenMenuDropdown.prototype.render = function () { showHideTokenConfirmationModal(this.props.token) this.props.onClose() }, - text: this.context.t('hideToken'), + text: this.context.t('hideToken'), }), h(Item, { onClick: (e) => { @@ -62,7 +62,7 @@ TokenMenuDropdown.prototype.render = function () { copyToClipboard(this.props.token.address) this.props.onClose() }, - text: this.context.t('copyContractAddress'), + text: this.context.t('copyContractAddress'), }), h(Item, { onClick: (e) => { @@ -71,7 +71,7 @@ TokenMenuDropdown.prototype.render = function () { global.platform.openWindow({ url }) this.props.onClose() }, - text: this.context.t('viewOnEtherscan'), + text: this.context.t('viewOnEtherscan'), }), ]) } diff --git a/ui/app/components/pages/create-account/index.js b/ui/app/components/pages/create-account/index.js index 6e3b93742..5681e43a9 100644 --- a/ui/app/components/pages/create-account/index.js +++ b/ui/app/components/pages/create-account/index.js @@ -42,7 +42,7 @@ class CreateAccountPage extends Component { render () { return h('div.new-account', {}, [ h('div.new-account__header', [ - h('div.new-account__title', this.context.t('newAccount') ), + h('div.new-account__title', this.context.t('newAccount')), this.renderTabs(), ]), h('div.new-account__form', [ diff --git a/ui/app/components/send_/account-list-item/index.js b/ui/app/components/send_/account-list-item/index.js index 1fca540be..907485cf7 100644 --- a/ui/app/components/send_/account-list-item/index.js +++ b/ui/app/components/send_/account-list-item/index.js @@ -1 +1 @@ -export { default } from './account-list-item.container' \ No newline at end of file +export { default } from './account-list-item.container' diff --git a/ui/app/components/send_/index.js b/ui/app/components/send_/index.js index 9a4dd5727..b5114babc 100644 --- a/ui/app/components/send_/index.js +++ b/ui/app/components/send_/index.js @@ -1 +1 @@ -export { default } from './send.container' \ No newline at end of file +export { default } from './send.container' diff --git a/ui/app/components/send_/send-content/index.js b/ui/app/components/send_/send-content/index.js index 10b3c850e..891c17e6a 100644 --- a/ui/app/components/send_/send-content/index.js +++ b/ui/app/components/send_/send-content/index.js @@ -1 +1 @@ -export { default } from './send-content.component' \ No newline at end of file +export { default } from './send-content.component' diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js index 548b51f33..ee8271494 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js @@ -1 +1 @@ -export { default } from './amount-max-button.container' \ No newline at end of file +export { default } from './amount-max-button.container' diff --git a/ui/app/components/send_/send-content/send-amount-row/index.js b/ui/app/components/send_/send-content/send-amount-row/index.js index 94a7da56f..abc6852fe 100644 --- a/ui/app/components/send_/send-content/send-amount-row/index.js +++ b/ui/app/components/send_/send-content/send-amount-row/index.js @@ -1 +1 @@ -export { default } from './send-amount-row.container' \ No newline at end of file +export { default } from './send-amount-row.container' diff --git a/ui/app/components/send_/send-content/send-dropdown-list/index.js b/ui/app/components/send_/send-content/send-dropdown-list/index.js index ee7736376..04af6536c 100644 --- a/ui/app/components/send_/send-content/send-dropdown-list/index.js +++ b/ui/app/components/send_/send-content/send-dropdown-list/index.js @@ -1 +1 @@ -export { default } from './send-dropdown-list.component' \ No newline at end of file +export { default } from './send-dropdown-list.component' diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js index 6ab9a157a..2314ef4e3 100644 --- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js +++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js @@ -1 +1 @@ -export { default } from './from-dropdown.component' \ No newline at end of file +export { default } from './from-dropdown.component' diff --git a/ui/app/components/send_/send-content/send-from-row/index.js b/ui/app/components/send_/send-content/send-from-row/index.js index 4a0916dba..0a79726b2 100644 --- a/ui/app/components/send_/send-content/send-from-row/index.js +++ b/ui/app/components/send_/send-content/send-from-row/index.js @@ -1 +1 @@ -export { default } from './send-from-row.container' \ No newline at end of file +export { default } from './send-from-row.container' diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js index b1fd67412..c8d619be5 100644 --- a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js @@ -14,7 +14,7 @@ export default class GasFeeDisplay extends Component { onClick: PropTypes.func, }; - render() { + render () { const { conversionRate, gasTotal, diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js index 66f3a94df..7cbe8d0df 100644 --- a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js +++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js @@ -10,7 +10,7 @@ const propsMethodSpies = { showCustomizeGasModal: sinon.spy(), } -describe('SendGasRow Component', function() { +describe('SendGasRow Component', function () { let wrapper beforeEach(() => { diff --git a/ui/app/components/send_/send-content/send-gas-row/index.js b/ui/app/components/send_/send-content/send-gas-row/index.js index 060ed7fd3..3c7ff1d5f 100644 --- a/ui/app/components/send_/send-content/send-gas-row/index.js +++ b/ui/app/components/send_/send-content/send-gas-row/index.js @@ -1 +1 @@ -export { default } from './send-gas-row.container' \ No newline at end of file +export { default } from './send-gas-row.container' diff --git a/ui/app/components/send_/send-content/send-row-wrapper/index.js b/ui/app/components/send_/send-content/send-row-wrapper/index.js index 5715f55c6..d17545dcc 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/index.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/index.js @@ -1 +1 @@ -export { default } from './send-row-wrapper.component' \ No newline at end of file +export { default } from './send-row-wrapper.component' diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js index bf49c55bd..c00617f83 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js @@ -1 +1 @@ -export { default } from './send-row-error-message.container' \ No newline at end of file +export { default } from './send-row-error-message.container' diff --git a/ui/app/components/send_/send-content/send-to-row/index.js b/ui/app/components/send_/send-content/send-to-row/index.js index 4e7aa9747..121f15148 100644 --- a/ui/app/components/send_/send-content/send-to-row/index.js +++ b/ui/app/components/send_/send-content/send-to-row/index.js @@ -1 +1 @@ -export { default } from './send-to-row.container' \ No newline at end of file +export { default } from './send-to-row.container' diff --git a/ui/app/components/send_/send-footer/index.js b/ui/app/components/send_/send-footer/index.js index cd1727330..58e91d622 100644 --- a/ui/app/components/send_/send-footer/index.js +++ b/ui/app/components/send_/send-footer/index.js @@ -1 +1 @@ -export { default } from './send-footer.container' \ No newline at end of file +export { default } from './send-footer.container' diff --git a/ui/app/components/send_/send-header/index.js b/ui/app/components/send_/send-header/index.js index b808eabbf..0b17f0b7d 100644 --- a/ui/app/components/send_/send-header/index.js +++ b/ui/app/components/send_/send-header/index.js @@ -1 +1 @@ -export { default } from './send-header.container' \ No newline at end of file +export { default } from './send-header.container' diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 9cacf5fe7..f453812b9 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -684,7 +684,7 @@ function reduceApp (state, action) { case actions.GAS_LOADING_FINISHED: return extend(appState, { gasIsLoading: false, - }) + }) default: return appState diff --git a/ui/app/token-util.js b/ui/app/token-util.js index 8c5b37d7b..cd6a47dbc 100644 --- a/ui/app/token-util.js +++ b/ui/app/token-util.js @@ -20,7 +20,7 @@ async function getSymbolAndDecimals (tokenAddress, existingTokens = []) { if (existingToken) { return existingToken } - + let result = [] try { const token = util.getContractAtAddress(tokenAddress) -- cgit