From 7294aede4fc33e950f84147f1b7402675f53398d Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 20 Aug 2018 20:16:54 -0700 Subject: Add new variant for SenderToRecipient component --- .../send-to-row/send-to-row.component.js | 2 +- .../tests/send-to-row-component.test.js | 2 +- ui/app/components/sender-to-recipient/index.scss | 147 ++++++++++++++------- .../sender-to-recipient.component.js | 141 ++++++++++++++------ .../sender-to-recipient.constants.js | 3 + 5 files changed, 199 insertions(+), 96 deletions(-) create mode 100644 ui/app/components/sender-to-recipient/sender-to-recipient.constants.js (limited to 'ui') 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 1163dcffc..434db81e5 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 @@ -48,7 +48,7 @@ export default class SendToRow extends Component { return ( + + + ) + } + + renderSenderAddress () { + const { t } = this.context + const { senderName, senderAddress, addressOnly } = this.props + + return ( + this.setState({ senderAddressCopied: false })} + > +
+ { addressOnly ? `${t('from')}: ${senderAddress}` : senderName } +
+
+ ) + } + + renderRecipientIdenticon () { + const { recipientAddress } = this.props + + return !this.props.addressOnly && ( +
+ +
+ ) + } + renderRecipientWithAddress () { const { t } = this.context - const { recipientName, recipientAddress } = this.props + const { recipientName, recipientAddress, addressOnly } = this.props return (
{ this.setState({ recipientAddressCopied: true }) copyToClipboard(recipientAddress) }} > -
- -
+ { this.renderRecipientIdenticon() } this.setState({ recipientAddressCopied: false })} > -
- { recipientName || this.context.t('newContract') } +
+ { + addressOnly + ? `${t('to')}: ${recipientAddress}` + : (recipientName || this.context.t('newContract')) + }
@@ -57,46 +112,25 @@ export default class SenderToRecipient extends Component { renderRecipientWithoutAddress () { return ( -
+
-
+
{ this.context.t('newContract') }
) } - render () { - const { t } = this.context - const { senderName, senderAddress, recipientAddress } = this.props - - return ( -
-
{ - this.setState({ senderAddressCopied: true }) - copyToClipboard(senderAddress) - }} - > -
- -
- this.setState({ senderAddressCopied: false })} - > -
- { senderName } -
-
+ renderArrow () { + return this.props.variant === CARDS_VARIANT + ? ( +
+
+ ) : (
+ ) + } + + render () { + const { senderAddress, recipientAddress, variant } = this.props + + return ( +
+
{ + this.setState({ senderAddressCopied: true }) + copyToClipboard(senderAddress) + }} + > + { this.renderSenderIdenticon() } + { this.renderSenderAddress() } +
+ { this.renderArrow() } { recipientAddress ? this.renderRecipientWithAddress() diff --git a/ui/app/components/sender-to-recipient/sender-to-recipient.constants.js b/ui/app/components/sender-to-recipient/sender-to-recipient.constants.js new file mode 100644 index 000000000..166228932 --- /dev/null +++ b/ui/app/components/sender-to-recipient/sender-to-recipient.constants.js @@ -0,0 +1,3 @@ +// Component design variants +export const DEFAULT_VARIANT = 'DEFAULT_VARIANT' +export const CARDS_VARIANT = 'CARDS_VARIANT' -- cgit v1.2.3 From 456f2faf4f1e95a618c480d373d87b0a32c97782 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 20 Aug 2018 12:36:01 -0230 Subject: Handle case where keyring is missing in AccountDetailsModal --- ui/app/components/modals/account-details-modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js index cc90cf578..bc577fda0 100644 --- a/ui/app/components/modals/account-details-modal.js +++ b/ui/app/components/modals/account-details-modal.js @@ -61,7 +61,7 @@ AccountDetailsModal.prototype.render = function () { let exportPrivateKeyFeatureEnabled = true // This feature is disabled for hardware wallets - if (keyring.type.search('Hardware') !== -1) { + if (keyring && keyring.type.search('Hardware') !== -1) { exportPrivateKeyFeatureEnabled = false } -- cgit v1.2.3 From 743c6e7ca4a9b20a2d8e2e1909e0891a303ca92e Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 20 Aug 2018 12:38:40 -0230 Subject: Clear warnings when exportAccount succeeds --- .../components/modals/export-private-key-modal.js | 23 ++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js index 80ece425f..163d1b15b 100644 --- a/ui/app/components/modals/export-private-key-modal.js +++ b/ui/app/components/modals/export-private-key-modal.js @@ -1,3 +1,4 @@ +const log = require('loglevel') const Component = require('react').Component const PropTypes = require('prop-types') const h = require('react-hyperscript') @@ -23,7 +24,13 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return { - exportAccount: (password, address) => dispatch(actions.exportAccount(password, address)), + exportAccount: (password, address) => { + return dispatch(actions.exportAccount(password, address)) + .then((res) => { + dispatch(actions.hideWarning()) + return res + }) + }, showAccountDetailModal: () => dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })), hideModal: () => dispatch(actions.hideModal()), } @@ -36,6 +43,7 @@ function ExportPrivateKeyModal () { this.state = { password: '', privateKey: null, + showWarning: true, } } @@ -50,7 +58,11 @@ ExportPrivateKeyModal.prototype.exportAccountAndGetPrivateKey = function (passwo const { exportAccount } = this.props exportAccount(password, address) - .then(privateKey => this.setState({ privateKey })) + .then(privateKey => this.setState({ + privateKey, + showWarning: false, + })) + .catch((e) => log.error(e)) } ExportPrivateKeyModal.prototype.renderPasswordLabel = function (privateKey) { @@ -110,7 +122,10 @@ ExportPrivateKeyModal.prototype.render = function () { } = this.props const { name, address } = selectedIdentity - const { privateKey } = this.state + const { + privateKey, + showWarning, + } = this.state return h(AccountModalContainer, { showBackButton: previousModalState === 'ACCOUNT_DETAILS', @@ -134,7 +149,7 @@ ExportPrivateKeyModal.prototype.render = function () { this.renderPasswordInput(privateKey), - !warning ? null : h('span.private-key-password-error', warning), + showWarning && warning ? h('span.private-key-password-error', warning) : null, ]), h('div.private-key-password-warning', this.context.t('privateKeyWarning')), -- cgit v1.2.3 From a90c152485b84d6b382218543a6b38918a3ce6cf Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 16 Aug 2018 14:58:58 -0230 Subject: Update AccountModalContainer to accept a selectedIdentity prop --- ui/app/components/modals/account-modal-container.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/modals/account-modal-container.js b/ui/app/components/modals/account-modal-container.js index a9856b20f..aa0593df8 100644 --- a/ui/app/components/modals/account-modal-container.js +++ b/ui/app/components/modals/account-modal-container.js @@ -7,9 +7,9 @@ const actions = require('../../actions') const { getSelectedIdentity } = require('../../selectors') const Identicon = require('../identicon') -function mapStateToProps (state) { +function mapStateToProps (state, ownProps) { return { - selectedIdentity: getSelectedIdentity(state), + selectedIdentity: ownProps.selectedIdentity || getSelectedIdentity(state), } } -- cgit v1.2.3 From 1e8e8bdfc87903249320b97d569c64c55a524899 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Tue, 14 Aug 2018 14:42:30 -0230 Subject: Don't re-render the export modal when the selected identity changes --- .../components/modals/export-private-key-modal.js | 25 +++++++++++++++------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'ui') diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js index 80ece425f..99b61bf9d 100644 --- a/ui/app/components/modals/export-private-key-modal.js +++ b/ui/app/components/modals/export-private-key-modal.js @@ -11,13 +11,21 @@ const ReadOnlyInput = require('../readonly-input') const copyToClipboard = require('copy-to-clipboard') const { checksumAddress } = require('../../util') -function mapStateToProps (state) { - return { - warning: state.appState.warning, - privateKey: state.appState.accountDetail.privateKey, - network: state.metamask.network, - selectedIdentity: getSelectedIdentity(state), - previousModalState: state.appState.modal.previousModalState.name, +function mapStateToPropsFactory () { + let selectedIdentity = null + return function mapStateToProps (state) { + // We should **not** change the identity displayed here even if it changes from underneath us. + // If we do, we will be showing the user one private key and a **different** address and name. + // Note that the selected identity **will** change from underneath us when we unlock the keyring + // which is the expected behavior that we are side-stepping. + selectedIdentity = selectedIdentity || getSelectedIdentity(state) + return { + warning: state.appState.warning, + privateKey: state.appState.accountDetail.privateKey, + network: state.metamask.network, + selectedIdentity, + previousModalState: state.appState.modal.previousModalState.name, + } } } @@ -43,7 +51,7 @@ ExportPrivateKeyModal.contextTypes = { t: PropTypes.func, } -module.exports = connect(mapStateToProps, mapDispatchToProps)(ExportPrivateKeyModal) +module.exports = connect(mapStateToPropsFactory, mapDispatchToProps)(ExportPrivateKeyModal) ExportPrivateKeyModal.prototype.exportAccountAndGetPrivateKey = function (password, address) { @@ -113,6 +121,7 @@ ExportPrivateKeyModal.prototype.render = function () { const { privateKey } = this.state return h(AccountModalContainer, { + selectedIdentity, showBackButton: previousModalState === 'ACCOUNT_DETAILS', backButtonAction: () => showAccountDetailModal(), }, [ -- cgit v1.2.3 From 01c0c98501c02623b0cd650483d14c99566ce0af Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 18 Jul 2018 17:47:01 -0700 Subject: Add tabs support for PageContainer --- ui/app/components/page-container/index.scss | 2 +- .../page-container-header.component.js | 28 +++++-- .../page-container/page-container.component.js | 95 ++++++++++++++++++---- .../pages/add-token/add-token.component.js | 82 ++++++------------- ui/app/components/tabs/tab/tab.component.js | 8 +- 5 files changed, 132 insertions(+), 83 deletions(-) (limited to 'ui') diff --git a/ui/app/components/page-container/index.scss b/ui/app/components/page-container/index.scss index 06c3ef709..14cdbacd3 100644 --- a/ui/app/components/page-container/index.scss +++ b/ui/app/components/page-container/index.scss @@ -109,7 +109,7 @@ &--selected { color: $curious-blue; - border-bottom: 3px solid $curious-blue; + border-bottom: 2px solid $curious-blue; } } 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 index 5a5de1e5a..338598e5f 100644 --- 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 @@ -1,8 +1,8 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' +import classnames from 'classnames' export default class PageContainerHeader extends Component { - static propTypes = { title: PropTypes.string, subtitle: PropTypes.string, @@ -11,8 +11,18 @@ export default class PageContainerHeader extends Component { onBackButtonClick: PropTypes.func, backButtonStyles: PropTypes.object, backButtonString: PropTypes.string, - children: PropTypes.node, - }; + tabs: PropTypes.node, + } + + renderTabs () { + const { tabs } = this.props + + return tabs && ( +
    + { tabs } +
+ ) + } renderHeaderRow () { const { showBackButton, onBackButtonClick, backButtonStyles, backButtonString } = this.props @@ -31,15 +41,18 @@ export default class PageContainerHeader extends Component { } render () { - const { title, subtitle, onClose, children } = this.props + const { title, subtitle, onClose, tabs } = this.props return ( -
+
{ this.renderHeaderRow() } - { children } - { title &&
{ title } @@ -59,6 +72,7 @@ export default class PageContainerHeader extends Component { /> } + { this.renderTabs() }
) } diff --git a/ui/app/components/page-container/page-container.component.js b/ui/app/components/page-container/page-container.component.js index 9bfb99ade..10923d2fd 100644 --- a/ui/app/components/page-container/page-container.component.js +++ b/ui/app/components/page-container/page-container.component.js @@ -5,26 +5,93 @@ import PageContainerHeader from './page-container-header' import PageContainerFooter from './page-container-footer' export default class PageContainer extends Component { - static propTypes = { // PageContainerHeader props - title: PropTypes.string.isRequired, - subtitle: PropTypes.string, + backButtonString: PropTypes.string, + backButtonStyles: PropTypes.object, + onBackButtonClick: PropTypes.func, onClose: PropTypes.func, showBackButton: PropTypes.bool, - onBackButtonClick: PropTypes.func, - backButtonStyles: PropTypes.object, - backButtonString: PropTypes.string, + subtitle: PropTypes.string, + title: PropTypes.string.isRequired, + // Tabs-related props + defaultActiveTabIndex: PropTypes.number, + tabsComponent: PropTypes.node, // Content props - ContentComponent: PropTypes.func, - contentComponentProps: PropTypes.object, + contentComponent: PropTypes.node, // PageContainerFooter props - onCancel: PropTypes.func, cancelText: PropTypes.string, + disabled: PropTypes.bool, + onCancel: PropTypes.func, onSubmit: PropTypes.func, submitText: PropTypes.string, - disabled: PropTypes.bool, - }; + } + + state = { + activeTabIndex: 0, + } + + componentDidMount () { + const { defaultActiveTabIndex } = this.props + + if (defaultActiveTabIndex) { + this.setState({ activeTabIndex: defaultActiveTabIndex }) + } + } + + handleTabClick (tabIndex) { + const { activeTabIndex } = this.state + + if (tabIndex !== activeTabIndex) { + this.setState({ + activeTabIndex: tabIndex, + }) + } + } + + renderTabs () { + const { tabsComponent } = this.props + + if (!tabsComponent) { + return + } + + const numberOfTabs = React.Children.count(tabsComponent.props.children) + + return React.Children.map(tabsComponent.props.children, (child, index) => { + return child && React.cloneElement(child, { + onClick: index => this.handleTabClick(index), + tabIndex: index, + isActive: numberOfTabs > 1 && index === this.state.activeTabIndex, + key: index, + className: 'page-container__tab', + activeClassName: 'page-container__tab--selected', + }) + }) + } + + renderActiveTabContent () { + const { tabsComponent } = this.props + const { children } = tabsComponent.props + const { activeTabIndex } = this.state + + return children[activeTabIndex] + ? children[activeTabIndex].props.children + : children.props.children + } + + renderContent () { + const { contentComponent, tabsComponent } = this.props + + switch (true) { + case Boolean(contentComponent): + return contentComponent + case Boolean(tabsComponent): + return this.renderActiveTabContent() + default: + return null + } + } render () { const { @@ -35,8 +102,6 @@ export default class PageContainer extends Component { onBackButtonClick, backButtonStyles, backButtonString, - ContentComponent, - contentComponentProps, onCancel, cancelText, onSubmit, @@ -54,9 +119,10 @@ export default class PageContainer extends Component { onBackButtonClick={onBackButtonClick} backButtonStyles={backButtonStyles} backButtonString={backButtonString} + tabs={this.renderTabs()} />
- + { this.renderContent() }
) } - } 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 bcb93d401..59748ff46 100644 --- a/ui/app/components/pages/add-token/add-token.component.js +++ b/ui/app/components/pages/add-token/add-token.component.js @@ -1,14 +1,14 @@ import React, { Component } from 'react' -import classnames from 'classnames' import PropTypes from 'prop-types' import ethUtil from 'ethereumjs-util' import { checkExistingAddresses } from './util' import { tokenInfoGetter } from '../../../token-util' import { DEFAULT_ROUTE, CONFIRM_ADD_TOKEN_ROUTE } from '../../../routes' -import Button from '../../button' import TextField from '../../text-field' import TokenList from './token-list' import TokenSearch from './token-search' +import PageContainer from '../../page-container' +import { Tabs, Tab } from '../../tabs' const emptyAddr = '0x0000000000000000000000000000000000000000' const SEARCH_TAB = 'SEARCH' @@ -285,65 +285,33 @@ class AddToken extends Component { ) } + renderTabs () { + return ( + + + { this.renderSearchToken() } + + + { this.renderCustomTokenForm() } + + + ) + } + render () { - const { displayedTab } = this.state const { history, clearPendingTokens } = this.props return ( -
-
-
- { this.context.t('addTokens') } -
-
-
this.setState({ displayedTab: SEARCH_TAB })} - > - { this.context.t('search') } -
-
this.setState({ displayedTab: CUSTOM_TOKEN_TAB })} - > - { this.context.t('customToken') } -
-
-
-
- { - displayedTab === CUSTOM_TOKEN_TAB - ? this.renderCustomTokenForm() - : this.renderSearchToken() - } -
-
- - -
-
+ this.handleNext()} + disabled={this.hasError() || !this.hasSelected()} + onCancel={() => { + clearPendingTokens() + history.push(DEFAULT_ROUTE) + }} + /> ) } } diff --git a/ui/app/components/tabs/tab/tab.component.js b/ui/app/components/tabs/tab/tab.component.js index a59da8904..481513233 100644 --- a/ui/app/components/tabs/tab/tab.component.js +++ b/ui/app/components/tabs/tab/tab.component.js @@ -3,13 +3,13 @@ import PropTypes from 'prop-types' import classnames from 'classnames' const Tab = props => { - const { name, onClick, isActive, tabIndex } = props + const { name, onClick, isActive, tabIndex, className, activeClassName } = props return (
  • { event.preventDefault() @@ -26,6 +26,8 @@ Tab.propTypes = { onClick: PropTypes.func, isActive: PropTypes.bool, tabIndex: PropTypes.number, + className: PropTypes.string, + activeClassName: PropTypes.string, } export default Tab -- cgit v1.2.3 From d7d141cea54ba7bbeb2e7db9fb7ed54ce0733d4b Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 20 Jul 2018 15:42:28 -0700 Subject: Fix code readability, use PureComponent over Component --- .../page-container-header.component.js | 2 +- .../page-container/page-container.component.js | 45 ++++++++-------------- ui/app/components/tabs/tab/tab.component.js | 9 ++++- 3 files changed, 23 insertions(+), 33 deletions(-) (limited to 'ui') 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 index 338598e5f..a8458604e 100644 --- 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 @@ -47,7 +47,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 10923d2fd..3a2274a29 100644 --- a/ui/app/components/page-container/page-container.component.js +++ b/ui/app/components/page-container/page-container.component.js @@ -1,10 +1,10 @@ -import React, { Component } from 'react' +import React, { PureComponent } 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 { +export default class PageContainer extends PureComponent { static propTypes = { // PageContainerHeader props backButtonString: PropTypes.string, @@ -28,25 +28,11 @@ export default class PageContainer extends Component { } state = { - activeTabIndex: 0, + activeTabIndex: this.props.defaultActiveTabIndex || 0, } - componentDidMount () { - const { defaultActiveTabIndex } = this.props - - if (defaultActiveTabIndex) { - this.setState({ activeTabIndex: defaultActiveTabIndex }) - } - } - - handleTabClick (tabIndex) { - const { activeTabIndex } = this.state - - if (tabIndex !== activeTabIndex) { - this.setState({ - activeTabIndex: tabIndex, - }) - } + handleTabClick (activeTabIndex) { + this.setState({ activeTabIndex }) } renderTabs () { @@ -58,12 +44,12 @@ export default class PageContainer extends Component { const numberOfTabs = React.Children.count(tabsComponent.props.children) - return React.Children.map(tabsComponent.props.children, (child, index) => { + return React.Children.map(tabsComponent.props.children, (child, tabIndex) => { return child && React.cloneElement(child, { onClick: index => this.handleTabClick(index), - tabIndex: index, - isActive: numberOfTabs > 1 && index === this.state.activeTabIndex, - key: index, + tabIndex, + isActive: numberOfTabs > 1 && tabIndex === this.state.activeTabIndex, + key: tabIndex, className: 'page-container__tab', activeClassName: 'page-container__tab--selected', }) @@ -83,13 +69,12 @@ export default class PageContainer extends Component { renderContent () { const { contentComponent, tabsComponent } = this.props - switch (true) { - case Boolean(contentComponent): - return contentComponent - case Boolean(tabsComponent): - return this.renderActiveTabContent() - default: - return null + if (contentComponent) { + return contentComponent + } else if (tabsComponent) { + return this.renderActiveTabContent() + } else { + return null } } diff --git a/ui/app/components/tabs/tab/tab.component.js b/ui/app/components/tabs/tab/tab.component.js index 481513233..9e590391c 100644 --- a/ui/app/components/tabs/tab/tab.component.js +++ b/ui/app/components/tabs/tab/tab.component.js @@ -8,8 +8,8 @@ const Tab = props => { return (
  • { event.preventDefault() @@ -30,4 +30,9 @@ Tab.propTypes = { activeClassName: PropTypes.string, } +Tab.defaultProps = { + className: 'tab', + activeClassName: 'tab--active', +} + export default Tab -- cgit v1.2.3 From 865a0d8173e27c073f00d0d156b95e8d92208a94 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 17 Aug 2018 14:34:14 -0700 Subject: Use eth_getCode to sort transaction action type --- .../confirm-transaction-switch.component.js | 13 +++- .../confirm-transaction-switch.container.js | 6 +- ui/app/ducks/confirm-transaction.duck.js | 42 +++++++++---- .../ducks/tests/confirm-transaction.duck.test.js | 70 +++++++++++++++------- ui/app/helpers/confirm-transaction/util.js | 5 ++ 5 files changed, 99 insertions(+), 37 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js index 0280f73c6..d494977cd 100644 --- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js +++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js @@ -23,14 +23,16 @@ export default class ConfirmTransactionSwitch extends Component { static propTypes = { txData: PropTypes.object, methodData: PropTypes.object, - fetchingMethodData: PropTypes.bool, + fetchingData: PropTypes.bool, + isEtherTransaction: PropTypes.bool, } redirectToTransaction () { const { txData, methodData: { name }, - fetchingMethodData, + fetchingData, + isEtherTransaction, } = this.props const { id, txParams: { data } = {} } = txData @@ -39,10 +41,15 @@ export default class ConfirmTransactionSwitch extends Component { return } - if (fetchingMethodData) { + if (fetchingData) { return } + if (isEtherTransaction) { + const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}` + return + } + if (data) { const methodName = name && name.toLowerCase() diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js index 3d7fc78cc..7f2c36af2 100644 --- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js +++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js @@ -6,14 +6,16 @@ const mapStateToProps = state => { confirmTransaction: { txData, methodData, - fetchingMethodData, + fetchingData, + toSmartContract, }, } = state return { txData, methodData, - fetchingMethodData, + fetchingData, + isEtherTransaction: !toSmartContract, } } diff --git a/ui/app/ducks/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction.duck.js index 1885e12d1..f17933ddd 100644 --- a/ui/app/ducks/confirm-transaction.duck.js +++ b/ui/app/ducks/confirm-transaction.duck.js @@ -14,6 +14,7 @@ import { addEth, increaseLastGasPrice, hexGreaterThan, + isSmartContractAddress, } from '../helpers/confirm-transaction/util' import { getSymbolAndDecimals } from '../token-util' @@ -35,8 +36,9 @@ const UPDATE_TRANSACTION_TOTALS = createActionType('UPDATE_TRANSACTION_TOTALS') const UPDATE_HEX_GAS_TOTAL = createActionType('UPDATE_HEX_GAS_TOTAL') const UPDATE_TOKEN_PROPS = createActionType('UPDATE_TOKEN_PROPS') const UPDATE_NONCE = createActionType('UPDATE_NONCE') -const FETCH_METHOD_DATA_START = createActionType('FETCH_METHOD_DATA_START') -const FETCH_METHOD_DATA_END = createActionType('FETCH_METHOD_DATA_END') +const UPDATE_TO_SMART_CONTRACT = createActionType('UPDATE_TO_SMART_CONTRACT') +const FETCH_DATA_START = createActionType('FETCH_DATA_START') +const FETCH_DATA_END = createActionType('FETCH_DATA_END') // Initial state const initState = { @@ -55,7 +57,8 @@ const initState = { ethTransactionTotal: '', hexGasTotal: '', nonce: '', - fetchingMethodData: false, + toSmartContract: false, + fetchingData: false, } // Reducer @@ -138,15 +141,20 @@ export default function reducer ({ confirmTransaction: confirmState = initState ...confirmState, nonce: action.payload, } - case FETCH_METHOD_DATA_START: + case UPDATE_TO_SMART_CONTRACT: return { ...confirmState, - fetchingMethodData: true, + toSmartContract: action.payload, } - case FETCH_METHOD_DATA_END: + case FETCH_DATA_START: return { ...confirmState, - fetchingMethodData: false, + fetchingData: true, + } + case FETCH_DATA_END: + return { + ...confirmState, + fetchingData: false, } case CLEAR_CONFIRM_TRANSACTION: return initState @@ -237,9 +245,16 @@ export function updateNonce (nonce) { } } -export function setFetchingMethodData (isFetching) { +export function updateToSmartContract (toSmartContract) { + return { + type: UPDATE_TO_SMART_CONTRACT, + payload: toSmartContract, + } +} + +export function setFetchingData (isFetching) { return { - type: isFetching ? FETCH_METHOD_DATA_START : FETCH_METHOD_DATA_END, + type: isFetching ? FETCH_DATA_START : FETCH_DATA_END, } } @@ -338,19 +353,22 @@ export function setTransactionToConfirm (transactionId) { dispatch(updateTxDataAndCalculate(txData)) const { txParams } = transaction + const { to } = txParams if (txParams.data) { const { tokens: existingTokens } = state const { data, to: tokenAddress } = txParams try { - dispatch(setFetchingMethodData(true)) + dispatch(setFetchingData(true)) const methodData = await getMethodData(data) dispatch(updateMethodData(methodData)) - dispatch(setFetchingMethodData(false)) + const toSmartContract = await isSmartContractAddress(to) + dispatch(updateToSmartContract(toSmartContract)) + dispatch(setFetchingData(false)) } catch (error) { dispatch(updateMethodData({})) - dispatch(setFetchingMethodData(false)) + dispatch(setFetchingData(false)) } const tokenData = getTokenData(data) diff --git a/ui/app/ducks/tests/confirm-transaction.duck.test.js b/ui/app/ducks/tests/confirm-transaction.duck.test.js index 111674e33..1bab0add0 100644 --- a/ui/app/ducks/tests/confirm-transaction.duck.test.js +++ b/ui/app/ducks/tests/confirm-transaction.duck.test.js @@ -1,6 +1,7 @@ import assert from 'assert' import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' +import sinon from 'sinon' import ConfirmTransactionReducer, * as actions from '../confirm-transaction.duck.js' @@ -20,7 +21,8 @@ const initialState = { ethTransactionTotal: '', hexGasTotal: '', nonce: '', - fetchingMethodData: false, + toSmartContract: false, + fetchingData: false, } const UPDATE_TX_DATA = 'metamask/confirm-transaction/UPDATE_TX_DATA' @@ -35,8 +37,9 @@ const UPDATE_TRANSACTION_TOTALS = 'metamask/confirm-transaction/UPDATE_TRANSACTI const UPDATE_HEX_GAS_TOTAL = 'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL' const UPDATE_TOKEN_PROPS = 'metamask/confirm-transaction/UPDATE_TOKEN_PROPS' const UPDATE_NONCE = 'metamask/confirm-transaction/UPDATE_NONCE' -const FETCH_METHOD_DATA_START = 'metamask/confirm-transaction/FETCH_METHOD_DATA_START' -const FETCH_METHOD_DATA_END = 'metamask/confirm-transaction/FETCH_METHOD_DATA_END' +const UPDATE_TO_SMART_CONTRACT = 'metamask/confirm-transaction/UPDATE_TO_SMART_CONTRACT' +const FETCH_DATA_START = 'metamask/confirm-transaction/FETCH_DATA_START' +const FETCH_DATA_END = 'metamask/confirm-transaction/FETCH_DATA_END' const CLEAR_CONFIRM_TRANSACTION = 'metamask/confirm-transaction/CLEAR_CONFIRM_TRANSACTION' describe('Confirm Transaction Duck', () => { @@ -64,7 +67,8 @@ describe('Confirm Transaction Duck', () => { ethTransactionTotal: '469.27', hexGasTotal: '0x1319718a5000', nonce: '0x0', - fetchingMethodData: false, + toSmartContract: false, + fetchingData: false, }, } @@ -271,30 +275,43 @@ describe('Confirm Transaction Duck', () => { ) }) - it('should set fetchingMethodData to true when receiving a FETCH_METHOD_DATA_START action', () => { + it('should update nonce when receiving an UPDATE_TO_SMART_CONTRACT action', () => { assert.deepEqual( ConfirmTransactionReducer(mockState, { - type: FETCH_METHOD_DATA_START, + type: UPDATE_TO_SMART_CONTRACT, + payload: true, }), { ...mockState.confirmTransaction, - fetchingMethodData: true, + toSmartContract: true, } ) }) - it('should set fetchingMethodData to false when receiving a FETCH_METHOD_DATA_END action', () => { + it('should set fetchingData to true when receiving a FETCH_DATA_START action', () => { assert.deepEqual( - ConfirmTransactionReducer({ confirmTransaction: { fetchingMethodData: true } }, { - type: FETCH_METHOD_DATA_END, + ConfirmTransactionReducer(mockState, { + type: FETCH_DATA_START, }), { - fetchingMethodData: false, + ...mockState.confirmTransaction, + fetchingData: true, } ) }) - it('should clear confirmTransaction when receiving a FETCH_METHOD_DATA_END action', () => { + it('should set fetchingData to false when receiving a FETCH_DATA_END action', () => { + assert.deepEqual( + ConfirmTransactionReducer({ confirmTransaction: { fetchingData: true } }, { + type: FETCH_DATA_END, + }), + { + fetchingData: false, + } + ) + }) + + it('should clear confirmTransaction when receiving a FETCH_DATA_END action', () => { assert.deepEqual( ConfirmTransactionReducer(mockState, { type: CLEAR_CONFIRM_TRANSACTION, @@ -460,24 +477,24 @@ describe('Confirm Transaction Duck', () => { ) }) - it('should create an action to set fetchingMethodData to true', () => { + it('should create an action to set fetchingData to true', () => { const expectedAction = { - type: FETCH_METHOD_DATA_START, + type: FETCH_DATA_START, } assert.deepEqual( - actions.setFetchingMethodData(true), + actions.setFetchingData(true), expectedAction ) }) - it('should create an action to set fetchingMethodData to false', () => { + it('should create an action to set fetchingData to false', () => { const expectedAction = { - type: FETCH_METHOD_DATA_END, + type: FETCH_DATA_END, } assert.deepEqual( - actions.setFetchingMethodData(false), + actions.setFetchingData(false), expectedAction ) }) @@ -495,6 +512,18 @@ describe('Confirm Transaction Duck', () => { }) describe('Thunk actions', done => { + beforeEach(() => { + global.eth = { + getCode: sinon.stub().callsFake( + address => Promise.resolve(address && address.match(/isContract/) ? 'not-0x' : '0x') + ), + } + }) + + afterEach(() => { + global.eth.getCode.resetHistory() + }) + it('updates txData and gas on an existing transaction in confirmTransaction', () => { const mockState = { metamask: { @@ -505,7 +534,7 @@ describe('Confirm Transaction Duck', () => { ethTransactionAmount: '1', ethTransactionFee: '0.000021', ethTransactionTotal: '1.000021', - fetchingMethodData: false, + fetchingData: false, fiatTransactionAmount: '469.26', fiatTransactionFee: '0.01', fiatTransactionTotal: '469.27', @@ -581,7 +610,7 @@ describe('Confirm Transaction Duck', () => { ethTransactionAmount: '1', ethTransactionFee: '0.000021', ethTransactionTotal: '1.000021', - fetchingMethodData: false, + fetchingData: false, fiatTransactionAmount: '469.26', fiatTransactionFee: '0.01', fiatTransactionTotal: '469.27', @@ -667,6 +696,7 @@ describe('Confirm Transaction Duck', () => { .then(() => { const storeActions = store.getActions() assert.equal(storeActions.length, expectedActions.length) + storeActions.forEach((action, index) => assert.equal(action.type, expectedActions[index])) done() }) diff --git a/ui/app/helpers/confirm-transaction/util.js b/ui/app/helpers/confirm-transaction/util.js index 76e80a8ac..3d0cb57e7 100644 --- a/ui/app/helpers/confirm-transaction/util.js +++ b/ui/app/helpers/confirm-transaction/util.js @@ -146,3 +146,8 @@ export function roundExponential (value) { // In JS, numbers with exponentials greater than 20 get displayed as an exponential. return bigNumberValue.e > 20 ? Number(bigNumberValue.toPrecision(PRECISION)) : value } + +export async function isSmartContractAddress (address) { + const code = await global.eth.getCode(address) + return code && code !== '0x' +} -- cgit v1.2.3 From b23cca14699b6c6a8c843c9cc020ec96fe758822 Mon Sep 17 00:00:00 2001 From: Evgeniy Filatov Date: Sun, 19 Aug 2018 20:25:33 +0300 Subject: implemented improvements to RPC history --- ui/app/components/dropdowns/network-dropdown.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js index e5363ff56..003ec1b98 100644 --- a/ui/app/components/dropdowns/network-dropdown.js +++ b/ui/app/components/dropdowns/network-dropdown.js @@ -275,7 +275,9 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { const rpcTarget = provider.rpcTarget return rpcList.map((rpc) => { - if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) { + const currentRpcTarget = provider.type === 'rpc' && rpc === rpcTarget + + if ((rpc === 'http://localhost:8545') || currentRpcTarget) { return null } else { return h( @@ -291,17 +293,17 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { }, }, [ - rpcTarget === rpc ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + currentRpcTarget ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), h('i.fa.fa-question-circle.fa-med.menu-icon-circle'), h('span.network-name-item', { style: { - color: rpcTarget === rpc ? '#ffffff' : '#9b9b9b', + color: currentRpcTarget ? '#ffffff' : '#9b9b9b', }, }, rpc), ] ) } - }) + }).reverse() } NetworkDropdown.prototype.renderCustomOption = function (provider) { -- cgit v1.2.3 From c0b5e8a088b7ed2ac557ad2b6bc6b55d9bae1919 Mon Sep 17 00:00:00 2001 From: Evgeniy Filatov Date: Sun, 19 Aug 2018 21:02:30 +0300 Subject: backported RPC improvements to oldui --- ui/app/components/dropdowns/network-dropdown.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js index 003ec1b98..b1aeed376 100644 --- a/ui/app/components/dropdowns/network-dropdown.js +++ b/ui/app/components/dropdowns/network-dropdown.js @@ -272,10 +272,9 @@ NetworkDropdown.prototype.getNetworkName = function () { NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { const props = this.props - const rpcTarget = provider.rpcTarget return rpcList.map((rpc) => { - const currentRpcTarget = provider.type === 'rpc' && rpc === rpcTarget + const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget if ((rpc === 'http://localhost:8545') || currentRpcTarget) { return null -- cgit v1.2.3 From 9a80d6e8598850fec00471c6101c194e90c30353 Mon Sep 17 00:00:00 2001 From: Evgeniy Filatov Date: Thu, 23 Aug 2018 01:26:30 +0300 Subject: updated docs, small improvement of recent RPC rendering --- ui/app/components/dropdowns/network-dropdown.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js index b1aeed376..63a30dd82 100644 --- a/ui/app/components/dropdowns/network-dropdown.js +++ b/ui/app/components/dropdowns/network-dropdown.js @@ -272,8 +272,9 @@ NetworkDropdown.prototype.getNetworkName = function () { NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { const props = this.props + const reversedRpcList = rpcList.slice().reverse() - return rpcList.map((rpc) => { + return reversedRpcList.map((rpc) => { const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget if ((rpc === 'http://localhost:8545') || currentRpcTarget) { @@ -302,7 +303,7 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { ] ) } - }).reverse() + }) } NetworkDropdown.prototype.renderCustomOption = function (provider) { -- cgit v1.2.3 From 7924dbc0c99730c500169c1a31cccd64b75cbce0 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 22 Aug 2018 16:54:18 -0700 Subject: Use eth-contract-metadata data if the token symbol or decimals fail on retrieval --- ui/app/token-util.js | 116 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 87 insertions(+), 29 deletions(-) (limited to 'ui') diff --git a/ui/app/token-util.js b/ui/app/token-util.js index 8798ed266..3d61ad1ca 100644 --- a/ui/app/token-util.js +++ b/ui/app/token-util.js @@ -1,55 +1,113 @@ const log = require('loglevel') const util = require('./util') const BigNumber = require('bignumber.js') +import contractMap from 'eth-contract-metadata' -function tokenInfoGetter () { - const tokens = {} +const casedContractMap = Object.keys(contractMap).reduce((acc, base) => { + return { + ...acc, + [base.toLowerCase()]: contractMap[base], + } +}, {}) - return async (address) => { - if (tokens[address]) { - return tokens[address] +const DEFAULT_SYMBOL = '' +const DEFAULT_DECIMALS = '0' + +async function getSymbolFromContract (tokenAddress) { + const token = util.getContractAtAddress(tokenAddress) + + try { + const result = await token.symbol() + return result[0] + } catch (error) { + log.warn(`symbol() call for token at address ${tokenAddress} resulted in error:`, error) + } +} + +async function getDecimalsFromContract (tokenAddress) { + const token = util.getContractAtAddress(tokenAddress) + + try { + const result = await token.decimals() + const decimalsBN = result[0] + return decimalsBN && decimalsBN.toString() + } catch (error) { + log.warn(`decimals() call for token at address ${tokenAddress} resulted in error:`, error) + } +} + +function getContractMetadata (tokenAddress) { + return tokenAddress && casedContractMap[tokenAddress.toLowerCase()] +} + +async function getSymbol (tokenAddress) { + let symbol = await getSymbolFromContract(tokenAddress) + + if (!symbol) { + const contractMetadataInfo = getContractMetadata(tokenAddress) + + if (contractMetadataInfo) { + symbol = contractMetadataInfo.symbol } + } - tokens[address] = await getSymbolAndDecimals(address) + return symbol +} - return tokens[address] +async function getDecimals (tokenAddress) { + let decimals = await getDecimalsFromContract(tokenAddress) + + if (!decimals || decimals === '0') { + const contractMetadataInfo = getContractMetadata(tokenAddress) + + if (contractMetadataInfo) { + decimals = contractMetadataInfo.decimals + } } + + return decimals } -async function getSymbolAndDecimals (tokenAddress, existingTokens = []) { +export async function getSymbolAndDecimals (tokenAddress, existingTokens = []) { const existingToken = existingTokens.find(({ address }) => tokenAddress === address) + if (existingToken) { - return existingToken + return { + symbol: existingToken.symbol, + decimals: existingToken.decimals, + } } - let result = [] + let symbol, decimals + try { - const token = util.getContractAtAddress(tokenAddress) + symbol = await getSymbol(tokenAddress) + decimals = await getDecimals(tokenAddress) + } catch (error) { + log.warn(`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`, error) + } - result = await Promise.all([ - token.symbol(), - token.decimals(), - ]) - } catch (err) { - log.warn(`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`, err) + return { + symbol: symbol || DEFAULT_SYMBOL, + decimals: decimals || DEFAULT_DECIMALS, } +} + +export function tokenInfoGetter () { + const tokens = {} - const [ symbol = [], decimals = [] ] = result + return async (address) => { + if (tokens[address]) { + return tokens[address] + } - return { - symbol: symbol[0] || null, - decimals: decimals[0] && decimals[0].toString() || null, + tokens[address] = await getSymbolAndDecimals(address) + + return tokens[address] } } -function calcTokenAmount (value, decimals) { +export function calcTokenAmount (value, decimals) { const multiplier = Math.pow(10, Number(decimals || 0)) return new BigNumber(String(value)).div(multiplier).toNumber() } - - -module.exports = { - tokenInfoGetter, - calcTokenAmount, - getSymbolAndDecimals, -} -- cgit v1.2.3 From 3b408715c745030aed6e720e6f19dc8f2aa511d1 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 24 Jul 2018 11:40:05 -0700 Subject: Refactor home screen and remove unused files --- ui/app/account-and-transaction-details.js | 33 --- ui/app/app.js | 4 +- ui/app/components/buy-button-subview.js | 267 ------------------------- ui/app/components/pages/home.js | 239 ---------------------- ui/app/components/pages/home/home.component.js | 73 +++++++ ui/app/components/pages/home/home.container.js | 34 ++++ ui/app/components/pages/home/index.js | 1 + ui/app/components/wallet-view.js | 7 +- ui/app/main-container.js | 49 ----- ui/app/new-keychain.js | 29 --- 10 files changed, 116 insertions(+), 620 deletions(-) delete mode 100644 ui/app/account-and-transaction-details.js delete mode 100644 ui/app/components/buy-button-subview.js delete mode 100644 ui/app/components/pages/home.js create mode 100644 ui/app/components/pages/home/home.component.js create mode 100644 ui/app/components/pages/home/home.container.js create mode 100644 ui/app/components/pages/home/index.js delete mode 100644 ui/app/main-container.js delete mode 100644 ui/app/new-keychain.js (limited to 'ui') diff --git a/ui/app/account-and-transaction-details.js b/ui/app/account-and-transaction-details.js deleted file mode 100644 index 03101d37a..000000000 --- a/ui/app/account-and-transaction-details.js +++ /dev/null @@ -1,33 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -// Main Views -const TxView = require('./components/tx-view') -const WalletView = require('./components/wallet-view') - -module.exports = AccountAndTransactionDetails - -inherits(AccountAndTransactionDetails, Component) -function AccountAndTransactionDetails () { - Component.call(this) -} - -AccountAndTransactionDetails.prototype.render = function () { - return h('div.account-and-transaction-details', [ - // wallet - h(WalletView, { - style: { - }, - responsiveDisplayClassname: '.lap-visible', - }, [ - ]), - - // transaction - h(TxView, { - style: { - }, - }, [ - ]), - ]) -} - diff --git a/ui/app/app.js b/ui/app/app.js index 4fcf092ca..f8e9a14db 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -18,7 +18,7 @@ const ConfirmTransaction = require('./components/pages/confirm-transaction') const WalletView = require('./components/wallet-view') // other views -const Home = require('./components/pages/home') +import Home from './components/pages/home' const Authenticated = require('./components/pages/authenticated') const Initialized = require('./components/pages/initialized') const Settings = require('./components/pages/settings') @@ -182,7 +182,7 @@ class App extends Component { }, [ // A second instance of Walletview is used for non-mobile viewports this.props.sidebarOpen ? h(WalletView, { - responsiveDisplayClassname: '.sidebar', + responsiveDisplayClassname: 'sidebar', style: {}, }) : undefined, diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js deleted file mode 100644 index c6957d2aa..000000000 --- a/ui/app/components/buy-button-subview.js +++ /dev/null @@ -1,267 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const inherits = require('util').inherits -const connect = require('react-redux').connect -const actions = require('../actions') -const CoinbaseForm = require('./coinbase-form') -const ShapeshiftForm = require('./shapeshift-form') -const Loading = require('./loading-screen') -const AccountPanel = require('./account-panel') -const RadioList = require('./custom-radio-list') -const { getNetworkDisplayName } = require('../../../app/scripts/controllers/network/util') - -BuyButtonSubview.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect(mapStateToProps)(BuyButtonSubview) - - -function mapStateToProps (state) { - return { - identity: state.appState.identity, - account: state.metamask.accounts[state.appState.buyView.buyAddress], - warning: state.appState.warning, - buyView: state.appState.buyView, - network: state.metamask.network, - provider: state.metamask.provider, - context: state.appState.currentView.context, - isSubLoading: state.appState.isSubLoading, - } -} - -inherits(BuyButtonSubview, Component) -function BuyButtonSubview () { - Component.call(this) -} - -BuyButtonSubview.prototype.render = function () { - return ( - h('div', { - style: { - width: '100%', - }, - }, [ - this.headerSubview(), - this.primarySubview(), - ]) - ) -} - -BuyButtonSubview.prototype.headerSubview = function () { - const props = this.props - const isLoading = props.isSubLoading - return ( - - h('.flex-column', { - style: { - alignItems: 'center', - }, - }, [ - - // header bar (back button, label) - h('.flex-row', { - style: { - alignItems: 'center', - justifyContent: 'center', - }, - }, [ - h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { - onClick: this.backButtonContext.bind(this), - style: { - position: 'absolute', - left: '10px', - }, - }), - h('h2.text-transform-uppercase.flex-center', { - style: { - width: '100vw', - background: 'rgb(235, 235, 235)', - color: 'rgb(174, 174, 174)', - paddingTop: '4px', - paddingBottom: '4px', - }, - }, this.context.t('depositEth')), - ]), - - // loading indication - h('div', { - style: { - position: 'absolute', - top: '57vh', - left: '49vw', - }, - }, [ - isLoading && h(Loading), - ]), - - // account panel - h('div', { - style: { - width: '80%', - }, - }, [ - h(AccountPanel, { - showFullAddress: true, - identity: props.identity, - account: props.account, - }), - ]), - - h('.flex-row', { - style: { - alignItems: 'center', - justifyContent: 'center', - }, - }, [ - h('h3.text-transform-uppercase.flex-center', { - style: { - paddingLeft: '15px', - width: '100vw', - background: 'rgb(235, 235, 235)', - color: 'rgb(174, 174, 174)', - paddingTop: '4px', - paddingBottom: '4px', - }, - }, this.context.t('selectService')), - ]), - - ]) - - ) -} - - -BuyButtonSubview.prototype.primarySubview = function () { - const props = this.props - const network = props.network - - switch (network) { - case 'loading': - return - - case '1': - return this.mainnetSubview() - - // Ropsten, Rinkeby, Kovan - case '3': - case '4': - case '42': - const networkName = getNetworkDisplayName(network) - const label = `${networkName} ${this.context.t('testFaucet')}` - return ( - h('div.flex-column', { - style: { - alignItems: 'center', - margin: '20px 50px', - }, - }, [ - h('button.text-transform-uppercase', { - onClick: () => this.props.dispatch(actions.buyEth({ network })), - style: { - marginTop: '15px', - }, - }, label), - // Kovan only: Dharma loans beta - network === '42' ? ( - h('button.text-transform-uppercase', { - onClick: () => this.navigateTo('https://borrow.dharma.io/'), - style: { - marginTop: '15px', - }, - }, this.context.t('borrowDharma')) - ) : null, - ]) - ) - - default: - return ( - h('h2.error', this.context.t('unknownNetworkId')) - ) - - } -} - -BuyButtonSubview.prototype.mainnetSubview = function () { - const props = this.props - - return ( - - h('.flex-column', { - style: { - alignItems: 'center', - }, - }, [ - - h('.flex-row.selected-exchange', { - style: { - position: 'relative', - right: '35px', - marginTop: '20px', - marginBottom: '20px', - }, - }, [ - h(RadioList, { - defaultFocus: props.buyView.subview, - labels: [ - 'Coinbase', - 'ShapeShift', - ], - subtext: { - 'Coinbase': `${this.context.t('crypto')}/${this.context.t('fiat')} (${this.context.t('usaOnly')})`, - 'ShapeShift': this.context.t('crypto'), - }, - onClick: this.radioHandler.bind(this), - }), - ]), - - h('h3.text-transform-uppercase', { - style: { - paddingLeft: '15px', - fontFamily: 'Montserrat Light', - width: '100vw', - background: 'rgb(235, 235, 235)', - color: 'rgb(174, 174, 174)', - paddingTop: '4px', - paddingBottom: '4px', - }, - }, props.buyView.subview), - - this.formVersionSubview(), - ]) - - ) -} - -BuyButtonSubview.prototype.formVersionSubview = function () { - const network = this.props.network - if (network === '1') { - if (this.props.buyView.formView.coinbase) { - return h(CoinbaseForm, this.props) - } else if (this.props.buyView.formView.shapeshift) { - return h(ShapeshiftForm, this.props) - } - } -} - -BuyButtonSubview.prototype.navigateTo = function (url) { - global.platform.openWindow({ url }) -} - -BuyButtonSubview.prototype.backButtonContext = function () { - if (this.props.context === 'confTx') { - this.props.dispatch(actions.showConfTxPage({transForward: false})) - } else { - this.props.dispatch(actions.goHome()) - } -} - -BuyButtonSubview.prototype.radioHandler = function (event) { - switch (event.target.title) { - case 'Coinbase': - return this.props.dispatch(actions.coinBaseSubview()) - case 'ShapeShift': - return this.props.dispatch(actions.shapeShiftSubview(this.props.provider.type)) - } -} diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js deleted file mode 100644 index 5e3fdc9af..000000000 --- a/ui/app/components/pages/home.js +++ /dev/null @@ -1,239 +0,0 @@ -const { Component } = require('react') -const { connect } = require('react-redux') -const PropTypes = require('prop-types') -const { Redirect, withRouter } = require('react-router-dom') -const { compose } = require('recompose') -const h = require('react-hyperscript') -const actions = require('../../actions') -const log = require('loglevel') - -// init -const NewKeyChainScreen = require('../../new-keychain') -// mascara -const MascaraBuyEtherScreen = require('../../../../mascara/src/app/first-time/buy-ether-screen').default - -// accounts -const MainContainer = require('../../main-container') - -// other views -const BuyView = require('../../components/buy-button-subview') -const QrView = require('../../components/qr-code') - -// Routes -const { - INITIALIZE_BACKUP_PHRASE_ROUTE, - RESTORE_VAULT_ROUTE, - CONFIRM_TRANSACTION_ROUTE, - NOTICE_ROUTE, -} = require('../../routes') - -const { unconfirmedTransactionsCountSelector } = require('../../selectors/confirm-transaction') - -class Home extends Component { - componentDidMount () { - const { - history, - unconfirmedTransactionsCount = 0, - } = this.props - - // unapprovedTxs and unapproved messages - if (unconfirmedTransactionsCount > 0) { - history.push(CONFIRM_TRANSACTION_ROUTE) - } - } - - render () { - log.debug('rendering primary') - const { - noActiveNotices, - lostAccounts, - forgottenPassword, - currentView, - activeAddress, - seedWords, - } = this.props - - // notices - if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) { - return h(Redirect, { - to: { - pathname: NOTICE_ROUTE, - }, - }) - } - - // seed words - if (seedWords) { - log.debug('rendering seed words') - return h(Redirect, { - to: { - pathname: INITIALIZE_BACKUP_PHRASE_ROUTE, - }, - }) - } - - if (forgottenPassword) { - log.debug('rendering restore vault screen') - return h(Redirect, { - to: { - pathname: RESTORE_VAULT_ROUTE, - }, - }) - } - - // show current view - switch (currentView.name) { - - case 'accountDetail': - log.debug('rendering main container') - return h(MainContainer, {key: 'account-detail'}) - - case 'newKeychain': - log.debug('rendering new keychain screen') - return h(NewKeyChainScreen, {key: 'new-keychain'}) - - case 'buyEth': - log.debug('rendering buy ether screen') - return h(BuyView, {key: 'buyEthView'}) - - case 'onboardingBuyEth': - log.debug('rendering onboarding buy ether screen') - return h(MascaraBuyEtherScreen, {key: 'buyEthView'}) - - case 'qr': - log.debug('rendering show qr screen') - return h('div', { - style: { - position: 'absolute', - height: '100%', - top: '0px', - left: '0px', - }, - }, [ - h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { - onClick: () => this.props.dispatch(actions.backToAccountDetail(activeAddress)), - style: { - marginLeft: '10px', - marginTop: '50px', - }, - }), - h('div', { - style: { - position: 'absolute', - left: '44px', - width: '285px', - }, - }, [ - h(QrView, {key: 'qr'}), - ]), - ]) - - default: - log.debug('rendering default, account detail screen') - return h(MainContainer, {key: 'account-detail'}) - } - } -} - -Home.propTypes = { - currentCurrency: PropTypes.string, - isLoading: PropTypes.bool, - loadingMessage: PropTypes.string, - network: PropTypes.string, - provider: PropTypes.object, - frequentRpcList: PropTypes.array, - currentView: PropTypes.object, - sidebarOpen: PropTypes.bool, - isMascara: PropTypes.bool, - isOnboarding: PropTypes.bool, - isUnlocked: PropTypes.bool, - networkDropdownOpen: PropTypes.bool, - history: PropTypes.object, - dispatch: PropTypes.func, - selectedAddress: PropTypes.string, - noActiveNotices: PropTypes.bool, - lostAccounts: PropTypes.array, - isInitialized: PropTypes.bool, - forgottenPassword: PropTypes.bool, - activeAddress: PropTypes.string, - unapprovedTxs: PropTypes.object, - seedWords: PropTypes.string, - unapprovedMsgCount: PropTypes.number, - unapprovedPersonalMsgCount: PropTypes.number, - unapprovedTypedMessagesCount: PropTypes.number, - welcomeScreenSeen: PropTypes.bool, - isPopup: PropTypes.bool, - isMouseUser: PropTypes.bool, - t: PropTypes.func, - unconfirmedTransactionsCount: PropTypes.number, -} - -function mapStateToProps (state) { - const { appState, metamask } = state - const { - networkDropdownOpen, - sidebarOpen, - isLoading, - loadingMessage, - } = appState - - const { - accounts, - address, - isInitialized, - noActiveNotices, - seedWords, - unapprovedTxs, - nextUnreadNotice, - lostAccounts, - unapprovedMsgCount, - unapprovedPersonalMsgCount, - unapprovedTypedMessagesCount, - } = metamask - const selected = address || Object.keys(accounts)[0] - - return { - // state from plugin - networkDropdownOpen, - sidebarOpen, - isLoading, - loadingMessage, - noActiveNotices, - isInitialized, - isUnlocked: state.metamask.isUnlocked, - selectedAddress: state.metamask.selectedAddress, - currentView: state.appState.currentView, - activeAddress: state.appState.activeAddress, - transForward: state.appState.transForward, - isMascara: state.metamask.isMascara, - isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized), - isPopup: state.metamask.isPopup, - seedWords: state.metamask.seedWords, - unapprovedTxs, - unapprovedMsgs: state.metamask.unapprovedMsgs, - unapprovedMsgCount, - unapprovedPersonalMsgCount, - unapprovedTypedMessagesCount, - menuOpen: state.appState.menuOpen, - network: state.metamask.network, - provider: state.metamask.provider, - forgottenPassword: state.appState.forgottenPassword, - nextUnreadNotice, - lostAccounts, - frequentRpcList: state.metamask.frequentRpcList || [], - currentCurrency: state.metamask.currentCurrency, - isMouseUser: state.appState.isMouseUser, - isRevealingSeedWords: state.metamask.isRevealingSeedWords, - Qr: state.appState.Qr, - welcomeScreenSeen: state.metamask.welcomeScreenSeen, - - // state needed to get account dropdown temporarily rendering from app bar - selected, - unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), - } -} - -module.exports = compose( - withRouter, - connect(mapStateToProps) -)(Home) diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js new file mode 100644 index 000000000..0ab6f77f8 --- /dev/null +++ b/ui/app/components/pages/home/home.component.js @@ -0,0 +1,73 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import { Redirect } from 'react-router-dom' +import WalletView from '../../wallet-view' +import TxView from '../../tx-view' +import { + INITIALIZE_BACKUP_PHRASE_ROUTE, + RESTORE_VAULT_ROUTE, + CONFIRM_TRANSACTION_ROUTE, + NOTICE_ROUTE, +} from '../../../routes' + +export default class Home extends PureComponent { + static propTypes = { + history: PropTypes.object, + unapprovedTxs: PropTypes.object, + unapprovedMsgCount: PropTypes.number, + unapprovedPersonalMsgCount: PropTypes.number, + unapprovedTypedMessagesCount: PropTypes.number, + noActiveNotices: PropTypes.bool, + lostAccounts: PropTypes.array, + forgottenPassword: PropTypes.bool, + seedWords: PropTypes.string, + } + + componentDidMount () { + const { + history, + unapprovedTxs = {}, + unapprovedMsgCount = 0, + unapprovedPersonalMsgCount = 0, + unapprovedTypedMessagesCount = 0, + } = this.props + + // unapprovedTxs and unapproved messages + if (Object.keys(unapprovedTxs).length || + unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { + history.push(CONFIRM_TRANSACTION_ROUTE) + } + } + + render () { + const { + noActiveNotices, + lostAccounts, + forgottenPassword, + seedWords, + } = this.props + + // notices + if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) { + return + } + + // seed words + if (seedWords) { + return + } + + if (forgottenPassword) { + return + } + + return ( +
    +
    + + +
    +
    + ) + } +} diff --git a/ui/app/components/pages/home/home.container.js b/ui/app/components/pages/home/home.container.js new file mode 100644 index 000000000..96a45a69b --- /dev/null +++ b/ui/app/components/pages/home/home.container.js @@ -0,0 +1,34 @@ +import Home from './home.component' +import { compose } from 'recompose' +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' + +const mapStateToProps = state => { + const { metamask, appState } = state + const { + unapprovedTxs = {}, + unapprovedMsgCount = 0, + unapprovedPersonalMsgCount = 0, + unapprovedTypedMessagesCount = 0, + noActiveNotices, + lostAccounts, + seedWords, + } = metamask + const { forgottenPassword } = appState + + return { + unapprovedTxs, + unapprovedMsgCount, + unapprovedPersonalMsgCount, + unapprovedTypedMessagesCount, + noActiveNotices, + lostAccounts, + forgottenPassword, + seedWords, + } +} + +export default compose( + withRouter, + connect(mapStateToProps) +)(Home) diff --git a/ui/app/components/pages/home/index.js b/ui/app/components/pages/home/index.js new file mode 100644 index 000000000..4474ba5b8 --- /dev/null +++ b/ui/app/components/pages/home/index.js @@ -0,0 +1 @@ +export { default } from './home.container' diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 8e092364c..ffa60e3ed 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -26,6 +26,10 @@ WalletView.contextTypes = { t: PropTypes.func, } +WalletView.defaultProps = { + responsiveDisplayClassname: '', +} + function mapStateToProps (state) { return { @@ -131,8 +135,9 @@ WalletView.prototype.render = function () { } } - return h('div.wallet-view.flex-column' + (responsiveDisplayClassname || ''), { + return h('div.wallet-view.flex-column', { style: {}, + className: responsiveDisplayClassname, }, [ // TODO: Separate component: wallet account details diff --git a/ui/app/main-container.js b/ui/app/main-container.js deleted file mode 100644 index 8a0708025..000000000 --- a/ui/app/main-container.js +++ /dev/null @@ -1,49 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const AccountAndTransactionDetails = require('./account-and-transaction-details') -const Settings = require('./components/pages/settings') -const log = require('loglevel') - -import UnlockScreen from './components/pages/unlock-page' - -module.exports = MainContainer - -inherits(MainContainer, Component) -function MainContainer () { - Component.call(this) -} - -MainContainer.prototype.render = function () { - // 3. summarize: - // switch statement goes inside MainContainer, - // or a method in renderPrimary - // - pass resulting h() to MainContainer - // - error checking in separate func - // - router in separate func - const contents = { - component: AccountAndTransactionDetails, - key: 'account-detail', - style: {}, - } - - if (this.props.isUnlocked === false) { - switch (this.props.currentViewName) { - case 'config': - log.debug('rendering config screen from unlock screen.') - return h(Settings, {key: 'config'}) - default: - log.debug('rendering locked screen') - return h('.unlock-screen-container', {}, h(UnlockScreen, { key: 'locked' })) - } - } - - return h('div.main-container', { - style: contents.style, - }, [ - h(contents.component, { - key: contents.key, - }, []), - ]) -} - diff --git a/ui/app/new-keychain.js b/ui/app/new-keychain.js deleted file mode 100644 index cc9633166..000000000 --- a/ui/app/new-keychain.js +++ /dev/null @@ -1,29 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect - -module.exports = connect(mapStateToProps)(NewKeychain) - -function mapStateToProps (state) { - return {} -} - -inherits(NewKeychain, Component) -function NewKeychain () { - Component.call(this) -} - -NewKeychain.prototype.render = function () { - // const props = this.props - - return ( - h('div', { - style: { - background: 'blue', - }, - }, [ - h('h1', `Here's a list!!!!`), - ]) - ) -} -- cgit v1.2.3 From d1de5ae94f1662f35a7b031ac59b4bb9bd719695 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 24 Jul 2018 11:40:22 -0700 Subject: Add react-media package to prevent rendering wallet-view twice in the popup view --- ui/app/components/pages/home/home.component.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js index 0ab6f77f8..20ba44484 100644 --- a/ui/app/components/pages/home/home.component.js +++ b/ui/app/components/pages/home/home.component.js @@ -1,5 +1,6 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' +import Media from 'react-media' import { Redirect } from 'react-router-dom' import WalletView from '../../wallet-view' import TxView from '../../tx-view' @@ -64,7 +65,10 @@ export default class Home extends PureComponent { return (
    - + } + />
    -- cgit v1.2.3 From 8a7547b9cd2d9e636883af55fd6382ebcbabf4f1 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 30 Jul 2018 10:42:09 -0700 Subject: Add MenuBar component --- ui/app/components/menu-bar/index.js | 1 + ui/app/components/menu-bar/index.scss | 23 +++++++++++ ui/app/components/menu-bar/menu-bar.component.js | 52 ++++++++++++++++++++++++ ui/app/components/menu-bar/menu-bar.container.js | 21 ++++++++++ 4 files changed, 97 insertions(+) create mode 100644 ui/app/components/menu-bar/index.js create mode 100644 ui/app/components/menu-bar/index.scss create mode 100644 ui/app/components/menu-bar/menu-bar.component.js create mode 100644 ui/app/components/menu-bar/menu-bar.container.js (limited to 'ui') diff --git a/ui/app/components/menu-bar/index.js b/ui/app/components/menu-bar/index.js new file mode 100644 index 000000000..c5760847f --- /dev/null +++ b/ui/app/components/menu-bar/index.js @@ -0,0 +1 @@ +export { default } from './menu-bar.container' diff --git a/ui/app/components/menu-bar/index.scss b/ui/app/components/menu-bar/index.scss new file mode 100644 index 000000000..f699f4090 --- /dev/null +++ b/ui/app/components/menu-bar/index.scss @@ -0,0 +1,23 @@ +.menu-bar { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + flex: 0 0 auto; + margin-bottom: 16px; + padding: 5px; + border-bottom: 1px solid #e5e5e5; + + &__sidebar-button { + font-size: 1.25rem; + cursor: pointer; + padding: 10px; + } + + &__open-in-browser { + cursor: pointer; + display: flex; + justify-content: center; + padding: 10px; + } +} diff --git a/ui/app/components/menu-bar/menu-bar.component.js b/ui/app/components/menu-bar/menu-bar.component.js new file mode 100644 index 000000000..eee9feebb --- /dev/null +++ b/ui/app/components/menu-bar/menu-bar.component.js @@ -0,0 +1,52 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Tooltip from '../tooltip' +import SelectedAccount from '../selected-account' + +export default class MenuBar extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + hideSidebar: PropTypes.func, + isMascara: PropTypes.bool, + sidebarOpen: PropTypes.bool, + showSidebar: PropTypes.func, + } + + render () { + const { t } = this.context + const { isMascara, sidebarOpen, hideSidebar, showSidebar } = this.props + + return ( +
    + +
    sidebarOpen ? hideSidebar() : showSidebar()} + /> + + + { + !isMascara && ( + +
    global.platform.openExtensionInBrowser()} + > + +
    +
    + ) + } +
    + ) + } +} diff --git a/ui/app/components/menu-bar/menu-bar.container.js b/ui/app/components/menu-bar/menu-bar.container.js new file mode 100644 index 000000000..2bd0ed6ed --- /dev/null +++ b/ui/app/components/menu-bar/menu-bar.container.js @@ -0,0 +1,21 @@ +import { connect } from 'react-redux' +import MenuBar from './menu-bar.component' +import { showSidebar, hideSidebar } from '../../actions' + +const mapStateToProps = state => { + const { appState: { sidebarOpen, isMascara } } = state + + return { + sidebarOpen, + isMascara, + } +} + +const mapDispatchToProps = dispatch => { + return { + showSidebar: () => dispatch(showSidebar()), + hideSidebar: () => dispatch(hideSidebar()), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(MenuBar) -- cgit v1.2.3 From 40d4ac9ae1ed9557d066c184abd90e51a380cf06 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 30 Jul 2018 11:53:54 -0700 Subject: Add TransactionStatus component --- ui/app/components/transaction-status/index.js | 1 + ui/app/components/transaction-status/index.scss | 22 +++++++++++ .../transaction-status.component.js | 44 ++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 ui/app/components/transaction-status/index.js create mode 100644 ui/app/components/transaction-status/index.scss create mode 100644 ui/app/components/transaction-status/transaction-status.component.js (limited to 'ui') diff --git a/ui/app/components/transaction-status/index.js b/ui/app/components/transaction-status/index.js new file mode 100644 index 000000000..dece41e9c --- /dev/null +++ b/ui/app/components/transaction-status/index.js @@ -0,0 +1 @@ +export { default } from './transaction-status.component' diff --git a/ui/app/components/transaction-status/index.scss b/ui/app/components/transaction-status/index.scss new file mode 100644 index 000000000..dd9bf5877 --- /dev/null +++ b/ui/app/components/transaction-status/index.scss @@ -0,0 +1,22 @@ +.transaction-status { + height: 26px; + width: 81px; + border-radius: 4px; + background-color: #f0f0f0; + color: #5e6064; + font-size: .625rem; + text-transform: uppercase; + display: flex; + justify-content: center; + align-items: center; + + &--confirmed { + background-color: #eafad7; + color: #609a1c; + } + + &--approved { + background-color: #FFF2DB; + color: #CA810A; + } +} \ No newline at end of file diff --git a/ui/app/components/transaction-status/transaction-status.component.js b/ui/app/components/transaction-status/transaction-status.component.js new file mode 100644 index 000000000..cf688558f --- /dev/null +++ b/ui/app/components/transaction-status/transaction-status.component.js @@ -0,0 +1,44 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +const UNAPPROVED_STATUS = 'unapproved' +const REJECTED_STATUS = 'rejected' +const APPROVED_STATUS = 'approved' +const SIGNED_STATUS = 'signed' +const SUBMITTED_STATUS = 'submitted' +const CONFIRMED_STATUS = 'confirmed' +const FAILED_STATUS = 'failed' +const DROPPED_STATUS = 'dropped' + +const statusToClassNameHash = { + [UNAPPROVED_STATUS]: 'transaction-status--unapproved', + [REJECTED_STATUS]: 'transaction-status--rejected', + [APPROVED_STATUS]: 'transaction-status--approved', + [SIGNED_STATUS]: 'transaction-status--signed', + [SUBMITTED_STATUS]: 'transaction-status--submitted', + [CONFIRMED_STATUS]: 'transaction-status--confirmed', + [FAILED_STATUS]: 'transaction-status--failed', + [DROPPED_STATUS]: 'transaction-status--dropped', +} + +const statusToTextHash = { + [APPROVED_STATUS]: 'pending', + [SUBMITTED_STATUS]: 'pending', +} + +export default class TransactionStatus extends PureComponent { + static propTypes = { + status: PropTypes.string, + } + + render () { + const { status } = this.props + + return ( +
    + { statusToTextHash[status] || status } +
    + ) + } +} -- cgit v1.2.3 From 4e0693eaff0107d11bf93042db50cbb022cfeed8 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 30 Jul 2018 12:02:55 -0700 Subject: Add withMethodData HOC, add higher-order-component folder --- .../confirm-add-token.component.js | 4 +- .../pages/confirm-add-token/token-balance/index.js | 2 - .../token-balance/token-balance.component.js | 16 --- .../token-balance/token-balance.container.js | 16 --- ui/app/components/token-balance/index.js | 1 + .../token-balance/token-balance.component.js | 23 +++++ .../token-balance/token-balance.container.js | 16 +++ ui/app/helpers/with-token-tracker.js | 108 --------------------- .../with-method-data/index.js | 1 + .../with-method-data/with-method-data.component.js | 44 +++++++++ .../with-token-tracker/index.js | 1 + .../with-token-tracker.component.js | 106 ++++++++++++++++++++ 12 files changed, 194 insertions(+), 144 deletions(-) delete mode 100644 ui/app/components/pages/confirm-add-token/token-balance/index.js delete mode 100644 ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js delete mode 100644 ui/app/components/pages/confirm-add-token/token-balance/token-balance.container.js create mode 100644 ui/app/components/token-balance/index.js create mode 100644 ui/app/components/token-balance/token-balance.component.js create mode 100644 ui/app/components/token-balance/token-balance.container.js delete mode 100644 ui/app/helpers/with-token-tracker.js create mode 100644 ui/app/higher-order-components/with-method-data/index.js create mode 100644 ui/app/higher-order-components/with-method-data/with-method-data.component.js create mode 100644 ui/app/higher-order-components/with-token-tracker/index.js create mode 100644 ui/app/higher-order-components/with-token-tracker/with-token-tracker.component.js (limited to 'ui') diff --git a/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js b/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js index 65d654b92..3dcc8cda9 100644 --- a/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js +++ b/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js @@ -2,8 +2,8 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { DEFAULT_ROUTE, ADD_TOKEN_ROUTE } from '../../../routes' import Button from '../../button' -import Identicon from '../../../components/identicon' -import TokenBalance from './token-balance' +import Identicon from '../../identicon' +import TokenBalance from '../../token-balance' export default class ConfirmAddToken extends Component { static contextTypes = { diff --git a/ui/app/components/pages/confirm-add-token/token-balance/index.js b/ui/app/components/pages/confirm-add-token/token-balance/index.js deleted file mode 100644 index 6fb5c8223..000000000 --- a/ui/app/components/pages/confirm-add-token/token-balance/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import TokenBalance from './token-balance.container' -module.exports = TokenBalance diff --git a/ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js b/ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js deleted file mode 100644 index 976788d4c..000000000 --- a/ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js +++ /dev/null @@ -1,16 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' - -export default class TokenBalance extends Component { - static propTypes = { - string: PropTypes.string, - symbol: PropTypes.string, - error: PropTypes.string, - } - - render () { - return ( -
    { this.props.string }
    - ) - } -} diff --git a/ui/app/components/pages/confirm-add-token/token-balance/token-balance.container.js b/ui/app/components/pages/confirm-add-token/token-balance/token-balance.container.js deleted file mode 100644 index bc1289ce1..000000000 --- a/ui/app/components/pages/confirm-add-token/token-balance/token-balance.container.js +++ /dev/null @@ -1,16 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' -import withTokenTracker from '../../../../helpers/with-token-tracker' -import TokenBalance from './token-balance.component' -import selectors from '../../../../selectors' - -const mapStateToProps = state => { - return { - userAddress: selectors.getSelectedAddress(state), - } -} - -export default compose( - connect(mapStateToProps), - withTokenTracker -)(TokenBalance) diff --git a/ui/app/components/token-balance/index.js b/ui/app/components/token-balance/index.js new file mode 100644 index 000000000..f7da15cf8 --- /dev/null +++ b/ui/app/components/token-balance/index.js @@ -0,0 +1 @@ +export { default } from './token-balance.container' diff --git a/ui/app/components/token-balance/token-balance.component.js b/ui/app/components/token-balance/token-balance.component.js new file mode 100644 index 000000000..2b4f73980 --- /dev/null +++ b/ui/app/components/token-balance/token-balance.component.js @@ -0,0 +1,23 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +export default class TokenBalance extends PureComponent { + static propTypes = { + string: PropTypes.string, + symbol: PropTypes.string, + error: PropTypes.string, + className: PropTypes.string, + withSymbol: PropTypes.bool, + } + + render () { + const { className, string, withSymbol, symbol } = this.props + + return ( +
    + { string + (withSymbol ? ` ${symbol}` : '') } +
    + ) + } +} diff --git a/ui/app/components/token-balance/token-balance.container.js b/ui/app/components/token-balance/token-balance.container.js new file mode 100644 index 000000000..adc001f83 --- /dev/null +++ b/ui/app/components/token-balance/token-balance.container.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import withTokenTracker from '../../higher-order-components/with-token-tracker' +import TokenBalance from './token-balance.component' +import selectors from '../../selectors' + +const mapStateToProps = state => { + return { + userAddress: selectors.getSelectedAddress(state), + } +} + +export default compose( + connect(mapStateToProps), + withTokenTracker +)(TokenBalance) diff --git a/ui/app/helpers/with-token-tracker.js b/ui/app/helpers/with-token-tracker.js deleted file mode 100644 index 8608b15f4..000000000 --- a/ui/app/helpers/with-token-tracker.js +++ /dev/null @@ -1,108 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import TokenTracker from 'eth-token-tracker' - -const withTokenTracker = WrappedComponent => { - return class TokenTrackerWrappedComponent extends Component { - static propTypes = { - userAddress: PropTypes.string.isRequired, - token: PropTypes.object.isRequired, - } - - constructor (props) { - super(props) - - this.state = { - string: '', - symbol: '', - error: null, - } - - this.tracker = null - this.updateBalance = this.updateBalance.bind(this) - this.setError = this.setError.bind(this) - } - - componentDidMount () { - this.createFreshTokenTracker() - } - - componentDidUpdate (prevProps) { - const { userAddress: newAddress, token: { address: newTokenAddress } } = this.props - const { userAddress: oldAddress, token: { address: oldTokenAddress } } = prevProps - - if ((oldAddress === newAddress) && (oldTokenAddress === newTokenAddress)) { - return - } - - if ((!oldAddress || !newAddress) && (!oldTokenAddress || !newTokenAddress)) { - return - } - - this.createFreshTokenTracker() - } - - componentWillUnmount () { - this.removeListeners() - } - - createFreshTokenTracker () { - this.removeListeners() - - if (!global.ethereumProvider) { - return - } - - const { userAddress, token } = this.props - - this.tracker = new TokenTracker({ - userAddress, - provider: global.ethereumProvider, - tokens: [token], - pollingInterval: 8000, - }) - - this.tracker.on('update', this.updateBalance) - this.tracker.on('error', this.setError) - - this.tracker.updateBalances() - .then(() => this.updateBalance(this.tracker.serialize())) - .catch(error => this.setState({ error: error.message })) - } - - setError (error) { - this.setState({ error }) - } - - updateBalance (tokens = []) { - if (!this.tracker.running) { - return - } - const [{ string, symbol }] = tokens - this.setState({ string, symbol, error: null }) - } - - removeListeners () { - if (this.tracker) { - this.tracker.stop() - this.tracker.removeListener('update', this.updateBalance) - this.tracker.removeListener('error', this.setError) - } - } - - render () { - const { string, symbol, error } = this.state - - return ( - - ) - } - } -} - -module.exports = withTokenTracker diff --git a/ui/app/higher-order-components/with-method-data/index.js b/ui/app/higher-order-components/with-method-data/index.js new file mode 100644 index 000000000..f511e1ae7 --- /dev/null +++ b/ui/app/higher-order-components/with-method-data/index.js @@ -0,0 +1 @@ +export { default } from './with-method-data.component' diff --git a/ui/app/higher-order-components/with-method-data/with-method-data.component.js b/ui/app/higher-order-components/with-method-data/with-method-data.component.js new file mode 100644 index 000000000..aa38afd8a --- /dev/null +++ b/ui/app/higher-order-components/with-method-data/with-method-data.component.js @@ -0,0 +1,44 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import { getMethodData } from '../../helpers/confirm-transaction/util' + +export default function withMethodData (WrappedComponent) { + return class MethodDataWrappedComponent extends PureComponent { + static propTypes = { + transaction: PropTypes.object, + } + + static defaultProps = { + transaction: {}, + } + + state = { + methodData: {}, + } + + componentDidMount () { + this.fetchMethodData() + } + + async fetchMethodData () { + const { transaction } = this.props + const { txParams: { data = '' } = {} } = transaction + + if (data) { + const methodData = await getMethodData(data) + this.setState({ methodData }) + } + } + + render () { + const { methodData } = this.state + + return ( + + ) + } + } +} diff --git a/ui/app/higher-order-components/with-token-tracker/index.js b/ui/app/higher-order-components/with-token-tracker/index.js new file mode 100644 index 000000000..d401e81f1 --- /dev/null +++ b/ui/app/higher-order-components/with-token-tracker/index.js @@ -0,0 +1 @@ +export { default } from './with-token-tracker.component' diff --git a/ui/app/higher-order-components/with-token-tracker/with-token-tracker.component.js b/ui/app/higher-order-components/with-token-tracker/with-token-tracker.component.js new file mode 100644 index 000000000..36f6a6efd --- /dev/null +++ b/ui/app/higher-order-components/with-token-tracker/with-token-tracker.component.js @@ -0,0 +1,106 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import TokenTracker from 'eth-token-tracker' + +export default function withTokenTracker (WrappedComponent) { + return class TokenTrackerWrappedComponent extends Component { + static propTypes = { + userAddress: PropTypes.string.isRequired, + token: PropTypes.object.isRequired, + } + + constructor (props) { + super(props) + + this.state = { + string: '', + symbol: '', + error: null, + } + + this.tracker = null + this.updateBalance = this.updateBalance.bind(this) + this.setError = this.setError.bind(this) + } + + componentDidMount () { + this.createFreshTokenTracker() + } + + componentDidUpdate (prevProps) { + const { userAddress: newAddress, token: { address: newTokenAddress } } = this.props + const { userAddress: oldAddress, token: { address: oldTokenAddress } } = prevProps + + if ((oldAddress === newAddress) && (oldTokenAddress === newTokenAddress)) { + return + } + + if ((!oldAddress || !newAddress) && (!oldTokenAddress || !newTokenAddress)) { + return + } + + this.createFreshTokenTracker() + } + + componentWillUnmount () { + this.removeListeners() + } + + createFreshTokenTracker () { + this.removeListeners() + + if (!global.ethereumProvider) { + return + } + + const { userAddress, token } = this.props + + this.tracker = new TokenTracker({ + userAddress, + provider: global.ethereumProvider, + tokens: [token], + pollingInterval: 8000, + }) + + this.tracker.on('update', this.updateBalance) + this.tracker.on('error', this.setError) + + this.tracker.updateBalances() + .then(() => this.updateBalance(this.tracker.serialize())) + .catch(error => this.setState({ error: error.message })) + } + + setError (error) { + this.setState({ error }) + } + + updateBalance (tokens = []) { + if (!this.tracker.running) { + return + } + const [{ string, symbol }] = tokens + this.setState({ string, symbol, error: null }) + } + + removeListeners () { + if (this.tracker) { + this.tracker.stop() + this.tracker.removeListener('update', this.updateBalance) + this.tracker.removeListener('error', this.setError) + } + } + + render () { + const { string, symbol, error } = this.state + + return ( + + ) + } + } +} -- cgit v1.2.3 From d733bd34fbd356bca640b3a50582208c0284be40 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 30 Jul 2018 12:03:19 -0700 Subject: Delete unused files --- ui/app/components/custom-radio-list.js | 60 -------------------------- ui/app/components/pending-msg-details.js | 56 ------------------------ ui/app/components/pending-msg.js | 73 -------------------------------- 3 files changed, 189 deletions(-) delete mode 100644 ui/app/components/custom-radio-list.js delete mode 100644 ui/app/components/pending-msg-details.js delete mode 100644 ui/app/components/pending-msg.js (limited to 'ui') diff --git a/ui/app/components/custom-radio-list.js b/ui/app/components/custom-radio-list.js deleted file mode 100644 index a4c525396..000000000 --- a/ui/app/components/custom-radio-list.js +++ /dev/null @@ -1,60 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits - -module.exports = RadioList - -inherits(RadioList, Component) -function RadioList () { - Component.call(this) -} - -RadioList.prototype.render = function () { - const props = this.props - const activeClass = '.custom-radio-selected' - const inactiveClass = '.custom-radio-inactive' - const { - labels, - defaultFocus, - } = props - - - return ( - h('.flex-row', { - style: { - fontSize: '12px', - }, - }, [ - h('.flex-column.custom-radios', { - style: { - marginRight: '5px', - }, - }, - labels.map((lable, i) => { - let isSelcted = (this.state !== null) - isSelcted = isSelcted ? (this.state.selected === lable) : (defaultFocus === lable) - return h(isSelcted ? activeClass : inactiveClass, { - title: lable, - onClick: (event) => { - this.setState({selected: event.target.title}) - props.onClick(event) - }, - }) - }) - ), - h('.text', {}, - labels.map((lable) => { - if (props.subtext) { - return h('.flex-row', {}, [ - h('.radio-titles', lable), - h('.radio-titles-subtext', `- ${props.subtext[lable]}`), - ]) - } else { - return h('.radio-titles', lable) - } - }) - ), - ]) - ) -} - diff --git a/ui/app/components/pending-msg-details.js b/ui/app/components/pending-msg-details.js deleted file mode 100644 index f16fcb1c7..000000000 --- a/ui/app/components/pending-msg-details.js +++ /dev/null @@ -1,56 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const inherits = require('util').inherits -const connect = require('react-redux').connect - -const AccountPanel = require('./account-panel') - -PendingMsgDetails.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect()(PendingMsgDetails) - - -inherits(PendingMsgDetails, Component) -function PendingMsgDetails () { - Component.call(this) -} - -PendingMsgDetails.prototype.render = function () { - var state = this.props - var msgData = state.txData - - var msgParams = msgData.msgParams || {} - var address = msgParams.from || state.selectedAddress - var identity = state.identities[address] || { address: address } - var account = state.accounts[address] || { address: address } - - return ( - h('div', { - key: msgData.id, - style: { - margin: '10px 20px', - }, - }, [ - - // account that will sign - h(AccountPanel, { - showFullAddress: true, - identity: identity, - account: account, - imageifyIdenticons: state.imageifyIdenticons, - }), - - // message data - h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [ - h('.flex-column.flex-space-between', [ - h('label.font-small.allcaps', this.context.t('message')), - h('span.font-small', msgParams.data), - ]), - ]), - - ]) - ) -} diff --git a/ui/app/components/pending-msg.js b/ui/app/components/pending-msg.js deleted file mode 100644 index 21a7864e4..000000000 --- a/ui/app/components/pending-msg.js +++ /dev/null @@ -1,73 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const inherits = require('util').inherits -const PendingTxDetails = require('./pending-msg-details') -const connect = require('react-redux').connect - -PendingMsg.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect()(PendingMsg) - - -inherits(PendingMsg, Component) -function PendingMsg () { - Component.call(this) -} - -PendingMsg.prototype.render = function () { - var state = this.props - var msgData = state.txData - - return ( - - h('div', { - key: msgData.id, - style: { - maxWidth: '350px', - }, - }, [ - - // header - h('h3', { - style: { - fontWeight: 'bold', - textAlign: 'center', - }, - }, this.context.t('signMessage')), - - h('.error', { - style: { - margin: '10px', - }, - }, [ - this.context.t('signNotice'), - h('a', { - href: 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527', - style: { color: 'rgb(247, 134, 28)' }, - onClick: (event) => { - event.preventDefault() - const url = 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527' - global.platform.openWindow({ url }) - }, - }, this.context.t('readMore')), - ]), - - // message details - h(PendingTxDetails, state), - - // sign + cancel - h('.flex-row.flex-space-around', [ - h('button', { - onClick: state.cancelMessage, - }, this.context.t('cancel')), - h('button', { - onClick: state.signMessage, - }, this.context.t('sign')), - ]), - ]) - - ) -} -- cgit v1.2.3 From 5ee40675b9f986a9ff2e5d15a271d7de2145d0e9 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 30 Jul 2018 22:03:20 -0700 Subject: Refactor transactions list views. Add redesign components --- ui/app/components/index.scss | 24 +++- .../confirm-transaction-switch.component.js | 4 +- .../confirm-transaction-switch.constants.js | 3 - ui/app/components/pages/home/home.component.js | 21 +--- ui/app/components/pages/home/home.container.js | 10 +- ui/app/components/token-balance.js | 120 ------------------- ui/app/components/token-view-balance/index.js | 1 + ui/app/components/token-view-balance/index.scss | 66 +++++++++++ .../token-view-balance.component.js | 92 +++++++++++++++ .../token-view-balance.container.js | 42 +++++++ ui/app/components/token-view/index.js | 1 + ui/app/components/token-view/index.scss | 27 +++++ .../components/token-view/token-view.component.js | 28 +++++ ui/app/components/transaction-action/index.js | 1 + .../transaction-action.component.js | 52 +++++++++ .../transaction-action.container.js | 4 + ui/app/components/transaction-list-item/index.js | 1 + ui/app/components/transaction-list-item/index.scss | 71 +++++++++++ .../transaction-list-item.component.js | 82 +++++++++++++ .../transaction-list-item.container.js | 28 +++++ ui/app/components/transaction-list/index.js | 1 + ui/app/components/transaction-list/index.scss | 40 +++++++ .../transaction-list/transaction-list.component.js | 90 ++++++++++++++ .../transaction-list/transaction-list.container.js | 20 ++++ ui/app/components/transaction-status/index.scss | 6 + ui/app/components/tx-view.js | 51 ++------ ui/app/constants/transactions.js | 18 +++ ui/app/css/itcss/components/hero-balance.scss | 130 --------------------- ui/app/css/itcss/components/index.scss | 2 - ui/app/css/itcss/components/newui-sections.scss | 7 -- ui/app/ducks/confirm-transaction.duck.js | 8 +- ui/app/helpers/confirm-transaction/util.js | 17 +-- ui/app/helpers/confirm-transaction/util.test.js | 6 +- ui/app/helpers/conversions.util.js | 37 ++++++ ui/app/helpers/transactions.util.js | 57 +++++++++ .../with-method-data/with-method-data.component.js | 22 +++- ui/app/i18n-provider.js | 4 + ui/app/selectors.js | 62 +++++++--- ui/app/selectors/transactions.js | 50 ++++++++ 39 files changed, 926 insertions(+), 380 deletions(-) delete mode 100644 ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js delete mode 100644 ui/app/components/token-balance.js create mode 100644 ui/app/components/token-view-balance/index.js create mode 100644 ui/app/components/token-view-balance/index.scss create mode 100644 ui/app/components/token-view-balance/token-view-balance.component.js create mode 100644 ui/app/components/token-view-balance/token-view-balance.container.js create mode 100644 ui/app/components/token-view/index.js create mode 100644 ui/app/components/token-view/index.scss create mode 100644 ui/app/components/token-view/token-view.component.js create mode 100644 ui/app/components/transaction-action/index.js create mode 100644 ui/app/components/transaction-action/transaction-action.component.js create mode 100644 ui/app/components/transaction-action/transaction-action.container.js create mode 100644 ui/app/components/transaction-list-item/index.js create mode 100644 ui/app/components/transaction-list-item/index.scss create mode 100644 ui/app/components/transaction-list-item/transaction-list-item.component.js create mode 100644 ui/app/components/transaction-list-item/transaction-list-item.container.js create mode 100644 ui/app/components/transaction-list/index.js create mode 100644 ui/app/components/transaction-list/index.scss create mode 100644 ui/app/components/transaction-list/transaction-list.component.js create mode 100644 ui/app/components/transaction-list/transaction-list.container.js create mode 100644 ui/app/constants/transactions.js delete mode 100644 ui/app/css/itcss/components/hero-balance.scss create mode 100644 ui/app/helpers/conversions.util.js create mode 100644 ui/app/helpers/transactions.util.js create mode 100644 ui/app/selectors/transactions.js (limited to 'ui') diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index 35d38e2a3..261d917f6 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -1,23 +1,35 @@ +@import './app-header/index'; + @import './button-group/index'; -@import './export-text-container/index'; +@import './confirm-page-container/index'; -@import './selected-account/index'; +@import './export-text-container/index'; @import './info-box/index'; -@import './network-display/index'; +@import './menu-bar/index'; -@import './confirm-page-container/index'; +@import './modals/index'; + +@import './network-display/index'; @import './page-container/index'; @import './pages/index'; -@import './modals/index'; +@import './selected-account/index'; @import './sender-to-recipient/index'; @import './tabs/index'; -@import './app-header/index'; +@import './token-view/index'; + +@import './token-view-balance/index'; + +@import './transaction-list/index'; + +@import './transaction-list-item/index'; + +@import './transaction-status/index'; diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js index d494977cd..2c44b6094 100644 --- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js +++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js @@ -12,12 +12,12 @@ import { CONFIRM_TOKEN_METHOD_PATH, SIGNATURE_REQUEST_PATH, } from '../../../routes' -import { isConfirmDeployContract } from './confirm-transaction-switch.util' +import { isConfirmDeployContract } from '../../../helpers/transactions.util' import { TOKEN_METHOD_TRANSFER, TOKEN_METHOD_APPROVE, TOKEN_METHOD_TRANSFER_FROM, -} from './confirm-transaction-switch.constants' +} from '../../../constants/transactions' export default class ConfirmTransactionSwitch extends Component { static propTypes = { diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js deleted file mode 100644 index 9db4a2f96..000000000 --- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js +++ /dev/null @@ -1,3 +0,0 @@ -export const TOKEN_METHOD_TRANSFER = 'transfer' -export const TOKEN_METHOD_APPROVE = 'approve' -export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom' diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js index 20ba44484..dae9790de 100644 --- a/ui/app/components/pages/home/home.component.js +++ b/ui/app/components/pages/home/home.component.js @@ -4,6 +4,7 @@ import Media from 'react-media' import { Redirect } from 'react-router-dom' import WalletView from '../../wallet-view' import TxView from '../../tx-view' +import TokenView from '../../token-view' import { INITIALIZE_BACKUP_PHRASE_ROUTE, RESTORE_VAULT_ROUTE, @@ -14,28 +15,17 @@ import { export default class Home extends PureComponent { static propTypes = { history: PropTypes.object, - unapprovedTxs: PropTypes.object, - unapprovedMsgCount: PropTypes.number, - unapprovedPersonalMsgCount: PropTypes.number, - unapprovedTypedMessagesCount: PropTypes.number, noActiveNotices: PropTypes.bool, lostAccounts: PropTypes.array, forgottenPassword: PropTypes.bool, seedWords: PropTypes.string, + unconfirmedTransactionsCount: PropTypes.number, } componentDidMount () { - const { - history, - unapprovedTxs = {}, - unapprovedMsgCount = 0, - unapprovedPersonalMsgCount = 0, - unapprovedTypedMessagesCount = 0, - } = this.props + const { history, unconfirmedTransactionsCount = 0 } = this.props - // unapprovedTxs and unapproved messages - if (Object.keys(unapprovedTxs).length || - unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { + if (unconfirmedTransactionsCount > 0) { history.push(CONFIRM_TRANSACTION_ROUTE) } } @@ -69,7 +59,8 @@ export default class Home extends PureComponent { query="(min-width: 576px)" render={() => } /> - + + {/* */}
  • ) diff --git a/ui/app/components/pages/home/home.container.js b/ui/app/components/pages/home/home.container.js index 96a45a69b..b0e34f832 100644 --- a/ui/app/components/pages/home/home.container.js +++ b/ui/app/components/pages/home/home.container.js @@ -2,14 +2,11 @@ import Home from './home.component' import { compose } from 'recompose' import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' +import { unconfirmedTransactionsCountSelector } from '../../../selectors/confirm-transaction' const mapStateToProps = state => { const { metamask, appState } = state const { - unapprovedTxs = {}, - unapprovedMsgCount = 0, - unapprovedPersonalMsgCount = 0, - unapprovedTypedMessagesCount = 0, noActiveNotices, lostAccounts, seedWords, @@ -17,14 +14,11 @@ const mapStateToProps = state => { const { forgottenPassword } = appState return { - unapprovedTxs, - unapprovedMsgCount, - unapprovedPersonalMsgCount, - unapprovedTypedMessagesCount, noActiveNotices, lostAccounts, forgottenPassword, seedWords, + unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), } } diff --git a/ui/app/components/token-balance.js b/ui/app/components/token-balance.js deleted file mode 100644 index 99ca7335c..000000000 --- a/ui/app/components/token-balance.js +++ /dev/null @@ -1,120 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const TokenTracker = require('eth-token-tracker') -const connect = require('react-redux').connect -const selectors = require('../selectors') -const log = require('loglevel') - -function mapStateToProps (state) { - return { - userAddress: selectors.getSelectedAddress(state), - } -} - -module.exports = connect(mapStateToProps)(TokenBalance) - - -inherits(TokenBalance, Component) -function TokenBalance () { - this.state = { - string: '', - symbol: '', - isLoading: true, - error: null, - } - Component.call(this) -} - -TokenBalance.prototype.render = function () { - const state = this.state - const { symbol, string, isLoading } = state - const { balanceOnly } = this.props - - return isLoading - ? h('span', '') - : h('span.token-balance', [ - h('span.hide-text-overflow.token-balance__amount', string), - !balanceOnly && h('span.token-balance__symbol', symbol), - ]) -} - -TokenBalance.prototype.componentDidMount = function () { - this.createFreshTokenTracker() -} - -TokenBalance.prototype.createFreshTokenTracker = function () { - if (this.tracker) { - // Clean up old trackers when refreshing: - this.tracker.stop() - this.tracker.removeListener('update', this.balanceUpdater) - this.tracker.removeListener('error', this.showError) - } - - if (!global.ethereumProvider) return - const { userAddress, token } = this.props - - this.tracker = new TokenTracker({ - userAddress, - provider: global.ethereumProvider, - tokens: [token], - pollingInterval: 8000, - }) - - - // Set up listener instances for cleaning up - this.balanceUpdater = this.updateBalance.bind(this) - this.showError = error => { - this.setState({ error, isLoading: false }) - } - this.tracker.on('update', this.balanceUpdater) - this.tracker.on('error', this.showError) - - this.tracker.updateBalances() - .then(() => { - this.updateBalance(this.tracker.serialize()) - }) - .catch((reason) => { - log.error(`Problem updating balances`, reason) - this.setState({ isLoading: false }) - }) -} - -TokenBalance.prototype.componentDidUpdate = function (nextProps) { - const { - userAddress: oldAddress, - token: { address: oldTokenAddress }, - } = this.props - const { - userAddress: newAddress, - token: { address: newTokenAddress }, - } = nextProps - - if ((!oldAddress || !newAddress) && (!oldTokenAddress || !newTokenAddress)) return - if ((oldAddress === newAddress) && (oldTokenAddress === newTokenAddress)) return - - this.setState({ isLoading: true }) - this.createFreshTokenTracker() -} - -TokenBalance.prototype.updateBalance = function (tokens = []) { - if (!this.tracker.running) { - return - } - - const [{ string, symbol }] = tokens - - this.setState({ - string, - symbol, - isLoading: false, - }) -} - -TokenBalance.prototype.componentWillUnmount = function () { - if (!this.tracker) return - this.tracker.stop() - this.tracker.removeListener('update', this.balanceUpdater) - this.tracker.removeListener('error', this.showError) -} - diff --git a/ui/app/components/token-view-balance/index.js b/ui/app/components/token-view-balance/index.js new file mode 100644 index 000000000..e0509096a --- /dev/null +++ b/ui/app/components/token-view-balance/index.js @@ -0,0 +1 @@ +export { default } from './token-view-balance.container' diff --git a/ui/app/components/token-view-balance/index.scss b/ui/app/components/token-view-balance/index.scss new file mode 100644 index 000000000..6a89e125b --- /dev/null +++ b/ui/app/components/token-view-balance/index.scss @@ -0,0 +1,66 @@ +.token-view-balance { + display: flex; + justify-content: space-between; + align-items: center; + flex: 1; + height: 54px; + + &__balance { + margin-left: 12px; + display: flex; + flex-direction: column; + + @media screen and (max-width: $break-small) { + align-items: center; + margin: 16px 0; + } + } + + &__primary-balance { + font-size: 1.5rem; + + @media screen and (max-width: $break-small) { + margin-bottom: 12px; + font-size: 1.75rem; + } + } + + &__secondary-balance { + font-size: 1.15rem; + color: #a0a0a0; + } + + &__balance-container { + flex: 1; + display: flex; + flex-direction: row; + align-items: center; + + @media screen and (max-width: $break-small) { + flex-direction: column; + } + } + + &__buttons { + display: flex; + flex-direction: row; + + @media screen and (max-width: $break-small) { + margin-bottom: 16px; + } + } + + &__button { + min-width: initial; + width: 100px; + + &:not(:last-child) { + margin-right: 12px; + } + } + + @media screen and (max-width: $break-small) { + flex-direction: column; + height: initial + } +} diff --git a/ui/app/components/token-view-balance/token-view-balance.component.js b/ui/app/components/token-view-balance/token-view-balance.component.js new file mode 100644 index 000000000..6b8140a22 --- /dev/null +++ b/ui/app/components/token-view-balance/token-view-balance.component.js @@ -0,0 +1,92 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Button from '../button' +import Identicon from '../identicon' +import TokenBalance from '../token-balance' +import { SEND_ROUTE } from '../../routes' +import { formatCurrency } from '../../helpers/confirm-transaction/util' + +export default class TokenViewBalance extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + showDepositModal: PropTypes.func, + selectedToken: PropTypes.object, + history: PropTypes.object, + network: PropTypes.string, + ethBalance: PropTypes.string, + fiatBalance: PropTypes.string, + currentCurrency: PropTypes.string, + } + + renderBalance () { + const { selectedToken, ethBalance, fiatBalance, currentCurrency } = this.props + const formattedFiatBalance = formatCurrency(fiatBalance, currentCurrency) + + return selectedToken + ? ( + + ) : ( +
    +
    + { `${ethBalance} ETH` } +
    +
    + { formattedFiatBalance } +
    +
    + ) + } + + renderButtons () { + const { t } = this.context + const { selectedToken, showDepositModal, history } = this.props + + return ( +
    + { + !selectedToken && ( + + ) + } + +
    + ) + } + + render () { + const { network, selectedToken } = this.props + + return ( +
    +
    + + { this.renderBalance() } +
    + { this.renderButtons() } +
    + ) + } +} diff --git a/ui/app/components/token-view-balance/token-view-balance.container.js b/ui/app/components/token-view-balance/token-view-balance.container.js new file mode 100644 index 000000000..692e6e32f --- /dev/null +++ b/ui/app/components/token-view-balance/token-view-balance.container.js @@ -0,0 +1,42 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import TokenViewBalance from './token-view-balance.component' +import { getSelectedToken, getSelectedAddress } from '../../selectors' +import { showModal } from '../../actions' +import { getValueFromWeiHex } from '../../helpers/confirm-transaction/util' + +const mapStateToProps = state => { + const selectedAddress = getSelectedAddress(state) + const { metamask } = state + const { network, accounts, currentCurrency, conversionRate } = metamask + const account = accounts[selectedAddress] + const { balance: value } = account + + const ethBalance = getValueFromWeiHex({ + value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 3, + }) + + const fiatBalance = getValueFromWeiHex({ + value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2, + }) + + return { + selectedToken: getSelectedToken(state), + network, + ethBalance, + fiatBalance, + currentCurrency, + } +} + +const mapDispatchToProps = dispatch => { + return { + showDepositModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER' })), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(TokenViewBalance) diff --git a/ui/app/components/token-view/index.js b/ui/app/components/token-view/index.js new file mode 100644 index 000000000..f49cb034f --- /dev/null +++ b/ui/app/components/token-view/index.js @@ -0,0 +1 @@ +export { default } from './token-view.component' diff --git a/ui/app/components/token-view/index.scss b/ui/app/components/token-view/index.scss new file mode 100644 index 000000000..438147ad9 --- /dev/null +++ b/ui/app/components/token-view/index.scss @@ -0,0 +1,27 @@ +.token-view { + flex: 1 1 66.5%; + background: $white; + min-width: 0; + display: flex; + flex-direction: column; + + &__balance-wrapper { + @media screen and (max-width: $break-small) { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + flex: 0 0 auto; + padding-top: 16px; + } + + @media screen and (min-width: $break-large) { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + margin: 2.3em 2.37em .8em; + flex: 0 0 auto; + } + } +} diff --git a/ui/app/components/token-view/token-view.component.js b/ui/app/components/token-view/token-view.component.js new file mode 100644 index 000000000..3e1a4a0c3 --- /dev/null +++ b/ui/app/components/token-view/token-view.component.js @@ -0,0 +1,28 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Media from 'react-media' +import MenuBar from '../menu-bar' +import TokenViewBalance from '../token-view-balance' +// import TransactionList from '../tx-list' +import TransactionList from '../transaction-list' + +export default class TokenView extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + render () { + return ( +
    + } + /> +
    + +
    + +
    + ) + } +} diff --git a/ui/app/components/transaction-action/index.js b/ui/app/components/transaction-action/index.js new file mode 100644 index 000000000..5882443b6 --- /dev/null +++ b/ui/app/components/transaction-action/index.js @@ -0,0 +1 @@ +export { default } from './transaction-action.container' diff --git a/ui/app/components/transaction-action/transaction-action.component.js b/ui/app/components/transaction-action/transaction-action.component.js new file mode 100644 index 000000000..b608615d0 --- /dev/null +++ b/ui/app/components/transaction-action/transaction-action.component.js @@ -0,0 +1,52 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import { getTransactionActionKey } from '../../helpers/transactions.util' + +export default class TransactionAction extends PureComponent { + static contextTypes = { + tOrDefault: PropTypes.func, + } + + static propTypes = { + className: PropTypes.string, + transaction: PropTypes.object, + methodData: PropTypes.object, + } + + state = { + transactionAction: '', + } + + componentDidMount () { + this.getTransactionAction() + } + + componentDidUpdate () { + this.getTransactionAction() + } + + getTransactionAction () { + const { transactionAction } = this.state + const { transaction, methodData } = this.props + const { data, isFetching } = methodData + + if (isFetching || transactionAction) { + return + } + + const actionKey = getTransactionActionKey(transaction, data) + const action = actionKey && this.context.tOrDefault(actionKey) + this.setState({ transactionAction: action }) + } + + render () { + const { className } = this.props + const { transactionAction } = this.state + + return ( +
    + { transactionAction || '--' } +
    + ) + } +} diff --git a/ui/app/components/transaction-action/transaction-action.container.js b/ui/app/components/transaction-action/transaction-action.container.js new file mode 100644 index 000000000..56efbdc26 --- /dev/null +++ b/ui/app/components/transaction-action/transaction-action.container.js @@ -0,0 +1,4 @@ +import withMethodData from '../../higher-order-components/with-method-data' +import TransactionAction from './transaction-action.component' + +export default withMethodData(TransactionAction) diff --git a/ui/app/components/transaction-list-item/index.js b/ui/app/components/transaction-list-item/index.js new file mode 100644 index 000000000..697cc55e9 --- /dev/null +++ b/ui/app/components/transaction-list-item/index.js @@ -0,0 +1 @@ +export { default } from './transaction-list-item.container' diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/transaction-list-item/index.scss new file mode 100644 index 000000000..8a3973f92 --- /dev/null +++ b/ui/app/components/transaction-list-item/index.scss @@ -0,0 +1,71 @@ +.transaction-list-item { + box-sizing: border-box; + height: 74px; + padding: 0 21px; + display: flex; + flex-direction: row; + align-items: center; + border-bottom: 1px solid $geyser; + cursor: pointer; + + @media screen and (max-width: $break-small) { + padding: 0 12px; + } + + &__identicon-wrapper { + padding-top: 2px; + } + + &__action-block { + padding: 0 8px 0 12px; + width: 180px; + + @media screen and (max-width: $break-small) { + padding: 0 8px; + width: 160px; + } + } + + &__action { + text-transform: capitalize; + padding-bottom: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + @media screen and (max-width: $break-small) { + padding-bottom: 0; + font-size: .875rem; + } + } + + &__nonce { + font-size: .75rem; + color: #5e6064; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &__transaction-amounts { + flex: 1; + } + + &__primary-transaction-amount { + text-align: end; + + @media screen and (max-width: $break-small) { + font-size: .75rem; + } + } + + &__secondary-transaction-amount { + text-align: end; + font-size: .75rem; + color: #5e6064; + } + + &:hover { + background: rgba($alto, .2); + } +} diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js new file mode 100644 index 000000000..e334cd938 --- /dev/null +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -0,0 +1,82 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Media from 'react-media' +import Identicon from '../identicon' +import TransactionStatus from '../transaction-status' +import TransactionAction from '../transaction-action' +import { formatDate } from '../../util' +import prefixForNetwork from '../../../lib/etherscan-prefix-for-network' +import { CONFIRM_TRANSACTION_ROUTE } from '../../routes' +import { UNAPPROVED_STATUS } from '../../constants/transactions' +import { hexToDecimal } from '../../helpers/conversions.util' + +export default class TransactionListItem extends PureComponent { + static propTypes = { + history: PropTypes.object, + methodData: PropTypes.object, + transaction: PropTypes.object, + ethTransactionAmount: PropTypes.string, + fiatDisplayValue: PropTypes.string, + } + + handleClick = () => { + const { transaction, history } = this.props + const { id, status, hash, metamaskNetworkId } = transaction + + if (status === UNAPPROVED_STATUS) { + history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`) + } else if (hash) { + const prefix = prefixForNetwork(metamaskNetworkId) + const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}` + global.platform.openWindow({ url: etherscanUrl }) + } + } + + render () { + const { + transaction, + ethTransactionAmount, + fiatDisplayValue, + } = this.props + const { txParams = {} } = transaction + const nonce = hexToDecimal(txParams.nonce) + + return ( +
    +
    + + { + matches => ( + + ) + } + +
    +
    + +
    + { `#${nonce} - ${formatDate(transaction.time)}` } +
    +
    + +
    +
    + { `-${fiatDisplayValue}` } +
    +
    + { `-${ethTransactionAmount} ETH` } +
    +
    +
    + ) + } +} diff --git a/ui/app/components/transaction-list-item/transaction-list-item.container.js b/ui/app/components/transaction-list-item/transaction-list-item.container.js new file mode 100644 index 000000000..bc47f20aa --- /dev/null +++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js @@ -0,0 +1,28 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import TransactionListItem from './transaction-list-item.component' +import { getEthFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util' +import { formatCurrency } from '../../helpers/confirm-transaction/util' + +const mapStateToProps = (state, ownProps) => { + const { metamask } = state + const { currentCurrency, conversionRate } = metamask + const { transaction: { txParams: { value } = {} } = {} } = ownProps + const ethTransactionAmount = getEthFromWeiHex({ value, conversionRate }) + const fiatTransactionAmount = getValueFromWeiHex({ + value, conversionRate, toCurrency: currentCurrency, numberOfDecimals: 2, + }) + const fiatFormattedAmount = formatCurrency(fiatTransactionAmount, currentCurrency) + const fiatDisplayValue = `${fiatFormattedAmount} ${currentCurrency.toUpperCase()}` + + return { + ethTransactionAmount, + fiatDisplayValue, + } +} + +export default compose( + withRouter, + connect(mapStateToProps), +)(TransactionListItem) diff --git a/ui/app/components/transaction-list/index.js b/ui/app/components/transaction-list/index.js new file mode 100644 index 000000000..688994367 --- /dev/null +++ b/ui/app/components/transaction-list/index.js @@ -0,0 +1 @@ +export { default } from './transaction-list.container' diff --git a/ui/app/components/transaction-list/index.scss b/ui/app/components/transaction-list/index.scss new file mode 100644 index 000000000..f6f209831 --- /dev/null +++ b/ui/app/components/transaction-list/index.scss @@ -0,0 +1,40 @@ +.transaction-list { + display: flex; + flex-direction: column; + flex: 1; + overflow-y: hidden; + + &__header { + flex: 0 0 auto; + font-size: .875rem; + color: $dusty-gray; + border-bottom: 1px solid $geyser; + padding: 16px 0 8px 20px; + + @media screen and (max-width: $break-small) { + padding: 8px 0 8px 16px; + } + } + + &__transactions { + flex: 1; + overflow-y: auto; + } + + &__pending-transactions { + margin-bottom: 16px; + } + + &__empty { + flex: 1; + display: grid; + grid-template-rows: 35% 1fr; + } + + &__empty-text { + grid-row-start: 2; + display: flex; + justify-content: center; + color: $silver; + } +} diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/transaction-list/transaction-list.component.js new file mode 100644 index 000000000..63d171127 --- /dev/null +++ b/ui/app/components/transaction-list/transaction-list.component.js @@ -0,0 +1,90 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import TransactionListItem from '../transaction-list-item' + +export default class TransactionList extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + static defaultProps = { + pendingTransactions: [], + completedTransactions: [], + } + + static propTypes = { + pendingTransactions: PropTypes.array, + completedTransactions: PropTypes.array, + } + + renderTransactions () { + const { t } = this.context + const { pendingTransactions, completedTransactions } = this.props + + return ( +
    + { + pendingTransactions.length > 0 && ( +
    +
    + { `${t('pending')} (${pendingTransactions.length})` } +
    + { + pendingTransactions.map(transaction => { + return ( + + ) + }) + } +
    + ) + } +
    +
    + { t('history') } +
    + { + completedTransactions.length > 0 + ? ( + completedTransactions.map(transaction => { + return ( + + ) + }) + ) + : this.renderEmpty() + } +
    +
    + ) + } + + renderEmpty () { + return ( +
    +
    + { this.context.t('noTransactions') } +
    +
    + ) + } + + render () { + return ( +
    + { + this.renderTransactions() + // pendingTransactions.length + completedTransactions.length > 0 + // ? this.renderTransactions() + // : this.renderEmpty() + } +
    + ) + } +} diff --git a/ui/app/components/transaction-list/transaction-list.container.js b/ui/app/components/transaction-list/transaction-list.container.js new file mode 100644 index 000000000..b1c2c04c9 --- /dev/null +++ b/ui/app/components/transaction-list/transaction-list.container.js @@ -0,0 +1,20 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import TransactionList from './transaction-list.component' +import { + pendingTransactionsSelector, + completedTransactionsSelector, +} from '../../selectors/transactions' + +const mapStateToProps = state => { + return { + pendingTransactions: pendingTransactionsSelector(state), + completedTransactions: completedTransactionsSelector(state), + } +} + +export default compose( + withRouter, + connect(mapStateToProps) +)(TransactionList) diff --git a/ui/app/components/transaction-status/index.scss b/ui/app/components/transaction-status/index.scss index dd9bf5877..03a378b4e 100644 --- a/ui/app/components/transaction-status/index.scss +++ b/ui/app/components/transaction-status/index.scss @@ -10,6 +10,12 @@ justify-content: center; align-items: center; + @media screen and (max-width: $break-small) { + height: 24px; + width: 74px; + font-size: .5rem; + } + &--confirmed { background-color: #eafad7; color: #609a1c; diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js index 654090da6..aa540249f 100644 --- a/ui/app/components/tx-view.js +++ b/ui/app/components/tx-view.js @@ -15,6 +15,9 @@ const Tooltip = require('./tooltip') const TxList = require('./tx-list') const SelectedAccount = require('./selected-account') +import Media from 'react-media' +import MenuBar from './menu-bar' + module.exports = compose( withRouter, connect(mapStateToProps, mapDispatchToProps) @@ -104,49 +107,11 @@ TxView.prototype.renderButtons = function () { } TxView.prototype.render = function () { - const { hideSidebar, isMascara, showSidebar, sidebarOpen } = this.props - const { t } = this.context - - return h('div.tx-view.flex-column', { - style: {}, - }, [ - - h('div.flex-row.phone-visible', { - style: { - justifyContent: 'center', - alignItems: 'center', - flex: '0 0 auto', - marginBottom: '16px', - padding: '5px', - borderBottom: '1px solid #e5e5e5', - }, - }, [ - - h(Tooltip, { - title: t('menu'), - position: 'bottom', - }, [ - h('div.fa.fa-bars', { - style: { - fontSize: '1.3em', - cursor: 'pointer', - padding: '10px', - }, - onClick: () => sidebarOpen ? hideSidebar() : showSidebar(), - }), - ]), - - h(SelectedAccount), - - !isMascara && h(Tooltip, { - title: t('openInTab'), - position: 'bottom', - }, [ - h('div.open-in-browser', { - onClick: () => global.platform.openExtensionInBrowser(), - }, [h('img', { src: 'images/popout.svg' })]), - ]), - ]), + return h('div.tx-view.flex-column', [ + h(Media, { + query: '(max-width: 575px)', + render: () => h(MenuBar), + }), this.renderHeroBalance(), diff --git a/ui/app/constants/transactions.js b/ui/app/constants/transactions.js new file mode 100644 index 000000000..8b843ba2b --- /dev/null +++ b/ui/app/constants/transactions.js @@ -0,0 +1,18 @@ +export const UNAPPROVED_STATUS = 'unapproved' +export const REJECTED_STATUS = 'rejected' +export const APPROVED_STATUS = 'approved' +export const SIGNED_STATUS = 'signed' +export const SUBMITTED_STATUS = 'submitted' +export const CONFIRMED_STATUS = 'confirmed' +export const FAILED_STATUS = 'failed' +export const DROPPED_STATUS = 'dropped' + +export const TOKEN_METHOD_TRANSFER = 'transfer' +export const TOKEN_METHOD_APPROVE = 'approve' +export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom' + +export const SEND_ETHER_ACTION_KEY = 'sendEther' +export const DEPLOY_CONTRACT_ACTION_KEY = 'contractDeployment' +export const APPROVE_ACTION_KEY = 'approve' +export const SEND_TOKEN_ACTION_KEY = 'sendToken' +export const TRANSFER_FROM_ACTION_KEY = 'transferFrom' diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss deleted file mode 100644 index eba93ecb4..000000000 --- a/ui/app/css/itcss/components/hero-balance.scss +++ /dev/null @@ -1,130 +0,0 @@ -.hero-balance { - - @media screen and (max-width: $break-small) { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - flex: 0 0 auto; - padding-top: 16px; - } - - @media screen and (min-width: $break-large) { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - margin: 2.3em 2.37em .8em; - flex: 0 0 auto; - } - - .balance-container { - display: flex; - margin: 0; - justify-content: flex-start; - align-items: center; - - @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 { - font-size: 115%; - margin-top: 8.5%; - color: #a0a0a0; - } - } - - @media screen and (min-width: $break-large) { - margin: 0 .8em; - justify-content: flex-start; - align-items: flex-start; - min-width: 0; - - .token-amount { - font-size: 1.5rem; - } - - .fiat-amount { - margin-top: .25%; - font-size: 105%; - } - } - - @media #{$sub-mid-size-breakpoint-range} { - margin-left: .4em; - margin-right: .4em; - justify-content: flex-start; - align-items: flex-start; - - .token-amount { - font-size: 1rem; - } - - .fiat-amount { - margin-top: .25%; - font-size: 1rem; - } - } - } - - .hero-balance-buttons { - - @media screen and (max-width: $break-small) { - width: 100%; - // height: 100px; // needed a round number to set the heights of the buttons inside - flex: 0 0 auto; - padding: 16px 0; - } - - @media screen and (min-width: $break-large) { - flex-grow: 2; - justify-content: flex-end; - } - } -} - -.hero-balance-button { - min-width: initial; - width: 6rem; - - @media #{$sub-mid-size-breakpoint-range} { - padding: .4rem; - width: 4rem; - display: flex; - flex: 1; - justify-content: center; - } -} diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index 821a6b612..9e2008b54 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -19,8 +19,6 @@ @import './loading-overlay.scss'; // Balances -@import './hero-balance.scss'; - @import './wallet-balance.scss'; // Tx List and Sections diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index bbfd85c90..7ad5cd076 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -49,13 +49,6 @@ $wallet-view-bg: $alabaster; } } -.open-in-browser { - cursor: pointer; - display: flex; - justify-content: center; - padding: 10px; -} - // wallet view and sidebar .wallet-view { diff --git a/ui/app/ducks/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction.duck.js index f17933ddd..eb56d5695 100644 --- a/ui/app/ducks/confirm-transaction.duck.js +++ b/ui/app/ducks/confirm-transaction.duck.js @@ -6,8 +6,7 @@ import { import { getTokenData, - getMethodData, - getTransactionAmount, + getValueFromWeiHex, getTransactionFee, getHexGasTotal, addFiat, @@ -17,6 +16,7 @@ import { isSmartContractAddress, } from '../helpers/confirm-transaction/util' +import { getMethodData } from '../helpers/transactions.util' import { getSymbolAndDecimals } from '../token-util' import { conversionUtil } from '../conversion-util' @@ -301,10 +301,10 @@ export function updateTxDataAndCalculate (txData) { const { txParams: { value, gas: gasLimit = '0x0', gasPrice = '0x0' } = {} } = txData - const fiatTransactionAmount = getTransactionAmount({ + const fiatTransactionAmount = getValueFromWeiHex({ value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2, }) - const ethTransactionAmount = getTransactionAmount({ + const ethTransactionAmount = getValueFromWeiHex({ value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 6, }) diff --git a/ui/app/helpers/confirm-transaction/util.js b/ui/app/helpers/confirm-transaction/util.js index 3d0cb57e7..04978b48f 100644 --- a/ui/app/helpers/confirm-transaction/util.js +++ b/ui/app/helpers/confirm-transaction/util.js @@ -7,9 +7,6 @@ import BigNumber from 'bignumber.js' abiDecoder.addABI(abi) -import MethodRegistry from 'eth-method-registry' -const registry = new MethodRegistry({ provider: global.ethereumProvider }) - import { conversionUtil, addCurrencies, @@ -23,18 +20,6 @@ export function getTokenData (data = {}) { return abiDecoder.decodeMethod(data) } -export async function getMethodData (data = {}) { - const prefixedData = ethUtil.addHexPrefix(data) - const fourBytePrefix = prefixedData.slice(0, 10) - const sig = await registry.lookup(fourBytePrefix) - const parsedResult = registry.parse(sig) - - return { - name: parsedResult.name, - params: parsedResult.args, - } -} - export function increaseLastGasPrice (lastGasPrice) { return ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { multiplicandBase: 16, @@ -76,7 +61,7 @@ export function addFiat (...args) { }) } -export function getTransactionAmount ({ +export function getValueFromWeiHex ({ value, toCurrency, conversionRate, diff --git a/ui/app/helpers/confirm-transaction/util.test.js b/ui/app/helpers/confirm-transaction/util.test.js index a9c8fae34..4c1a3e16b 100644 --- a/ui/app/helpers/confirm-transaction/util.test.js +++ b/ui/app/helpers/confirm-transaction/util.test.js @@ -92,9 +92,9 @@ describe('Confirm Transaction utils', () => { }) }) - describe('getTransactionAmount', () => { + describe('getValueFromWeiHex', () => { it('should get the transaction amount in ETH', () => { - const ethTransactionAmount = utils.getTransactionAmount({ + const ethTransactionAmount = utils.getValueFromWeiHex({ value: '0xde0b6b3a7640000', toCurrency: 'ETH', conversionRate: 468.58, numberOfDecimals: 6, }) @@ -102,7 +102,7 @@ describe('Confirm Transaction utils', () => { }) it('should get the transaction amount in fiat', () => { - const fiatTransactionAmount = utils.getTransactionAmount({ + const fiatTransactionAmount = utils.getValueFromWeiHex({ value: '0xde0b6b3a7640000', toCurrency: 'usd', conversionRate: 468.58, numberOfDecimals: 2, }) diff --git a/ui/app/helpers/conversions.util.js b/ui/app/helpers/conversions.util.js new file mode 100644 index 000000000..1dec216fa --- /dev/null +++ b/ui/app/helpers/conversions.util.js @@ -0,0 +1,37 @@ +import { conversionUtil } from '../conversion-util' + +export function hexToDecimal (hexValue) { + return conversionUtil(hexValue, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + }) +} + +export function getEthFromWeiHex ({ + value, + conversionRate, +}) { + return getValueFromWeiHex({ + value, + conversionRate, + toCurrency: 'ETH', + numberOfDecimals: 6, + }) +} + +export function getValueFromWeiHex ({ + value, + toCurrency, + conversionRate, + numberOfDecimals, +}) { + return conversionUtil(value, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromCurrency: 'ETH', + toCurrency, + numberOfDecimals, + fromDenomination: 'WEI', + conversionRate, + }) +} diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js new file mode 100644 index 000000000..04cef150f --- /dev/null +++ b/ui/app/helpers/transactions.util.js @@ -0,0 +1,57 @@ +import ethUtil from 'ethereumjs-util' +import MethodRegistry from 'eth-method-registry' +const registry = new MethodRegistry({ provider: global.ethereumProvider }) + +import { + TOKEN_METHOD_TRANSFER, + TOKEN_METHOD_APPROVE, + TOKEN_METHOD_TRANSFER_FROM, + SEND_ETHER_ACTION_KEY, + DEPLOY_CONTRACT_ACTION_KEY, + APPROVE_ACTION_KEY, + SEND_TOKEN_ACTION_KEY, + TRANSFER_FROM_ACTION_KEY, +} from '../constants/transactions' + +export function isConfirmDeployContract (txData = {}) { + const { txParams = {} } = txData + return !txParams.to +} + +export function getTransactionActionKey (transaction, methodData) { + const { txParams: { data } = {} } = transaction + + if (isConfirmDeployContract(transaction)) { + return DEPLOY_CONTRACT_ACTION_KEY + } + + if (data) { + const { name } = methodData + const methodName = name && name.toLowerCase() + + switch (methodName) { + case TOKEN_METHOD_TRANSFER: + return SEND_TOKEN_ACTION_KEY + case TOKEN_METHOD_APPROVE: + return APPROVE_ACTION_KEY + case TOKEN_METHOD_TRANSFER_FROM: + return TRANSFER_FROM_ACTION_KEY + default: + return name + } + } else { + return SEND_ETHER_ACTION_KEY + } +} + +export async function getMethodData (data = {}) { + const prefixedData = ethUtil.addHexPrefix(data) + const fourBytePrefix = prefixedData.slice(0, 10) + const sig = await registry.lookup(fourBytePrefix) + const parsedResult = registry.parse(sig) + + return { + name: parsedResult.name, + params: parsedResult.args, + } +} diff --git a/ui/app/higher-order-components/with-method-data/with-method-data.component.js b/ui/app/higher-order-components/with-method-data/with-method-data.component.js index aa38afd8a..c05d33c20 100644 --- a/ui/app/higher-order-components/with-method-data/with-method-data.component.js +++ b/ui/app/higher-order-components/with-method-data/with-method-data.component.js @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' -import { getMethodData } from '../../helpers/confirm-transaction/util' +import { getMethodData } from '../../helpers/transactions.util' export default function withMethodData (WrappedComponent) { return class MethodDataWrappedComponent extends PureComponent { @@ -13,7 +13,11 @@ export default function withMethodData (WrappedComponent) { } state = { - methodData: {}, + methodData: { + data: {}, + }, + isFetching: false, + error: null, } componentDidMount () { @@ -25,18 +29,24 @@ export default function withMethodData (WrappedComponent) { const { txParams: { data = '' } = {} } = transaction if (data) { - const methodData = await getMethodData(data) - this.setState({ methodData }) + this.setState({ isFetching: true }) + + try { + const methodData = await getMethodData(data) + this.setState({ methodData, isFetching: false }) + } catch (error) { + this.setState({ isFetching: false, error }) + } } } render () { - const { methodData } = this.state + const { methodData, isFetching, error } = this.state return ( ) } diff --git a/ui/app/i18n-provider.js b/ui/app/i18n-provider.js index d46911f7c..936b185f7 100644 --- a/ui/app/i18n-provider.js +++ b/ui/app/i18n-provider.js @@ -13,6 +13,9 @@ class I18nProvider extends Component { t (key, ...args) { return t(current, key, ...args) || t(en, key, ...args) || `[${key}]` }, + tOrDefault (key, ...args) { + return t(current, key, ...args) || t(en, key, ...args) || key + }, } } @@ -28,6 +31,7 @@ I18nProvider.propTypes = { I18nProvider.childContextTypes = { t: PropTypes.func, + tOrDefault: PropTypes.func, } const mapStateToProps = state => { diff --git a/ui/app/selectors.js b/ui/app/selectors.js index d86462275..1b0100297 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -1,5 +1,9 @@ -const valuesFor = require('./util').valuesFor const abi = require('human-standard-token-abi') +import { createSelector } from 'reselect' + +import { + transactionsSelector, +} from './selectors/transactions' const { multiplyCurrencies, @@ -101,21 +105,49 @@ 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 shapeShiftTxListSelector (state) { +// // return state.metamask.shapeShiftTxList || [] +// // } + +// const transactionsSelector = createSelector( +// selectedTokenAddressSelector, +// unapprovedMsgsSelector, +// shapeShiftTxListSelector, +// selectedAddressTxListSelector, +// (selectedTokenAddress, unapprovedMsgs = {}, shapeShiftTxList = [], transactions = []) => { +// const unapprovedMsgsList = valuesFor(unapprovedMsgs) +// const txsToRender = transactions.concat(unapprovedMsgsList, 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) +// } +// ) + +// // function transactionsSelector (state) { +// // const { selectedTokenAddress } = state.metamask +// // const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs) +// // const shapeShiftTxList = shapeShiftTxListSelector(state) +// // const transactions = state.metamask.selectedAddressTxList || [] +// // const txsToRender = 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) +// // } + +export const pendingTransactionsSelector = createSelector( + transactionsSelector, + transactions => { - // 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 getGasIsLoading (state) { return state.appState.gasIsLoading diff --git a/ui/app/selectors/transactions.js b/ui/app/selectors/transactions.js new file mode 100644 index 000000000..a265b8e70 --- /dev/null +++ b/ui/app/selectors/transactions.js @@ -0,0 +1,50 @@ +import { createSelector } from 'reselect' +import { valuesFor } from '../util' +import { + UNAPPROVED_STATUS, + APPROVED_STATUS, + SUBMITTED_STATUS, +} from '../constants/transactions' + +export const shapeShiftTxListSelector = state => state.metamask.shapeShiftTxList +export const selectedTokenAddressSelector = state => state.metamask.selectedTokenAddress +export const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs +export const selectedAddressTxListSelector = state => state.metamask.selectedAddressTxList + +const pendingStatusHash = { + [UNAPPROVED_STATUS]: true, + [APPROVED_STATUS]: true, + [SUBMITTED_STATUS]: true, +} + +export const transactionsSelector = createSelector( + selectedTokenAddressSelector, + unapprovedMsgsSelector, + shapeShiftTxListSelector, + selectedAddressTxListSelector, + (selectedTokenAddress, unapprovedMsgs = {}, shapeShiftTxList = [], transactions = []) => { + const unapprovedMsgsList = valuesFor(unapprovedMsgs) + const txsToRender = transactions.concat(unapprovedMsgsList, 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) + } +) + +export const pendingTransactionsSelector = createSelector( + transactionsSelector, + (transactions = []) => ( + transactions.filter(transaction => transaction.status in pendingStatusHash) + ) +) + +export const completedTransactionsSelector = createSelector( + transactionsSelector, + (transactions = []) => ( + transactions.filter(transaction => !(transaction.status in pendingStatusHash)) + ) +) -- cgit v1.2.3 From 01f00a9ca6807dd019a68bf2be8d99cee67a2738 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 31 Jul 2018 19:21:25 -0700 Subject: Add tOrKey function to I18nProvider --- ui/app/i18n-provider.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/i18n-provider.js b/ui/app/i18n-provider.js index 936b185f7..3419474c4 100644 --- a/ui/app/i18n-provider.js +++ b/ui/app/i18n-provider.js @@ -6,6 +6,11 @@ const { compose } = require('recompose') const t = require('../i18n-helper').getMessage class I18nProvider extends Component { + tOrDefault = (key, defaultValue, ...args) => { + const { localeMessages: { current, en } = {} } = this.props + return t(current, key, ...args) || t(en, key, ...args) || defaultValue + } + getChildContext () { const { localeMessages } = this.props const { current, en } = localeMessages @@ -13,8 +18,9 @@ class I18nProvider extends Component { t (key, ...args) { return t(current, key, ...args) || t(en, key, ...args) || `[${key}]` }, - tOrDefault (key, ...args) { - return t(current, key, ...args) || t(en, key, ...args) || key + tOrDefault: this.tOrDefault, + tOrKey (key, ...args) { + return this.tOrDefault(key, key, ...args) }, } } @@ -32,6 +38,7 @@ I18nProvider.propTypes = { I18nProvider.childContextTypes = { t: PropTypes.func, tOrDefault: PropTypes.func, + tOrKey: PropTypes.func, } const mapStateToProps = state => { -- cgit v1.2.3 From 5de48c67a080f2681a005e364eefb9ea1d6b1e12 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 31 Jul 2018 19:37:38 -0700 Subject: Use css grid for TransactionListItem for responsive layout --- ui/app/components/identicon.js | 3 +- ui/app/components/transaction-list-item/index.scss | 81 ++++++++++++++-------- .../transaction-list-item.component.js | 53 +++++++------- .../transaction-list/transaction-list.component.js | 2 +- ui/app/components/transaction-status/index.scss | 4 +- .../transaction-status.component.js | 5 +- ui/app/constants/transactions.js | 4 +- ui/app/util.js | 2 +- 8 files changed, 88 insertions(+), 66 deletions(-) (limited to 'ui') diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js index 424048745..80db2b8e9 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -47,7 +47,8 @@ IdenticonComponent.prototype.render = function () { }) ) : ( - h('img.balance-icon', { + h('img', { + className: `${className} balance-icon`, src: './images/eth_logo.svg', style: { height: diameter, diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/transaction-list-item/index.scss index 8a3973f92..b93edebcc 100644 --- a/ui/app/components/transaction-list-item/index.scss +++ b/ui/app/components/transaction-list-item/index.scss @@ -1,41 +1,51 @@ .transaction-list-item { box-sizing: border-box; - height: 74px; - padding: 0 21px; - display: flex; - flex-direction: row; - align-items: center; + min-height: 74px; + padding: 8px 20px; + display: grid; + grid-template-columns: 45px 1fr 1fr 1fr; + grid-template-areas: + "identicon action status primary-amount" + "identicon nonce status secondary-amount"; border-bottom: 1px solid $geyser; cursor: pointer; @media screen and (max-width: $break-small) { - padding: 0 12px; + padding: 8px 20px 12px; + grid-template-columns: 45px 5fr 3fr; + grid-template-areas: + "nonce nonce nonce" + "identicon action primary-amount" + "identicon status secondary-amount"; } - &__identicon-wrapper { - padding-top: 2px; - } - - &__action-block { - padding: 0 8px 0 12px; - width: 180px; + &__identicon { + grid-area: identicon; + grid-row: 1 / span 2; + align-self: center; @media screen and (max-width: $break-small) { - padding: 0 8px; - width: 160px; + grid-row: 2 / span 2; } } &__action { text-transform: capitalize; - padding-bottom: 2px; + padding: 0 8px 2px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + grid-area: action; + align-self: end; + } + + &__status { + grid-area: status; + grid-row: 1 / span 2; + align-self: center; @media screen and (max-width: $break-small) { - padding-bottom: 0; - font-size: .875rem; + grid-row: 3; } } @@ -45,25 +55,38 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - } + grid-area: nonce; + align-self: start; - &__transaction-amounts { - flex: 1; + @media screen and (max-width: $break-small) { + padding-bottom: 4px; + } } - &__primary-transaction-amount { - text-align: end; + &__amount { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; - @media screen and (max-width: $break-small) { + &--primary { + text-align: end; + grid-area: primary-amount; + align-self: end; + + @media screen and (max-width: $break-small) { + padding-bottom: 2px; + } + } + + &--secondary { + text-align: end; font-size: .75rem; + color: #5e6064; + grid-area: secondary-amount; + align-self: start; } } - &__secondary-transaction-amount { - text-align: end; - font-size: .75rem; - color: #5e6064; - } &:hover { background: rgba($alto, .2); diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index e334cd938..8c2a0d04c 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -1,6 +1,5 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' -import Media from 'react-media' import Identicon from '../identicon' import TransactionStatus from '../transaction-status' import TransactionAction from '../transaction-action' @@ -46,35 +45,33 @@ export default class TransactionListItem extends PureComponent { className="transaction-list-item" onClick={this.handleClick} > -
    - - { - matches => ( - - ) - } - + + +
    + { `#${nonce} - ${formatDate(transaction.time)}` }
    -
    - -
    - { `#${nonce} - ${formatDate(transaction.time)}` } -
    + +
    + { `-${fiatDisplayValue}` }
    - -
    -
    - { `-${fiatDisplayValue}` } -
    -
    - { `-${ethTransactionAmount} ETH` } -
    +
    + { `-${ethTransactionAmount} ETH` }
    ) diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/transaction-list/transaction-list.component.js index 63d171127..48e731d24 100644 --- a/ui/app/components/transaction-list/transaction-list.component.js +++ b/ui/app/components/transaction-list/transaction-list.component.js @@ -27,7 +27,7 @@ export default class TransactionList extends PureComponent { pendingTransactions.length > 0 && (
    - { `${t('pending')} (${pendingTransactions.length})` } + { `${t('queue')} (${pendingTransactions.length})` }
    { pendingTransactions.map(transaction => { diff --git a/ui/app/components/transaction-status/index.scss b/ui/app/components/transaction-status/index.scss index 03a378b4e..95d29f6d3 100644 --- a/ui/app/components/transaction-status/index.scss +++ b/ui/app/components/transaction-status/index.scss @@ -11,8 +11,8 @@ align-items: center; @media screen and (max-width: $break-small) { - height: 24px; - width: 74px; + height: 16px; + width: 70px; font-size: .5rem; } diff --git a/ui/app/components/transaction-status/transaction-status.component.js b/ui/app/components/transaction-status/transaction-status.component.js index cf688558f..1b05d61b2 100644 --- a/ui/app/components/transaction-status/transaction-status.component.js +++ b/ui/app/components/transaction-status/transaction-status.component.js @@ -30,13 +30,14 @@ const statusToTextHash = { export default class TransactionStatus extends PureComponent { static propTypes = { status: PropTypes.string, + className: PropTypes.string, } render () { - const { status } = this.props + const { className, status } = this.props return ( -
    +
    { statusToTextHash[status] || status }
    ) diff --git a/ui/app/constants/transactions.js b/ui/app/constants/transactions.js index 8b843ba2b..a9ab1a0d6 100644 --- a/ui/app/constants/transactions.js +++ b/ui/app/constants/transactions.js @@ -11,8 +11,8 @@ export const TOKEN_METHOD_TRANSFER = 'transfer' export const TOKEN_METHOD_APPROVE = 'approve' export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom' -export const SEND_ETHER_ACTION_KEY = 'sendEther' +export const SEND_ETHER_ACTION_KEY = 'outgoing' export const DEPLOY_CONTRACT_ACTION_KEY = 'contractDeployment' export const APPROVE_ACTION_KEY = 'approve' -export const SEND_TOKEN_ACTION_KEY = 'sendToken' +export const SEND_TOKEN_ACTION_KEY = 'outgoing' export const TRANSFER_FROM_ACTION_KEY = 'transferFrom' diff --git a/ui/app/util.js b/ui/app/util.js index ade4fec8a..d5558c04e 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -9,7 +9,7 @@ const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR) // formatData :: ( date: ) -> String function formatDate (date) { - return vreme.format(new Date(date), 'March 16 2014 14:30') + return vreme.format(new Date(date), 'March 16 2014, at 14:30') } var valueTable = { -- cgit v1.2.3 From fa8313f9036882e1a558d871f4e520da71ffaa03 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 31 Jul 2018 19:56:51 -0700 Subject: Code cleanup --- .../transaction-list-item.component.js | 7 +--- .../transaction-list/transaction-list.component.js | 37 ++++++++-------------- 2 files changed, 14 insertions(+), 30 deletions(-) (limited to 'ui') diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index 8c2a0d04c..928d531f0 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -12,7 +12,6 @@ import { hexToDecimal } from '../../helpers/conversions.util' export default class TransactionListItem extends PureComponent { static propTypes = { history: PropTypes.object, - methodData: PropTypes.object, transaction: PropTypes.object, ethTransactionAmount: PropTypes.string, fiatDisplayValue: PropTypes.string, @@ -32,11 +31,7 @@ export default class TransactionListItem extends PureComponent { } render () { - const { - transaction, - ethTransactionAmount, - fiatDisplayValue, - } = this.props + const { transaction, ethTransactionAmount, fiatDisplayValue } = this.props const { txParams = {} } = transaction const nonce = hexToDecimal(txParams.nonce) diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/transaction-list/transaction-list.component.js index 48e731d24..d9b8e3cf8 100644 --- a/ui/app/components/transaction-list/transaction-list.component.js +++ b/ui/app/components/transaction-list/transaction-list.component.js @@ -30,14 +30,12 @@ export default class TransactionList extends PureComponent { { `${t('queue')} (${pendingTransactions.length})` }
    { - pendingTransactions.map(transaction => { - return ( - - ) - }) + pendingTransactions.map(transaction => ( + + )) }
    ) @@ -48,16 +46,12 @@ export default class TransactionList extends PureComponent {
    { completedTransactions.length > 0 - ? ( - completedTransactions.map(transaction => { - return ( - - ) - }) - ) + ? completedTransactions.map(transaction => ( + + )) : this.renderEmpty() }
    @@ -78,12 +72,7 @@ export default class TransactionList extends PureComponent { render () { return (
    - { - this.renderTransactions() - // pendingTransactions.length + completedTransactions.length > 0 - // ? this.renderTransactions() - // : this.renderEmpty() - } + { this.renderTransactions() }
    ) } -- cgit v1.2.3 From 5ddd9b55be0d8bd778822b4b401cbd22a7b57c54 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 2 Aug 2018 20:20:15 -0700 Subject: Add retry button to TransactionListItem --- ui/app/components/token-view-balance/index.scss | 10 ++ .../token-view-balance.component.js | 2 +- ui/app/components/transaction-action/index.js | 2 +- .../transaction-action.container.js | 4 - ui/app/components/transaction-list-item/index.scss | 45 ++++++--- .../transaction-list-item.component.js | 110 +++++++++++++++------ .../transaction-list-item.container.js | 12 ++- .../transaction-list/transaction-list.component.js | 11 ++- .../transaction-list/transaction-list.container.js | 6 +- ui/app/helpers/transactions.util.js | 21 ++++ ui/app/util.js | 2 +- 11 files changed, 175 insertions(+), 50 deletions(-) delete mode 100644 ui/app/components/transaction-action/transaction-action.container.js (limited to 'ui') diff --git a/ui/app/components/token-view-balance/index.scss b/ui/app/components/token-view-balance/index.scss index 6a89e125b..b522a10f9 100644 --- a/ui/app/components/token-view-balance/index.scss +++ b/ui/app/components/token-view-balance/index.scss @@ -16,6 +16,16 @@ } } + &__token-balance { + margin-left: 12px; + font-size: 1.5rem; + + @media screen and (max-width: $break-small) { + margin-bottom: 12px; + font-size: 1.75rem; + } + } + &__primary-balance { font-size: 1.5rem; diff --git a/ui/app/components/token-view-balance/token-view-balance.component.js b/ui/app/components/token-view-balance/token-view-balance.component.js index 6b8140a22..f74cc4926 100644 --- a/ui/app/components/token-view-balance/token-view-balance.component.js +++ b/ui/app/components/token-view-balance/token-view-balance.component.js @@ -30,7 +30,7 @@ export default class TokenViewBalance extends PureComponent { ) : (
    diff --git a/ui/app/components/transaction-action/index.js b/ui/app/components/transaction-action/index.js index 5882443b6..a6e9097f1 100644 --- a/ui/app/components/transaction-action/index.js +++ b/ui/app/components/transaction-action/index.js @@ -1 +1 @@ -export { default } from './transaction-action.container' +export { default } from './transaction-action.component' diff --git a/ui/app/components/transaction-action/transaction-action.container.js b/ui/app/components/transaction-action/transaction-action.container.js deleted file mode 100644 index 56efbdc26..000000000 --- a/ui/app/components/transaction-action/transaction-action.container.js +++ /dev/null @@ -1,4 +0,0 @@ -import withMethodData from '../../higher-order-components/with-method-data' -import TransactionAction from './transaction-action.component' - -export default withMethodData(TransactionAction) diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/transaction-list-item/index.scss index b93edebcc..9c53c8960 100644 --- a/ui/app/components/transaction-list-item/index.scss +++ b/ui/app/components/transaction-list-item/index.scss @@ -2,21 +2,36 @@ box-sizing: border-box; min-height: 74px; padding: 8px 20px; - display: grid; - grid-template-columns: 45px 1fr 1fr 1fr; - grid-template-areas: - "identicon action status primary-amount" - "identicon nonce status secondary-amount"; border-bottom: 1px solid $geyser; cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; @media screen and (max-width: $break-small) { padding: 8px 20px 12px; - grid-template-columns: 45px 5fr 3fr; + } + + &:hover { + background: rgba($alto, .2); + } + + &__grid { + width: 100%; + display: grid; + grid-template-columns: 45px 1fr 1fr 1fr; grid-template-areas: - "nonce nonce nonce" - "identicon action primary-amount" - "identicon status secondary-amount"; + "identicon action status primary-amount" + "identicon nonce status secondary-amount"; + + @media screen and (max-width: $break-small) { + grid-template-columns: 45px 5fr 3fr; + grid-template-areas: + "nonce nonce nonce" + "identicon action primary-amount" + "identicon status secondary-amount"; + } } &__identicon { @@ -87,8 +102,16 @@ } } + &__retry { + background: #d1edff; + border-radius: 12px; + font-size: .75rem; + padding: 4px 12px; + cursor: pointer; + margin-top: 8px; - &:hover { - background: rgba($alto, .2); + @media screen and (max-width: $break-small) { + font-size: .5rem; + } } } diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index 928d531f0..bf3f09d28 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -6,7 +6,7 @@ import TransactionAction from '../transaction-action' import { formatDate } from '../../util' import prefixForNetwork from '../../../lib/etherscan-prefix-for-network' import { CONFIRM_TRANSACTION_ROUTE } from '../../routes' -import { UNAPPROVED_STATUS } from '../../constants/transactions' +import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions' import { hexToDecimal } from '../../helpers/conversions.util' export default class TransactionListItem extends PureComponent { @@ -15,6 +15,10 @@ export default class TransactionListItem extends PureComponent { transaction: PropTypes.object, ethTransactionAmount: PropTypes.string, fiatDisplayValue: PropTypes.string, + methodData: PropTypes.object, + showRetry: PropTypes.bool, + retryTransaction: PropTypes.func, + setSelectedToken: PropTypes.func, } handleClick = () => { @@ -30,44 +34,92 @@ export default class TransactionListItem extends PureComponent { } } + handleRetryClick = event => { + event.stopPropagation() + + const { + transaction: { txParams: { to } = {} }, + methodData: { name } = {}, + setSelectedToken, + } = this.props + + if (name === TOKEN_METHOD_TRANSFER) { + setSelectedToken(to) + } + + this.resubmit() + } + + resubmit () { + const { transaction: { id }, retryTransaction, history } = this.props + retryTransaction(id) + .then(id => history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)) + } + render () { - const { transaction, ethTransactionAmount, fiatDisplayValue } = this.props + const { + transaction, + ethTransactionAmount, + fiatDisplayValue, + methodData, + showRetry, + } = this.props const { txParams = {} } = transaction const nonce = hexToDecimal(txParams.nonce) + const nonceAndDateText = `#${nonce} - ${formatDate(transaction.time)}` + const fiatDisplayText = `-${fiatDisplayValue}` + const ethDisplayText = `-${ethTransactionAmount} ETH` + return (
    - - -
    - { `#${nonce} - ${formatDate(transaction.time)}` } -
    - -
    - { `-${fiatDisplayValue}` } -
    -
    - { `-${ethTransactionAmount} ETH` } +
    + + +
    + { nonceAndDateText } +
    + +
    + { fiatDisplayText } +
    +
    + { ethDisplayText } +
    + { + showRetry && !methodData.isFetching && ( +
    + Taking too long? Increase the gas price on your transaction +
    + ) + }
    ) } diff --git a/ui/app/components/transaction-list-item/transaction-list-item.container.js b/ui/app/components/transaction-list-item/transaction-list-item.container.js index bc47f20aa..d6e57028e 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.container.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js @@ -1,7 +1,9 @@ import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' import { compose } from 'recompose' +import withMethodData from '../../higher-order-components/with-method-data' import TransactionListItem from './transaction-list-item.component' +import { setSelectedToken, retryTransaction } from '../../actions' import { getEthFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util' import { formatCurrency } from '../../helpers/confirm-transaction/util' @@ -22,7 +24,15 @@ const mapStateToProps = (state, ownProps) => { } } +const mapDispatchToProps = dispatch => { + return { + setSelectedToken: tokenAddress => dispatch(setSelectedToken(tokenAddress)), + retryTransaction: transactionId => dispatch(retryTransaction(transactionId)), + } +} + export default compose( withRouter, - connect(mapStateToProps), + connect(mapStateToProps, mapDispatchToProps), + withMethodData, )(TransactionListItem) diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/transaction-list/transaction-list.component.js index d9b8e3cf8..fb23ece7a 100644 --- a/ui/app/components/transaction-list/transaction-list.component.js +++ b/ui/app/components/transaction-list/transaction-list.component.js @@ -10,16 +10,24 @@ export default class TransactionList extends PureComponent { static defaultProps = { pendingTransactions: [], completedTransactions: [], + transactionToRetry: {}, } static propTypes = { pendingTransactions: PropTypes.array, completedTransactions: PropTypes.array, + transactionToRetry: PropTypes.object, + } + + shouldShowRetry = transaction => { + const { transactionToRetry } = this.props + const { id, submittedTime } = transaction + return id === transactionToRetry.id && Date.now() - submittedTime > 30000 } renderTransactions () { const { t } = this.context - const { pendingTransactions, completedTransactions } = this.props + const { pendingTransactions = [], completedTransactions = [] } = this.props return (
    @@ -34,6 +42,7 @@ export default class TransactionList extends PureComponent { )) } diff --git a/ui/app/components/transaction-list/transaction-list.container.js b/ui/app/components/transaction-list/transaction-list.container.js index b1c2c04c9..97a94b981 100644 --- a/ui/app/components/transaction-list/transaction-list.container.js +++ b/ui/app/components/transaction-list/transaction-list.container.js @@ -6,11 +6,15 @@ import { pendingTransactionsSelector, completedTransactionsSelector, } from '../../selectors/transactions' +import { getLatestSubmittedTxWithEarliestNonce } from '../../helpers/transactions.util' const mapStateToProps = state => { + const pendingTransactions = pendingTransactionsSelector(state) + return { - pendingTransactions: pendingTransactionsSelector(state), completedTransactions: completedTransactionsSelector(state), + pendingTransactions, + transactionToRetry: getLatestSubmittedTxWithEarliestNonce(pendingTransactions), } } diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js index 04cef150f..68e935702 100644 --- a/ui/app/helpers/transactions.util.js +++ b/ui/app/helpers/transactions.util.js @@ -2,6 +2,8 @@ import ethUtil from 'ethereumjs-util' import MethodRegistry from 'eth-method-registry' const registry = new MethodRegistry({ provider: global.ethereumProvider }) +import { hexToDecimal } from './conversions.util' + import { TOKEN_METHOD_TRANSFER, TOKEN_METHOD_APPROVE, @@ -55,3 +57,22 @@ export async function getMethodData (data = {}) { params: parsedResult.args, } } + +export function getLatestSubmittedTxWithEarliestNonce (transactions = []) { + if (!transactions.length) { + return {} + } + + return transactions.reduce((acc, current) => { + const accNonce = hexToDecimal(acc.nonce) + const currentNonce = hexToDecimal(current.nonce) + + if (currentNonce < accNonce) { + return current + } else if (currentNonce === accNonce) { + return current.submittedTime > acc.submittedTime ? current : acc + } else { + return acc + } + }) +} diff --git a/ui/app/util.js b/ui/app/util.js index d5558c04e..37c0fb698 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -9,7 +9,7 @@ const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR) // formatData :: ( date: ) -> String function formatDate (date) { - return vreme.format(new Date(date), 'March 16 2014, at 14:30') + return vreme.format(new Date(date), '3/16/2014 at 14:30') } var valueTable = { -- cgit v1.2.3 From 33a94332e48b280fcf4c9fb23aa4d349eaa8a54d Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sun, 5 Aug 2018 22:25:58 -0700 Subject: Show token amounts in TransactionListItem for token transfers --- .../transaction-list-item.component.js | 2 +- .../transaction-list-item.container.js | 35 +++++++++++++---- .../transaction-list/transaction-list.component.js | 9 ++++- .../transaction-list/transaction-list.container.js | 2 + ui/app/ducks/confirm-transaction.duck.js | 3 +- ui/app/helpers/confirm-transaction/util.js | 8 ---- ui/app/helpers/transactions.util.js | 36 ++++++++++------- ui/app/selectors.js | 45 ---------------------- ui/app/selectors/tokens.js | 11 ++++++ ui/app/selectors/transactions.js | 3 +- 10 files changed, 75 insertions(+), 79 deletions(-) create mode 100644 ui/app/selectors/tokens.js (limited to 'ui') diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index bf3f09d28..d4b1bec94 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -69,7 +69,7 @@ export default class TransactionListItem extends PureComponent { const nonceAndDateText = `#${nonce} - ${formatDate(transaction.time)}` const fiatDisplayText = `-${fiatDisplayValue}` - const ethDisplayText = `-${ethTransactionAmount} ETH` + const ethDisplayText = ethTransactionAmount && `-${ethTransactionAmount} ETH` return (
    { const { metamask } = state const { currentCurrency, conversionRate } = metamask - const { transaction: { txParams: { value } = {} } = {} } = ownProps - const ethTransactionAmount = getEthFromWeiHex({ value, conversionRate }) - const fiatTransactionAmount = getValueFromWeiHex({ - value, conversionRate, toCurrency: currentCurrency, numberOfDecimals: 2, - }) - const fiatFormattedAmount = formatCurrency(fiatTransactionAmount, currentCurrency) - const fiatDisplayValue = `${fiatFormattedAmount} ${currentCurrency.toUpperCase()}` + const { transaction: { txParams: { value, data } = {} } = {}, token } = ownProps + + let ethTransactionAmount, fiatDisplayValue + + if (token) { + const { decimals = '', symbol = '' } = token + const tokenData = getTokenData(data) + + if (tokenData.params && tokenData.params.length === 2) { + const tokenDataName = tokenData.name || '' + const tokenValue = tokenData.params[1].value + const tokenAmount = tokenDataName.toLowerCase() === TOKEN_METHOD_TRANSFER + ? calcTokenAmount(tokenValue, decimals) + : tokenValue + + fiatDisplayValue = `${tokenAmount} ${symbol}` + } + } else { + ethTransactionAmount = getEthFromWeiHex({ value, conversionRate }) + const fiatTransactionAmount = getValueFromWeiHex({ + value, conversionRate, toCurrency: currentCurrency, numberOfDecimals: 2, + }) + const fiatFormattedAmount = formatCurrency(fiatTransactionAmount, currentCurrency) + fiatDisplayValue = `${fiatFormattedAmount} ${currentCurrency.toUpperCase()}` + } return { ethTransactionAmount, diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/transaction-list/transaction-list.component.js index fb23ece7a..953b07217 100644 --- a/ui/app/components/transaction-list/transaction-list.component.js +++ b/ui/app/components/transaction-list/transaction-list.component.js @@ -17,6 +17,7 @@ export default class TransactionList extends PureComponent { pendingTransactions: PropTypes.array, completedTransactions: PropTypes.array, transactionToRetry: PropTypes.object, + selectedToken: PropTypes.object, } shouldShowRetry = transaction => { @@ -27,7 +28,11 @@ export default class TransactionList extends PureComponent { renderTransactions () { const { t } = this.context - const { pendingTransactions = [], completedTransactions = [] } = this.props + const { + pendingTransactions = [], + completedTransactions = [], + selectedToken, + } = this.props return (
    @@ -43,6 +48,7 @@ export default class TransactionList extends PureComponent { transaction={transaction} key={transaction.id} showRetry={this.shouldShowRetry(transaction)} + token={selectedToken} /> )) } @@ -59,6 +65,7 @@ export default class TransactionList extends PureComponent { )) : this.renderEmpty() diff --git a/ui/app/components/transaction-list/transaction-list.container.js b/ui/app/components/transaction-list/transaction-list.container.js index 97a94b981..5a63d916d 100644 --- a/ui/app/components/transaction-list/transaction-list.container.js +++ b/ui/app/components/transaction-list/transaction-list.container.js @@ -6,6 +6,7 @@ import { pendingTransactionsSelector, completedTransactionsSelector, } from '../../selectors/transactions' +import { selectedTokenSelector } from '../../selectors/tokens' import { getLatestSubmittedTxWithEarliestNonce } from '../../helpers/transactions.util' const mapStateToProps = state => { @@ -15,6 +16,7 @@ const mapStateToProps = state => { completedTransactions: completedTransactionsSelector(state), pendingTransactions, transactionToRetry: getLatestSubmittedTxWithEarliestNonce(pendingTransactions), + selectedToken: selectedTokenSelector(state), } } diff --git a/ui/app/ducks/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction.duck.js index eb56d5695..ccb418143 100644 --- a/ui/app/ducks/confirm-transaction.duck.js +++ b/ui/app/ducks/confirm-transaction.duck.js @@ -5,7 +5,6 @@ import { } from '../selectors/confirm-transaction' import { - getTokenData, getValueFromWeiHex, getTransactionFee, getHexGasTotal, @@ -16,7 +15,7 @@ import { isSmartContractAddress, } from '../helpers/confirm-transaction/util' -import { getMethodData } from '../helpers/transactions.util' +import { getTokenData, getMethodData } from '../helpers/transactions.util' import { getSymbolAndDecimals } from '../token-util' import { conversionUtil } from '../conversion-util' diff --git a/ui/app/helpers/confirm-transaction/util.js b/ui/app/helpers/confirm-transaction/util.js index 04978b48f..cbbc27666 100644 --- a/ui/app/helpers/confirm-transaction/util.js +++ b/ui/app/helpers/confirm-transaction/util.js @@ -1,12 +1,8 @@ import currencyFormatter from 'currency-formatter' import currencies from 'currency-formatter/currencies' -import abi from 'human-standard-token-abi' -import abiDecoder from 'abi-decoder' import ethUtil from 'ethereumjs-util' import BigNumber from 'bignumber.js' -abiDecoder.addABI(abi) - import { conversionUtil, addCurrencies, @@ -16,10 +12,6 @@ import { import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction' -export function getTokenData (data = {}) { - return abiDecoder.decodeMethod(data) -} - export function increaseLastGasPrice (lastGasPrice) { return ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { multiplicandBase: 16, diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js index 68e935702..89d2649c9 100644 --- a/ui/app/helpers/transactions.util.js +++ b/ui/app/helpers/transactions.util.js @@ -1,7 +1,7 @@ import ethUtil from 'ethereumjs-util' import MethodRegistry from 'eth-method-registry' -const registry = new MethodRegistry({ provider: global.ethereumProvider }) - +import abi from 'human-standard-token-abi' +import abiDecoder from 'abi-decoder' import { hexToDecimal } from './conversions.util' import { @@ -15,6 +15,26 @@ import { TRANSFER_FROM_ACTION_KEY, } from '../constants/transactions' +abiDecoder.addABI(abi) + +export function getTokenData (data = {}) { + return abiDecoder.decodeMethod(data) +} + +const registry = new MethodRegistry({ provider: global.ethereumProvider }) + +export async function getMethodData (data = {}) { + const prefixedData = ethUtil.addHexPrefix(data) + const fourBytePrefix = prefixedData.slice(0, 10) + const sig = await registry.lookup(fourBytePrefix) + const parsedResult = registry.parse(sig) + + return { + name: parsedResult.name, + params: parsedResult.args, + } +} + export function isConfirmDeployContract (txData = {}) { const { txParams = {} } = txData return !txParams.to @@ -46,18 +66,6 @@ export function getTransactionActionKey (transaction, methodData) { } } -export async function getMethodData (data = {}) { - const prefixedData = ethUtil.addHexPrefix(data) - const fourBytePrefix = prefixedData.slice(0, 10) - const sig = await registry.lookup(fourBytePrefix) - const parsedResult = registry.parse(sig) - - return { - name: parsedResult.name, - params: parsedResult.args, - } -} - export function getLatestSubmittedTxWithEarliestNonce (transactions = []) { if (!transactions.length) { return {} diff --git a/ui/app/selectors.js b/ui/app/selectors.js index 1b0100297..1d5f4d4cb 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -1,5 +1,4 @@ const abi = require('human-standard-token-abi') -import { createSelector } from 'reselect' import { transactionsSelector, @@ -105,50 +104,6 @@ function getCurrentAccountWithSendEtherInfo (state) { return accounts.find(({ address }) => address === currentAddress) } -// // function shapeShiftTxListSelector (state) { -// // return state.metamask.shapeShiftTxList || [] -// // } - -// const transactionsSelector = createSelector( -// selectedTokenAddressSelector, -// unapprovedMsgsSelector, -// shapeShiftTxListSelector, -// selectedAddressTxListSelector, -// (selectedTokenAddress, unapprovedMsgs = {}, shapeShiftTxList = [], transactions = []) => { -// const unapprovedMsgsList = valuesFor(unapprovedMsgs) -// const txsToRender = transactions.concat(unapprovedMsgsList, 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) -// } -// ) - -// // function transactionsSelector (state) { -// // const { selectedTokenAddress } = state.metamask -// // const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs) -// // const shapeShiftTxList = shapeShiftTxListSelector(state) -// // const transactions = state.metamask.selectedAddressTxList || [] -// // const txsToRender = 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) -// // } - -export const pendingTransactionsSelector = createSelector( - transactionsSelector, - transactions => { - - } -) - function getGasIsLoading (state) { return state.appState.gasIsLoading } diff --git a/ui/app/selectors/tokens.js b/ui/app/selectors/tokens.js new file mode 100644 index 000000000..47b6e0192 --- /dev/null +++ b/ui/app/selectors/tokens.js @@ -0,0 +1,11 @@ +import { createSelector } from 'reselect' + +export const selectedTokenAddressSelector = state => state.metamask.selectedTokenAddress +export const tokenSelector = state => state.metamask.tokens +export const selectedTokenSelector = createSelector( + tokenSelector, + selectedTokenAddressSelector, + (tokens = [], selectedTokenAddress = '') => { + return tokens.find(({ address }) => address === selectedTokenAddress) + } +) diff --git a/ui/app/selectors/transactions.js b/ui/app/selectors/transactions.js index a265b8e70..6fedf36be 100644 --- a/ui/app/selectors/transactions.js +++ b/ui/app/selectors/transactions.js @@ -6,8 +6,9 @@ import { SUBMITTED_STATUS, } from '../constants/transactions' +import { selectedTokenAddressSelector } from './tokens' + export const shapeShiftTxListSelector = state => state.metamask.shapeShiftTxList -export const selectedTokenAddressSelector = state => state.metamask.selectedTokenAddress export const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs export const selectedAddressTxListSelector = state => state.metamask.selectedAddressTxList -- cgit v1.2.3 From e104744d3b29af518bc60c45339d4d4458baaa68 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 6 Aug 2018 13:45:56 -0700 Subject: Fix shapeshift transactions. Delete unused files --- ui/app/components/pages/home/home.component.js | 2 - .../components/token-view/token-view.component.js | 1 - ui/app/components/transaction-list/index.scss | 6 + .../transaction-list/transaction-list.component.js | 44 +-- ui/app/components/tx-list-item.js | 356 --------------------- ui/app/components/tx-list.js | 171 ---------- ui/app/components/tx-view.js | 121 ------- ui/app/constants/transactions.js | 2 + ui/app/css/itcss/components/newui-sections.scss | 20 -- 9 files changed, 34 insertions(+), 689 deletions(-) delete mode 100644 ui/app/components/tx-list-item.js delete mode 100644 ui/app/components/tx-list.js delete mode 100644 ui/app/components/tx-view.js (limited to 'ui') diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js index dae9790de..65273ec8e 100644 --- a/ui/app/components/pages/home/home.component.js +++ b/ui/app/components/pages/home/home.component.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types' import Media from 'react-media' import { Redirect } from 'react-router-dom' import WalletView from '../../wallet-view' -import TxView from '../../tx-view' import TokenView from '../../token-view' import { INITIALIZE_BACKUP_PHRASE_ROUTE, @@ -60,7 +59,6 @@ export default class Home extends PureComponent { render={() => } /> - {/* */}
    ) diff --git a/ui/app/components/token-view/token-view.component.js b/ui/app/components/token-view/token-view.component.js index 3e1a4a0c3..2267047af 100644 --- a/ui/app/components/token-view/token-view.component.js +++ b/ui/app/components/token-view/token-view.component.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types' import Media from 'react-media' import MenuBar from '../menu-bar' import TokenViewBalance from '../token-view-balance' -// import TransactionList from '../tx-list' import TransactionList from '../transaction-list' export default class TokenView extends PureComponent { diff --git a/ui/app/components/transaction-list/index.scss b/ui/app/components/transaction-list/index.scss index f6f209831..0e8db485c 100644 --- a/ui/app/components/transaction-list/index.scss +++ b/ui/app/components/transaction-list/index.scss @@ -4,6 +4,12 @@ flex: 1; overflow-y: hidden; + &__completed-transactions { + display: flex; + flex-direction: column; + height: 100%; + } + &__header { flex: 0 0 auto; font-size: .875rem; diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/transaction-list/transaction-list.component.js index 953b07217..c1e1885f3 100644 --- a/ui/app/components/transaction-list/transaction-list.component.js +++ b/ui/app/components/transaction-list/transaction-list.component.js @@ -1,6 +1,8 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import TransactionListItem from '../transaction-list-item' +import ShapeShiftTransactionListItem from '../shift-list-item' +import { TRANSACTION_TYPE_SHAPESHIFT } from '../../constants/transactions' export default class TransactionList extends PureComponent { static contextTypes = { @@ -28,11 +30,7 @@ export default class TransactionList extends PureComponent { renderTransactions () { const { t } = this.context - const { - pendingTransactions = [], - completedTransactions = [], - selectedToken, - } = this.props + const { pendingTransactions = [], completedTransactions = [] } = this.props return (
    @@ -43,13 +41,8 @@ export default class TransactionList extends PureComponent { { `${t('queue')} (${pendingTransactions.length})` }
    { - pendingTransactions.map(transaction => ( - + pendingTransactions.map((transaction, index) => ( + this.renderTransaction(transaction, index) )) }
    @@ -61,12 +54,8 @@ export default class TransactionList extends PureComponent {
    { completedTransactions.length > 0 - ? completedTransactions.map(transaction => ( - + ? completedTransactions.map((transaction, index) => ( + this.renderTransaction(transaction, index) )) : this.renderEmpty() } @@ -75,6 +64,25 @@ export default class TransactionList extends PureComponent { ) } + renderTransaction (transaction, index) { + const { selectedToken } = this.props + + return transaction.key === TRANSACTION_TYPE_SHAPESHIFT + ? ( + + ) : ( + + ) + } + renderEmpty () { return (
    diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js deleted file mode 100644 index 474d62638..000000000 --- a/ui/app/components/tx-list-item.js +++ /dev/null @@ -1,356 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const { compose } = require('recompose') -const { withRouter } = require('react-router-dom') -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const inherits = require('util').inherits -const classnames = require('classnames') -const abi = require('human-standard-token-abi') -const abiDecoder = require('abi-decoder') -abiDecoder.addABI(abi) -const Identicon = require('./identicon') -const contractMap = require('eth-contract-metadata') -const { checksumAddress } = require('../util') - -const actions = require('../actions') -const { conversionUtil, multiplyCurrencies } = require('../conversion-util') -const { calcTokenAmount } = require('../token-util') - -const { getCurrentCurrency } = require('../selectors') -const { CONFIRM_TRANSACTION_ROUTE } = require('../routes') - -TxListItem.contextTypes = { - t: PropTypes.func, -} - -module.exports = compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(TxListItem) - -function mapStateToProps (state) { - return { - tokens: state.metamask.tokens, - currentCurrency: getCurrentCurrency(state), - contractExchangeRates: state.metamask.contractExchangeRates, - selectedAddressTxList: state.metamask.selectedAddressTxList, - networkNonce: state.appState.networkNonce, - } -} - -function mapDispatchToProps (dispatch) { - return { - setSelectedToken: tokenAddress => dispatch(actions.setSelectedToken(tokenAddress)), - retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)), - } -} - -inherits(TxListItem, Component) -function TxListItem () { - Component.call(this) - - this.state = { - total: null, - fiatTotal: null, - isTokenTx: null, - } - - this.unmounted = false -} - -TxListItem.prototype.componentDidMount = async function () { - const { txParams = {} } = this.props - - const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data) - const { name: txDataName } = decodedData || {} - const isTokenTx = txDataName === 'transfer' - - const { total, fiatTotal } = isTokenTx - ? await this.getSendTokenTotal() - : this.getSendEtherTotal() - - if (this.unmounted) { - return - } - this.setState({ total, fiatTotal, isTokenTx }) -} - -TxListItem.prototype.componentWillUnmount = function () { - this.unmounted = true -} - -TxListItem.prototype.getAddressText = function () { - const { - address, - txParams = {}, - isMsg, - } = this.props - - const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data) - const { name: txDataName, params = [] } = decodedData || {} - const { value } = params[0] || {} - const checksummedAddress = checksumAddress(address) - const checksummedValue = checksumAddress(value) - - let addressText - if (txDataName === 'transfer' || address) { - const addressToRender = txDataName === 'transfer' ? checksummedValue : checksummedAddress - addressText = `${addressToRender.slice(0, 10)}...${addressToRender.slice(-4)}` - } else if (isMsg) { - addressText = this.context.t('sigRequest') - } else { - addressText = this.context.t('contractDeployment') - } - - return addressText -} - -TxListItem.prototype.getSendEtherTotal = function () { - const { - transactionAmount, - conversionRate, - address, - currentCurrency, - } = this.props - - if (!address) { - return {} - } - - const totalInFiat = conversionUtil(transactionAmount, { - fromNumericBase: 'hex', - toNumericBase: 'dec', - fromCurrency: 'ETH', - toCurrency: currentCurrency, - fromDenomination: 'WEI', - numberOfDecimals: 2, - conversionRate, - }) - const totalInETH = conversionUtil(transactionAmount, { - fromNumericBase: 'hex', - toNumericBase: 'dec', - fromCurrency: 'ETH', - toCurrency: 'ETH', - fromDenomination: 'WEI', - conversionRate, - numberOfDecimals: 6, - }) - - return { - total: `${totalInETH} ETH`, - fiatTotal: `${totalInFiat} ${currentCurrency.toUpperCase()}`, - } -} - -TxListItem.prototype.getTokenInfo = async function () { - const { txParams = {}, tokenInfoGetter, tokens } = this.props - const toAddress = txParams.to - - let decimals - let symbol - - ({ decimals, symbol } = tokens.filter(({ address }) => address === toAddress)[0] || {}) - - if (!decimals && !symbol) { - ({ decimals, symbol } = contractMap[toAddress] || {}) - } - - if (!decimals && !symbol) { - ({ decimals, symbol } = await tokenInfoGetter(toAddress)) - } - - return { decimals, symbol, address: toAddress } -} - -TxListItem.prototype.getSendTokenTotal = async function () { - const { - txParams = {}, - conversionRate, - contractExchangeRates, - currentCurrency, - } = this.props - - const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data) - const { params = [] } = decodedData || {} - const { value } = params[1] || {} - const { decimals, symbol, address } = await this.getTokenInfo() - const total = calcTokenAmount(value, decimals) - - let tokenToFiatRate - let totalInFiat - - if (contractExchangeRates[address]) { - tokenToFiatRate = multiplyCurrencies( - contractExchangeRates[address], - conversionRate - ) - - totalInFiat = conversionUtil(total, { - fromNumericBase: 'dec', - toNumericBase: 'dec', - fromCurrency: symbol, - toCurrency: currentCurrency, - numberOfDecimals: 2, - conversionRate: tokenToFiatRate, - }) - } - - const showFiat = Boolean(totalInFiat) && currentCurrency.toUpperCase() !== symbol - - return { - total: `${total} ${symbol}`, - fiatTotal: showFiat && `${totalInFiat} ${currentCurrency.toUpperCase()}`, - } -} - -TxListItem.prototype.showRetryButton = function () { - const { - transactionSubmittedTime, - selectedAddressTxList, - transactionId, - txParams, - networkNonce, - } = this.props - if (!txParams) { - return false - } - let currentTxSharesEarliestNonce = false - const currentNonce = txParams.nonce - const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce) - const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') - const currentSubmittedTxs = selectedAddressTxList.filter(tx => tx.status === 'submitted') - const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1] - const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce && - lastSubmittedTxWithCurrentNonce.id === transactionId - if (currentSubmittedTxs.length > 0) { - currentTxSharesEarliestNonce = currentNonce === networkNonce - } - - return currentTxSharesEarliestNonce && currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 -} - -TxListItem.prototype.setSelectedToken = function (tokenAddress) { - this.props.setSelectedToken(tokenAddress) -} - -TxListItem.prototype.resubmit = function () { - const { transactionId } = this.props - this.props.retryTransaction(transactionId) - .then(id => this.props.history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)) -} - -TxListItem.prototype.render = function () { - const { - transactionStatus, - onClick, - transactionId, - dateString, - address, - className, - txParams, - } = this.props - const { total, fiatTotal, isTokenTx } = this.state - - return h(`div${className || ''}`, { - key: transactionId, - onClick: () => onClick && onClick(transactionId), - }, [ - h(`div.flex-column.tx-list-item-wrapper`, {}, [ - - h('div.tx-list-date-wrapper', { - style: {}, - }, [ - h('span.tx-list-date', {}, [ - dateString, - ]), - ]), - - h('div.flex-row.tx-list-content-wrapper', { - style: {}, - }, [ - - h('div.tx-list-identicon-wrapper', { - style: {}, - }, [ - h(Identicon, { - address, - diameter: 28, - }), - ]), - - h('div.tx-list-account-and-status-wrapper', {}, [ - h('div.tx-list-account-wrapper', { - style: {}, - }, [ - h('span.tx-list-account', {}, [ - this.getAddressText(address), - ]), - ]), - - h('div.tx-list-status-wrapper', { - style: {}, - }, [ - h('span', { - className: classnames('tx-list-status', { - 'tx-list-status--rejected': transactionStatus === 'rejected', - 'tx-list-status--failed': transactionStatus === 'failed', - 'tx-list-status--dropped': transactionStatus === 'dropped', - }), - }, - this.txStatusIndicator(), - ), - ]), - ]), - - h('div.flex-column.tx-list-details-wrapper', { - style: {}, - }, [ - - h('span.tx-list-value', total), - - fiatTotal && h('span.tx-list-fiat-value', fiatTotal), - - ]), - ]), - - this.showRetryButton() && h('.tx-list-item-retry-container', { - onClick: (event) => { - event.stopPropagation() - if (isTokenTx) { - this.setSelectedToken(txParams.to) - } - this.resubmit() - }, - }, [ - h('span', 'Taking too long? Increase the gas price on your transaction'), - ]), - - ]), // holding on icon from design - ]) -} - -TxListItem.prototype.txStatusIndicator = function () { - const { transactionStatus } = this.props - - let name - - if (transactionStatus === 'unapproved') { - name = this.context.t('unapproved') - } else if (transactionStatus === 'rejected') { - name = this.context.t('rejected') - } else if (transactionStatus === 'approved') { - name = this.context.t('approved') - } else if (transactionStatus === 'signed') { - name = this.context.t('signed') - } else if (transactionStatus === 'submitted') { - name = this.context.t('submitted') - } else if (transactionStatus === 'confirmed') { - name = this.context.t('confirmed') - } else if (transactionStatus === 'failed') { - name = this.context.t('failed') - } else if (transactionStatus === 'dropped') { - name = this.context.t('dropped') - } - return name -} diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js deleted file mode 100644 index d8c4a9d19..000000000 --- a/ui/app/components/tx-list.js +++ /dev/null @@ -1,171 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const connect = require('react-redux').connect -const h = require('react-hyperscript') -const inherits = require('util').inherits -const prefixForNetwork = require('../../lib/etherscan-prefix-for-network') -const selectors = require('../selectors') -const TxListItem = require('./tx-list-item') -const ShiftListItem = require('./shift-list-item') -const { formatDate } = require('../util') -const { showConfTxPage, updateNetworkNonce } = require('../actions') -const classnames = require('classnames') -const { tokenInfoGetter } = require('../token-util') -const { withRouter } = require('react-router-dom') -const { compose } = require('recompose') -const { CONFIRM_TRANSACTION_ROUTE } = require('../routes') - -module.exports = compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(TxList) - -TxList.contextTypes = { - t: PropTypes.func, -} - -function mapStateToProps (state) { - return { - txsToRender: selectors.transactionsSelector(state), - conversionRate: selectors.conversionRateSelector(state), - selectedAddress: selectors.getSelectedAddress(state), - } -} - -function mapDispatchToProps (dispatch) { - return { - showConfTxPage: ({ id }) => dispatch(showConfTxPage({ id })), - updateNetworkNonce: (address) => dispatch(updateNetworkNonce(address)), - } -} - -inherits(TxList, Component) -function TxList () { - Component.call(this) -} - -TxList.prototype.componentWillMount = function () { - this.tokenInfoGetter = tokenInfoGetter() - this.props.updateNetworkNonce(this.props.selectedAddress) -} - -TxList.prototype.componentDidUpdate = function (prevProps) { - const oldTxsToRender = prevProps.txsToRender - const { - txsToRender: newTxsToRender, - selectedAddress, - updateNetworkNonce, - } = this.props - - if (newTxsToRender.length > oldTxsToRender.length) { - updateNetworkNonce(selectedAddress) - } -} - -TxList.prototype.render = function () { - return h('div.flex-column', [ - h('div.flex-row.tx-list-header-wrapper', [ - h('div.flex-row.tx-list-header', [ - h('div', this.context.t('transactions')), - ]), - ]), - h('div.flex-column.tx-list-container', {}, [ - this.renderTransaction(), - ]), - ]) -} - -TxList.prototype.renderTransaction = function () { - const { txsToRender, conversionRate } = this.props - - return txsToRender.length - ? txsToRender.map((transaction, i) => this.renderTransactionListItem(transaction, conversionRate, i)) - : [h( - 'div.tx-list-item.tx-list-item--empty', - { key: 'tx-list-none' }, - [ this.context.t('noTransactions') ], - )] -} - -// TODO: Consider moving TxListItem into a separate component -TxList.prototype.renderTransactionListItem = function (transaction, conversionRate, index) { - // console.log({transaction}) - // refer to transaction-list.js:line 58 - - if (transaction.key === 'shapeshift') { - return h(ShiftListItem, { ...transaction, key: `shapeshift${index}` }) - } - - const props = { - dateString: formatDate(transaction.time), - address: transaction.txParams && transaction.txParams.to, - transactionStatus: transaction.status, - transactionAmount: transaction.txParams && transaction.txParams.value, - transactionId: transaction.id, - transactionHash: transaction.hash, - transactionNetworkId: transaction.metamaskNetworkId, - transactionSubmittedTime: transaction.submittedTime, - } - - const { - address, - transactionStatus, - transactionAmount, - dateString, - transactionId, - transactionHash, - transactionNetworkId, - transactionSubmittedTime, - } = props - const { history } = this.props - - const opts = { - key: transactionId || transactionHash, - txParams: transaction.txParams, - isMsg: Boolean(transaction.msgParams), - transactionStatus, - transactionId, - dateString, - address, - transactionAmount, - transactionHash, - conversionRate, - tokenInfoGetter: this.tokenInfoGetter, - transactionSubmittedTime, - } - - const isUnapproved = transactionStatus === 'unapproved' - - if (isUnapproved) { - opts.onClick = () => { - this.props.showConfTxPage({ id: transactionId }) - history.push(CONFIRM_TRANSACTION_ROUTE) - } - opts.transactionStatus = this.context.t('notStarted') - } else if (transactionHash) { - opts.onClick = () => this.view(transactionHash, transactionNetworkId) - } - - opts.className = classnames('.tx-list-item', { - '.tx-list-pending-item-container': isUnapproved, - '.tx-list-clickable': Boolean(transactionHash) || isUnapproved, - }) - - return h(TxListItem, opts) -} - -TxList.prototype.view = function (txHash, network) { - const url = etherscanLinkFor(txHash, network) - if (url) { - navigateTo(url) - } -} - -function navigateTo (url) { - global.platform.openWindow({ url }) -} - -function etherscanLinkFor (txHash, network) { - const prefix = prefixForNetwork(network) - return `https://${prefix}etherscan.io/tx/${txHash}` -} diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js deleted file mode 100644 index aa540249f..000000000 --- a/ui/app/components/tx-view.js +++ /dev/null @@ -1,121 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const connect = require('react-redux').connect -const h = require('react-hyperscript') -const inherits = require('util').inherits -const { withRouter } = require('react-router-dom') -const { compose } = require('recompose') -const actions = require('../actions') -const selectors = require('../selectors') -const { SEND_ROUTE } = require('../routes') -const { checksumAddress: toChecksumAddress } = require('../util') - -const BalanceComponent = require('./balance-component') -const Tooltip = require('./tooltip') -const TxList = require('./tx-list') -const SelectedAccount = require('./selected-account') - -import Media from 'react-media' -import MenuBar from './menu-bar' - -module.exports = compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(TxView) - -TxView.contextTypes = { - t: PropTypes.func, -} - -function mapStateToProps (state) { - const sidebarOpen = state.appState.sidebarOpen - const isMascara = state.appState.isMascara - - const identities = state.metamask.identities - const accounts = state.metamask.accounts - const network = state.metamask.network - const selectedTokenAddress = state.metamask.selectedTokenAddress - const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] - const checksumAddress = toChecksumAddress(selectedAddress) - const identity = identities[selectedAddress] - - return { - sidebarOpen, - selectedAddress, - checksumAddress, - selectedTokenAddress, - selectedToken: selectors.getSelectedToken(state), - identity, - network, - isMascara, - } -} - -function mapDispatchToProps (dispatch) { - return { - showSidebar: () => { dispatch(actions.showSidebar()) }, - hideSidebar: () => { dispatch(actions.hideSidebar()) }, - showModal: (payload) => { dispatch(actions.showModal(payload)) }, - showSendPage: () => { dispatch(actions.showSendPage()) }, - showSendTokenPage: () => { dispatch(actions.showSendTokenPage()) }, - } -} - -inherits(TxView, Component) -function TxView () { - Component.call(this) -} - -TxView.prototype.renderHeroBalance = function () { - const { selectedToken } = this.props - - return h('div.hero-balance', {}, [ - - h(BalanceComponent, { token: selectedToken }), - - this.renderButtons(), - ]) -} - -TxView.prototype.renderButtons = function () { - const {selectedToken, showModal, history } = this.props - - return !selectedToken - ? ( - h('div.flex-row.flex-center.hero-balance-buttons', [ - h('button.btn-primary.hero-balance-button', { - onClick: () => showModal({ - name: 'DEPOSIT_ETHER', - }), - }, this.context.t('deposit')), - - h('button.btn-primary.hero-balance-button', { - style: { - marginLeft: '0.8em', - }, - onClick: () => history.push(SEND_ROUTE), - }, this.context.t('send')), - ]) - ) - : ( - h('div.flex-row.flex-center.hero-balance-buttons', [ - h('button.btn-primary.hero-balance-button', { - onClick: () => history.push(SEND_ROUTE), - }, this.context.t('send')), - ]) - ) -} - -TxView.prototype.render = function () { - return h('div.tx-view.flex-column', [ - h(Media, { - query: '(max-width: 575px)', - render: () => h(MenuBar), - }), - - this.renderHeroBalance(), - - h(TxList), - - ]) -} diff --git a/ui/app/constants/transactions.js b/ui/app/constants/transactions.js index a9ab1a0d6..5f8935a64 100644 --- a/ui/app/constants/transactions.js +++ b/ui/app/constants/transactions.js @@ -16,3 +16,5 @@ export const DEPLOY_CONTRACT_ACTION_KEY = 'contractDeployment' export const APPROVE_ACTION_KEY = 'approve' export const SEND_TOKEN_ACTION_KEY = 'outgoing' export const TRANSFER_FROM_ACTION_KEY = 'transferFrom' + +export const TRANSACTION_TYPE_SHAPESHIFT = 'shapeshift' diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index 7ad5cd076..29dd18ae3 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -6,7 +6,6 @@ $sub-mid-size-breakpoint-range: "screen and (min-width: #{$break-large}) and (ma */ // Component Colors -$tx-view-bg: $white; $wallet-view-bg: $alabaster; // Main container @@ -30,25 +29,6 @@ $wallet-view-bg: $alabaster; min-width: 0; } -// tx view - -.tx-view { - flex: 1 1 66.5%; - background: $tx-view-bg; - min-width: 0; - - // No title on mobile - @media screen and (max-width: 575px) { - .identicon-wrapper { - display: none; - } - - .account-name { - display: none; - } - } -} - // wallet view and sidebar .wallet-view { -- cgit v1.2.3 From c9ec5ed38d0e2475778ca6eb18d29b3389f67190 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 6 Aug 2018 14:08:00 -0700 Subject: Fix e2e tests --- ui/app/css/itcss/components/transaction-list.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/css/itcss/components/transaction-list.scss b/ui/app/css/itcss/components/transaction-list.scss index 1d45ff13b..3435353d9 100644 --- a/ui/app/css/itcss/components/transaction-list.scss +++ b/ui/app/css/itcss/components/transaction-list.scss @@ -243,7 +243,7 @@ } .tx-list-item { - border-top: 1px solid rgb(231, 231, 231); + border-bottom: 1px solid $geyser; flex: 0 0 auto; display: flex; flex-flow: row nowrap; -- cgit v1.2.3 From 9adf0c4b60c863a820af7b20ff66a8b29f7bdbe7 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 6 Aug 2018 22:39:54 -0700 Subject: Fix integration tests --- ui/app/components/shift-list-item.js | 3 ++- .../transaction-list-item/transaction-list-item.component.js | 5 ++++- ui/app/components/transaction-list/transaction-list.container.js | 4 +++- ui/app/components/transaction-status/index.scss | 2 +- ui/app/constants/transactions.js | 1 + ui/app/helpers/transactions.util.js | 7 ++++++- ui/app/selectors/transactions.js | 7 +++++++ 7 files changed, 24 insertions(+), 5 deletions(-) (limited to 'ui') diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index 4334aacba..b87bf959e 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -35,12 +35,13 @@ function ShiftListItem () { } ShiftListItem.prototype.render = function () { - return h('div.tx-list-item.tx-list-clickable', { + return h('div.transaction-list-item.tx-list-clickable', { style: { paddingTop: '20px', paddingBottom: '20px', justifyContent: 'space-around', alignItems: 'center', + flexDirection: 'row', }, }, [ h('div', { diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index d4b1bec94..6e1abd0e4 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -67,7 +67,10 @@ export default class TransactionListItem extends PureComponent { const { txParams = {} } = transaction const nonce = hexToDecimal(txParams.nonce) - const nonceAndDateText = `#${nonce} - ${formatDate(transaction.time)}` + const nonceAndDateText = nonce + ? `#${nonce} - ${formatDate(transaction.time)}` + : formatDate(transaction.time) + const fiatDisplayText = `-${fiatDisplayValue}` const ethDisplayText = ethTransactionAmount && `-${ethTransactionAmount} ETH` diff --git a/ui/app/components/transaction-list/transaction-list.container.js b/ui/app/components/transaction-list/transaction-list.container.js index 5a63d916d..e1d19e787 100644 --- a/ui/app/components/transaction-list/transaction-list.container.js +++ b/ui/app/components/transaction-list/transaction-list.container.js @@ -4,6 +4,7 @@ import { compose } from 'recompose' import TransactionList from './transaction-list.component' import { pendingTransactionsSelector, + submittedPendingTransactionsSelector, completedTransactionsSelector, } from '../../selectors/transactions' import { selectedTokenSelector } from '../../selectors/tokens' @@ -11,11 +12,12 @@ import { getLatestSubmittedTxWithEarliestNonce } from '../../helpers/transaction const mapStateToProps = state => { const pendingTransactions = pendingTransactionsSelector(state) + const submittedPendingTransactions = submittedPendingTransactionsSelector(state) return { completedTransactions: completedTransactionsSelector(state), pendingTransactions, - transactionToRetry: getLatestSubmittedTxWithEarliestNonce(pendingTransactions), + transactionToRetry: getLatestSubmittedTxWithEarliestNonce(submittedPendingTransactions), selectedToken: selectedTokenSelector(state), } } diff --git a/ui/app/components/transaction-status/index.scss b/ui/app/components/transaction-status/index.scss index 95d29f6d3..35be550f7 100644 --- a/ui/app/components/transaction-status/index.scss +++ b/ui/app/components/transaction-status/index.scss @@ -21,7 +21,7 @@ color: #609a1c; } - &--approved { + &--approved, &--submitted { background-color: #FFF2DB; color: #CA810A; } diff --git a/ui/app/constants/transactions.js b/ui/app/constants/transactions.js index 5f8935a64..1dc8944d3 100644 --- a/ui/app/constants/transactions.js +++ b/ui/app/constants/transactions.js @@ -16,5 +16,6 @@ export const DEPLOY_CONTRACT_ACTION_KEY = 'contractDeployment' export const APPROVE_ACTION_KEY = 'approve' export const SEND_TOKEN_ACTION_KEY = 'outgoing' export const TRANSFER_FROM_ACTION_KEY = 'transferFrom' +export const SIGNATURE_REQUEST_KEY = 'signatureRequest' export const TRANSACTION_TYPE_SHAPESHIFT = 'shapeshift' diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js index 89d2649c9..8ded31bca 100644 --- a/ui/app/helpers/transactions.util.js +++ b/ui/app/helpers/transactions.util.js @@ -13,6 +13,7 @@ import { APPROVE_ACTION_KEY, SEND_TOKEN_ACTION_KEY, TRANSFER_FROM_ACTION_KEY, + SIGNATURE_REQUEST_KEY, } from '../constants/transactions' abiDecoder.addABI(abi) @@ -41,7 +42,11 @@ export function isConfirmDeployContract (txData = {}) { } export function getTransactionActionKey (transaction, methodData) { - const { txParams: { data } = {} } = transaction + const { txParams: { data } = {}, msgParams } = transaction + + if (msgParams) { + return SIGNATURE_REQUEST_KEY + } if (isConfirmDeployContract(transaction)) { return DEPLOY_CONTRACT_ACTION_KEY diff --git a/ui/app/selectors/transactions.js b/ui/app/selectors/transactions.js index 6fedf36be..3e9843722 100644 --- a/ui/app/selectors/transactions.js +++ b/ui/app/selectors/transactions.js @@ -43,6 +43,13 @@ export const pendingTransactionsSelector = createSelector( ) ) +export const submittedPendingTransactionsSelector = createSelector( + transactionsSelector, + (transactions = []) => ( + transactions.filter(transaction => transaction.status === SUBMITTED_STATUS) + ) +) + export const completedTransactionsSelector = createSelector( transactionsSelector, (transactions = []) => ( -- cgit v1.2.3 From 5dcd8ceb7bbaef33fef5588feceac17577679e74 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 7 Aug 2018 01:57:46 -0700 Subject: Fix e2e tests --- .../transaction-action/transaction-action.component.js | 8 ++++---- .../transaction-list-item.component.js | 14 ++++---------- .../transaction-list-item.container.js | 17 ++++++++--------- ui/app/constants/transactions.js | 1 + ui/app/helpers/transactions.util.js | 5 +++++ .../with-method-data/with-method-data.component.js | 18 ++++++++---------- 6 files changed, 30 insertions(+), 33 deletions(-) (limited to 'ui') diff --git a/ui/app/components/transaction-action/transaction-action.component.js b/ui/app/components/transaction-action/transaction-action.component.js index b608615d0..9f447fe5c 100644 --- a/ui/app/components/transaction-action/transaction-action.component.js +++ b/ui/app/components/transaction-action/transaction-action.component.js @@ -28,9 +28,9 @@ export default class TransactionAction extends PureComponent { getTransactionAction () { const { transactionAction } = this.state const { transaction, methodData } = this.props - const { data, isFetching } = methodData + const { data, done } = methodData - if (isFetching || transactionAction) { + if (!done || transactionAction) { return } @@ -40,12 +40,12 @@ export default class TransactionAction extends PureComponent { } render () { - const { className } = this.props + const { className, methodData: { isFetching } } = this.props const { transactionAction } = this.state return (
    - { transactionAction || '--' } + { (!isFetching && transactionAction) || '--' }
    ) } diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index 6e1abd0e4..a47f29023 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -3,11 +3,9 @@ import PropTypes from 'prop-types' import Identicon from '../identicon' import TransactionStatus from '../transaction-status' import TransactionAction from '../transaction-action' -import { formatDate } from '../../util' import prefixForNetwork from '../../../lib/etherscan-prefix-for-network' import { CONFIRM_TRANSACTION_ROUTE } from '../../routes' import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions' -import { hexToDecimal } from '../../helpers/conversions.util' export default class TransactionListItem extends PureComponent { static propTypes = { @@ -19,6 +17,7 @@ export default class TransactionListItem extends PureComponent { showRetry: PropTypes.bool, retryTransaction: PropTypes.func, setSelectedToken: PropTypes.func, + nonceAndDate: PropTypes.string, } handleClick = () => { @@ -63,14 +62,9 @@ export default class TransactionListItem extends PureComponent { fiatDisplayValue, methodData, showRetry, + nonceAndDate, } = this.props const { txParams = {} } = transaction - const nonce = hexToDecimal(txParams.nonce) - - const nonceAndDateText = nonce - ? `#${nonce} - ${formatDate(transaction.time)}` - : formatDate(transaction.time) - const fiatDisplayText = `-${fiatDisplayValue}` const ethDisplayText = ethTransactionAmount && `-${ethTransactionAmount} ETH` @@ -92,9 +86,9 @@ export default class TransactionListItem extends PureComponent { />
    - { nonceAndDateText } + { nonceAndDate }
    { const { metamask } = state const { currentCurrency, conversionRate } = metamask - const { transaction: { txParams: { value, data } = {} } = {}, token } = ownProps + const { transaction: { txParams: { value, data, nonce } = {}, time } = {}, token } = ownProps let ethTransactionAmount, fiatDisplayValue @@ -22,13 +22,9 @@ const mapStateToProps = (state, ownProps) => { const tokenData = getTokenData(data) if (tokenData.params && tokenData.params.length === 2) { - const tokenDataName = tokenData.name || '' const tokenValue = tokenData.params[1].value - const tokenAmount = tokenDataName.toLowerCase() === TOKEN_METHOD_TRANSFER - ? calcTokenAmount(tokenValue, decimals) - : tokenValue - - fiatDisplayValue = `${tokenAmount} ${symbol}` + const tokenAmount = calcTokenAmount(tokenValue, decimals) + fiatDisplayValue = `${tokenAmount} ${symbol}` } } else { ethTransactionAmount = getEthFromWeiHex({ value, conversionRate }) @@ -39,9 +35,12 @@ const mapStateToProps = (state, ownProps) => { fiatDisplayValue = `${fiatFormattedAmount} ${currentCurrency.toUpperCase()}` } + const nonceAndDate = nonce ? `#${hexToDecimal(nonce)} - ${formatDate(time)}` : formatDate(time) + return { ethTransactionAmount, fiatDisplayValue, + nonceAndDate, } } diff --git a/ui/app/constants/transactions.js b/ui/app/constants/transactions.js index 1dc8944d3..da1d2e590 100644 --- a/ui/app/constants/transactions.js +++ b/ui/app/constants/transactions.js @@ -17,5 +17,6 @@ export const APPROVE_ACTION_KEY = 'approve' export const SEND_TOKEN_ACTION_KEY = 'outgoing' export const TRANSFER_FROM_ACTION_KEY = 'transferFrom' export const SIGNATURE_REQUEST_KEY = 'signatureRequest' +export const UNKNOWN_FUNCTION_KEY = 'unknownFunction' export const TRANSACTION_TYPE_SHAPESHIFT = 'shapeshift' diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js index 8ded31bca..e890a0852 100644 --- a/ui/app/helpers/transactions.util.js +++ b/ui/app/helpers/transactions.util.js @@ -14,6 +14,7 @@ import { SEND_TOKEN_ACTION_KEY, TRANSFER_FROM_ACTION_KEY, SIGNATURE_REQUEST_KEY, + UNKNOWN_FUNCTION_KEY, } from '../constants/transactions' abiDecoder.addABI(abi) @@ -56,6 +57,10 @@ export function getTransactionActionKey (transaction, methodData) { const { name } = methodData const methodName = name && name.toLowerCase() + if (!methodName) { + return UNKNOWN_FUNCTION_KEY + } + switch (methodName) { case TOKEN_METHOD_TRANSFER: return SEND_TOKEN_ACTION_KEY diff --git a/ui/app/higher-order-components/with-method-data/with-method-data.component.js b/ui/app/higher-order-components/with-method-data/with-method-data.component.js index c05d33c20..fed7d9865 100644 --- a/ui/app/higher-order-components/with-method-data/with-method-data.component.js +++ b/ui/app/higher-order-components/with-method-data/with-method-data.component.js @@ -13,10 +13,8 @@ export default function withMethodData (WrappedComponent) { } state = { - methodData: { - data: {}, - }, - isFetching: false, + methodData: {}, + done: false, error: null, } @@ -29,24 +27,24 @@ export default function withMethodData (WrappedComponent) { const { txParams: { data = '' } = {} } = transaction if (data) { - this.setState({ isFetching: true }) - try { const methodData = await getMethodData(data) - this.setState({ methodData, isFetching: false }) + this.setState({ methodData, done: true }) } catch (error) { - this.setState({ isFetching: false, error }) + this.setState({ done: true, error }) } + } else { + this.setState({ done: true }) } } render () { - const { methodData, isFetching, error } = this.state + const { methodData, done, error } = this.state return ( ) } -- cgit v1.2.3 From b48a293af059d2ad23fea0af601740888acd3f8b Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sat, 11 Aug 2018 23:12:30 -0500 Subject: Update retry transaction logic to use network nonce --- .../transaction-list/transaction-list.component.js | 14 +++++++++++ .../transaction-list/transaction-list.container.js | 28 +++++++++++++++++++--- ui/app/helpers/transactions.util.js | 16 ++++++------- 3 files changed, 46 insertions(+), 12 deletions(-) (limited to 'ui') diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/transaction-list/transaction-list.component.js index c1e1885f3..e30476d8c 100644 --- a/ui/app/components/transaction-list/transaction-list.component.js +++ b/ui/app/components/transaction-list/transaction-list.component.js @@ -20,6 +20,20 @@ export default class TransactionList extends PureComponent { completedTransactions: PropTypes.array, transactionToRetry: PropTypes.object, selectedToken: PropTypes.object, + updateNetworkNonce: PropTypes.func, + } + + componentDidMount () { + this.props.updateNetworkNonce() + } + + componentDidUpdate (prevProps) { + const { pendingTransactions: prevPendingTransactions = [] } = prevProps + const { pendingTransactions = [], updateNetworkNonce } = this.props + + if (pendingTransactions.length > prevPendingTransactions.length) { + updateNetworkNonce() + } } shouldShowRetry = transaction => { diff --git a/ui/app/components/transaction-list/transaction-list.container.js b/ui/app/components/transaction-list/transaction-list.container.js index e1d19e787..1ec1f9ccf 100644 --- a/ui/app/components/transaction-list/transaction-list.container.js +++ b/ui/app/components/transaction-list/transaction-list.container.js @@ -7,22 +7,44 @@ import { submittedPendingTransactionsSelector, completedTransactionsSelector, } from '../../selectors/transactions' +import { getSelectedAddress } from '../../selectors' import { selectedTokenSelector } from '../../selectors/tokens' -import { getLatestSubmittedTxWithEarliestNonce } from '../../helpers/transactions.util' +import { getLatestSubmittedTxWithNonce } from '../../helpers/transactions.util' +import { updateNetworkNonce } from '../../actions' const mapStateToProps = state => { const pendingTransactions = pendingTransactionsSelector(state) const submittedPendingTransactions = submittedPendingTransactionsSelector(state) + const networkNonce = state.appState.networkNonce return { completedTransactions: completedTransactionsSelector(state), pendingTransactions, - transactionToRetry: getLatestSubmittedTxWithEarliestNonce(submittedPendingTransactions), + transactionToRetry: getLatestSubmittedTxWithNonce(submittedPendingTransactions, networkNonce), selectedToken: selectedTokenSelector(state), + selectedAddress: getSelectedAddress(state), + } +} + +const mapDispatchToProps = dispatch => { + return { + updateNetworkNonce: address => dispatch(updateNetworkNonce(address)), + } +} + +const mergeProps = (stateProps, dispatchProps, ownProps) => { + const { selectedAddress, ...restStateProps } = stateProps + const { updateNetworkNonce, ...restDispatchProps } = dispatchProps + + return { + ...restStateProps, + ...restDispatchProps, + ...ownProps, + updateNetworkNonce: () => updateNetworkNonce(selectedAddress), } } export default compose( withRouter, - connect(mapStateToProps) + connect(mapStateToProps, mapDispatchToProps, mergeProps) )(TransactionList) diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js index e890a0852..e92a22e16 100644 --- a/ui/app/helpers/transactions.util.js +++ b/ui/app/helpers/transactions.util.js @@ -2,7 +2,6 @@ import ethUtil from 'ethereumjs-util' import MethodRegistry from 'eth-method-registry' import abi from 'human-standard-token-abi' import abiDecoder from 'abi-decoder' -import { hexToDecimal } from './conversions.util' import { TOKEN_METHOD_TRANSFER, @@ -76,21 +75,20 @@ export function getTransactionActionKey (transaction, methodData) { } } -export function getLatestSubmittedTxWithEarliestNonce (transactions = []) { +export function getLatestSubmittedTxWithNonce (transactions = [], nonce = '0x0') { if (!transactions.length) { return {} } return transactions.reduce((acc, current) => { - const accNonce = hexToDecimal(acc.nonce) - const currentNonce = hexToDecimal(current.nonce) + const { submittedTime, txParams: { nonce: currentNonce } = {} } = current - if (currentNonce < accNonce) { - return current - } else if (currentNonce === accNonce) { - return current.submittedTime > acc.submittedTime ? current : acc + if (currentNonce === nonce) { + return acc.submittedTime + ? submittedTime > acc.submittedTime ? current : acc + : current } else { return acc } - }) + }, {}) } -- cgit v1.2.3 From da0df790472c816d933e9b697e4dab1a429740a3 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 15 Aug 2018 19:18:01 -0700 Subject: Add CurrencyDisplay and TokenCurrencyDisplay components --- .../currency-display/currency-display.component.js | 24 +++++ .../currency-display/currency-display.container.js | 20 ++++ ui/app/components/currency-display/index.js | 1 + .../tests/currency-display.component.test.js | 27 ++++++ .../tests/currency-display.container.test.js | 61 ++++++++++++ ui/app/components/token-currency-display/index.js | 1 + .../token-currency-display.component.js | 54 +++++++++++ .../tests/token-view-balance.component.test.js | 71 ++++++++++++++ .../token-view-balance.component.js | 26 +++--- .../token-view-balance.container.js | 18 +--- .../tests/transaction-action.component.test.js | 102 +++++++++++++++++++++ .../transaction-list-item.component.js | 61 ++++++++---- .../transaction-list-item.container.js | 32 +------ ui/app/constants/common.js | 1 + 14 files changed, 425 insertions(+), 74 deletions(-) create mode 100644 ui/app/components/currency-display/currency-display.component.js create mode 100644 ui/app/components/currency-display/currency-display.container.js create mode 100644 ui/app/components/currency-display/index.js create mode 100644 ui/app/components/currency-display/tests/currency-display.component.test.js create mode 100644 ui/app/components/currency-display/tests/currency-display.container.test.js create mode 100644 ui/app/components/token-currency-display/index.js create mode 100644 ui/app/components/token-currency-display/token-currency-display.component.js create mode 100644 ui/app/components/token-view-balance/tests/token-view-balance.component.test.js create mode 100644 ui/app/components/transaction-action/tests/transaction-action.component.test.js create mode 100644 ui/app/constants/common.js (limited to 'ui') diff --git a/ui/app/components/currency-display/currency-display.component.js b/ui/app/components/currency-display/currency-display.component.js new file mode 100644 index 000000000..f1bb933d7 --- /dev/null +++ b/ui/app/components/currency-display/currency-display.component.js @@ -0,0 +1,24 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' + +export default class CurrencyDisplay extends PureComponent { + static propTypes = { + className: PropTypes.string, + displayValue: PropTypes.string, + prefix: PropTypes.string, + } + + render () { + const { className, displayValue, prefix } = this.props + const text = `${prefix || ''}${displayValue}` + + return ( +
    + { text } +
    + ) + } +} diff --git a/ui/app/components/currency-display/currency-display.container.js b/ui/app/components/currency-display/currency-display.container.js new file mode 100644 index 000000000..b36bba52a --- /dev/null +++ b/ui/app/components/currency-display/currency-display.container.js @@ -0,0 +1,20 @@ +import { connect } from 'react-redux' +import CurrencyDisplay from './currency-display.component' +import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util' +import { ETH } from '../../constants/common' + +const mapStateToProps = (state, ownProps) => { + const { value, numberOfDecimals = 2, currency } = ownProps + const { metamask: { currentCurrency, conversionRate } } = state + + const toCurrency = currency === ETH ? ETH : currentCurrency + const convertedValue = getValueFromWeiHex({ value, toCurrency, conversionRate, numberOfDecimals }) + const formattedValue = formatCurrency(convertedValue, toCurrency) + const displayValue = `${formattedValue} ${toCurrency.toUpperCase()}` + + return { + displayValue, + } +} + +export default connect(mapStateToProps)(CurrencyDisplay) diff --git a/ui/app/components/currency-display/index.js b/ui/app/components/currency-display/index.js new file mode 100644 index 000000000..38f08765f --- /dev/null +++ b/ui/app/components/currency-display/index.js @@ -0,0 +1 @@ +export { default } from './currency-display.container' diff --git a/ui/app/components/currency-display/tests/currency-display.component.test.js b/ui/app/components/currency-display/tests/currency-display.component.test.js new file mode 100644 index 000000000..d9ef052f1 --- /dev/null +++ b/ui/app/components/currency-display/tests/currency-display.component.test.js @@ -0,0 +1,27 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import CurrencyDisplay from '../currency-display.component' + +describe('CurrencyDisplay Component', () => { + it('should render text with a className', () => { + const wrapper = shallow() + + assert.ok(wrapper.hasClass('currency-display')) + assert.equal(wrapper.text(), '$123.45') + }) + + it('should render text with a prefix', () => { + const wrapper = shallow() + + assert.ok(wrapper.hasClass('currency-display')) + assert.equal(wrapper.text(), '-$123.45') + }) +}) diff --git a/ui/app/components/currency-display/tests/currency-display.container.test.js b/ui/app/components/currency-display/tests/currency-display.container.test.js new file mode 100644 index 000000000..474ce5378 --- /dev/null +++ b/ui/app/components/currency-display/tests/currency-display.container.test.js @@ -0,0 +1,61 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' + +let mapStateToProps + +proxyquire('../currency-display.container.js', { + 'react-redux': { + connect: ms => { + mapStateToProps = ms + return () => ({}) + }, + }, +}) + +describe('CurrencyDisplay container', () => { + describe('mapStateToProps()', () => { + it('should return the correct props', () => { + const mockState = { + metamask: { + conversionRate: 280.45, + currentCurrency: 'usd', + }, + } + + const tests = [ + { + props: { + value: '0x2386f26fc10000', + numberOfDecimals: 2, + currency: 'usd', + }, + result: { + displayValue: '$2.80 USD', + }, + }, + { + props: { + value: '0x2386f26fc10000', + }, + result: { + displayValue: '$2.80 USD', + }, + }, + { + props: { + value: '0x1193461d01595930', + currency: 'ETH', + numberOfDecimals: 3, + }, + result: { + displayValue: '1.266 ETH', + }, + }, + ] + + tests.forEach(({ props, result }) => { + assert.deepEqual(mapStateToProps(mockState, props), result) + }) + }) + }) +}) diff --git a/ui/app/components/token-currency-display/index.js b/ui/app/components/token-currency-display/index.js new file mode 100644 index 000000000..6065cae1f --- /dev/null +++ b/ui/app/components/token-currency-display/index.js @@ -0,0 +1 @@ +export { default } from './token-currency-display.component' diff --git a/ui/app/components/token-currency-display/token-currency-display.component.js b/ui/app/components/token-currency-display/token-currency-display.component.js new file mode 100644 index 000000000..e992442d4 --- /dev/null +++ b/ui/app/components/token-currency-display/token-currency-display.component.js @@ -0,0 +1,54 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import CurrencyDisplay from '../currency-display/currency-display.component' +import { getTokenData } from '../../helpers/transactions.util' +import { calcTokenAmount } from '../../token-util' + +export default class TokenCurrencyDisplayContainer extends PureComponent { + static propTypes = { + transactionData: PropTypes.string, + token: PropTypes.object, + } + + state = { + displayValue: '', + } + + componentDidMount () { + this.setDisplayValue() + } + + componentDidUpdate (prevProps) { + const { transactionData } = this.props + const { transactionData: prevTransactionData } = prevProps + + if (transactionData !== prevTransactionData) { + this.setDisplayValue() + } + } + + setDisplayValue () { + const { transactionData: data, token } = this.props + const { decimals = '', symbol = '' } = token + const tokenData = getTokenData(data) + + let displayValue + + if (tokenData.params && tokenData.params.length === 2) { + const tokenValue = tokenData.params[1].value + const tokenAmount = calcTokenAmount(tokenValue, decimals) + displayValue = `${tokenAmount} ${symbol}` + } + + this.setState({ displayValue }) + } + + render () { + return ( + + ) + } +} diff --git a/ui/app/components/token-view-balance/tests/token-view-balance.component.test.js b/ui/app/components/token-view-balance/tests/token-view-balance.component.test.js new file mode 100644 index 000000000..909b4dc7f --- /dev/null +++ b/ui/app/components/token-view-balance/tests/token-view-balance.component.test.js @@ -0,0 +1,71 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import TokenBalance from '../../token-balance' +import CurrencyDisplay from '../../currency-display' +import { SEND_ROUTE } from '../../../routes' +import TokenViewBalance from '../token-view-balance.component' + +const propsMethodSpies = { + showDepositModal: sinon.spy(), +} + +const historySpies = { + push: sinon.spy(), +} + +const t = (str1, str2) => str2 ? str1 + str2 : str1 + +describe('TokenViewBalance Component', () => { + afterEach(() => { + propsMethodSpies.showDepositModal.resetHistory() + historySpies.push.resetHistory() + }) + + it('should render ETH balance properly', () => { + const wrapper = shallow(, { context: { t } }) + + assert.equal(wrapper.find('.token-view-balance').length, 1) + assert.equal(wrapper.find('.token-view-balance__button').length, 2) + assert.equal(wrapper.find(CurrencyDisplay).length, 2) + + const buttons = wrapper.find('.token-view-balance__buttons') + assert.equal(propsMethodSpies.showDepositModal.callCount, 0) + buttons.childAt(0).simulate('click') + assert.equal(propsMethodSpies.showDepositModal.callCount, 1) + assert.equal(historySpies.push.callCount, 0) + buttons.childAt(1).simulate('click') + assert.equal(historySpies.push.callCount, 1) + assert.equal(historySpies.push.getCall(0).args[0], SEND_ROUTE) + }) + + it('should render token balance properly', () => { + const token = { + address: '0x35865238f0bec9d5ce6abff0fdaebe7b853dfcc5', + decimals: '2', + symbol: 'ABC', + } + + const wrapper = shallow(, { context: { t } }) + + assert.equal(wrapper.find('.token-view-balance').length, 1) + assert.equal(wrapper.find('.token-view-balance__button').length, 1) + assert.equal(wrapper.find(TokenBalance).length, 1) + }) +}) diff --git a/ui/app/components/token-view-balance/token-view-balance.component.js b/ui/app/components/token-view-balance/token-view-balance.component.js index f74cc4926..89e9246e2 100644 --- a/ui/app/components/token-view-balance/token-view-balance.component.js +++ b/ui/app/components/token-view-balance/token-view-balance.component.js @@ -3,8 +3,9 @@ import PropTypes from 'prop-types' import Button from '../button' import Identicon from '../identicon' import TokenBalance from '../token-balance' +import CurrencyDisplay from '../currency-display' import { SEND_ROUTE } from '../../routes' -import { formatCurrency } from '../../helpers/confirm-transaction/util' +import { ETH } from '../../constants/common' export default class TokenViewBalance extends PureComponent { static contextTypes = { @@ -16,14 +17,11 @@ export default class TokenViewBalance extends PureComponent { selectedToken: PropTypes.object, history: PropTypes.object, network: PropTypes.string, - ethBalance: PropTypes.string, - fiatBalance: PropTypes.string, - currentCurrency: PropTypes.string, + balance: PropTypes.string, } renderBalance () { - const { selectedToken, ethBalance, fiatBalance, currentCurrency } = this.props - const formattedFiatBalance = formatCurrency(fiatBalance, currentCurrency) + const { selectedToken, balance } = this.props return selectedToken ? ( @@ -34,12 +32,16 @@ export default class TokenViewBalance extends PureComponent { /> ) : (
    -
    - { `${ethBalance} ETH` } -
    -
    - { formattedFiatBalance } -
    + +
    ) } diff --git a/ui/app/components/token-view-balance/token-view-balance.container.js b/ui/app/components/token-view-balance/token-view-balance.container.js index 692e6e32f..f6cdc30e1 100644 --- a/ui/app/components/token-view-balance/token-view-balance.container.js +++ b/ui/app/components/token-view-balance/token-view-balance.container.js @@ -4,29 +4,17 @@ import { compose } from 'recompose' import TokenViewBalance from './token-view-balance.component' import { getSelectedToken, getSelectedAddress } from '../../selectors' import { showModal } from '../../actions' -import { getValueFromWeiHex } from '../../helpers/confirm-transaction/util' const mapStateToProps = state => { const selectedAddress = getSelectedAddress(state) - const { metamask } = state - const { network, accounts, currentCurrency, conversionRate } = metamask + const { metamask: { network, accounts } } = state const account = accounts[selectedAddress] - const { balance: value } = account - - const ethBalance = getValueFromWeiHex({ - value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 3, - }) - - const fiatBalance = getValueFromWeiHex({ - value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2, - }) + const { balance } = account return { selectedToken: getSelectedToken(state), network, - ethBalance, - fiatBalance, - currentCurrency, + balance, } } diff --git a/ui/app/components/transaction-action/tests/transaction-action.component.test.js b/ui/app/components/transaction-action/tests/transaction-action.component.test.js new file mode 100644 index 000000000..bba997c20 --- /dev/null +++ b/ui/app/components/transaction-action/tests/transaction-action.component.test.js @@ -0,0 +1,102 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import TransactionAction from '../transaction-action.component' + +describe('TransactionAction Component', () => { + const tOrDefault = key => key + + describe('Outgoing transaction', () => { + it('should render -- when methodData is still fetching', () => { + const methodData = { data: {}, done: false, error: null } + const transaction = { + id: 1, + status: 'confirmed', + submittedTime: 1534045442919, + time: 1534045440641, + txParams: { + from: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6', + gas: '0x5208', + gasPrice: '0x3b9aca00', + nonce: '0x96', + to: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706', + value: '0x2386f26fc10000', + }, + } + + const wrapper = shallow(, { context: { tOrDefault }}) + + assert.equal(wrapper.find('.transaction-action').length, 1) + assert.equal(wrapper.text(), '--') + }) + + it('should render Outgoing', () => { + const methodData = { data: {}, done: true, error: null } + const transaction = { + id: 1, + status: 'confirmed', + submittedTime: 1534045442919, + time: 1534045440641, + txParams: { + from: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6', + gas: '0x5208', + gasPrice: '0x3b9aca00', + nonce: '0x96', + to: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706', + value: '0x2386f26fc10000', + }, + } + + const wrapper = shallow(, { context: { tOrDefault }}) + + assert.equal(wrapper.find('.transaction-action').length, 1) + assert.equal(wrapper.text(), 'outgoing') + }) + + it('should render Approved', () => { + const methodData = { + data: { + name: 'Approve', + params: [ + { type: 'address' }, + { type: 'uint256' }, + ], + }, + done: true, + error: null, + } + const transaction = { + id: 1, + status: 'confirmed', + submittedTime: 1534045442919, + time: 1534045440641, + txParams: { + from: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6', + gas: '0x5208', + gasPrice: '0x3b9aca00', + nonce: '0x96', + to: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706', + value: '0x2386f26fc10000', + data: '0x095ea7b300000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000000003', + }, + } + + const wrapper = shallow(, { context: { tOrDefault }}) + + assert.equal(wrapper.find('.transaction-action').length, 1) + assert.equal(wrapper.text(), 'approve') + }) + }) +}) diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index a47f29023..da1741731 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -3,21 +3,24 @@ import PropTypes from 'prop-types' import Identicon from '../identicon' import TransactionStatus from '../transaction-status' import TransactionAction from '../transaction-action' +import CurrencyDisplay from '../currency-display' +import TokenCurrencyDisplay from '../token-currency-display' import prefixForNetwork from '../../../lib/etherscan-prefix-for-network' import { CONFIRM_TRANSACTION_ROUTE } from '../../routes' import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions' +import { ETH } from '../../constants/common' export default class TransactionListItem extends PureComponent { static propTypes = { history: PropTypes.object, transaction: PropTypes.object, - ethTransactionAmount: PropTypes.string, - fiatDisplayValue: PropTypes.string, + value: PropTypes.string, methodData: PropTypes.object, showRetry: PropTypes.bool, retryTransaction: PropTypes.func, setSelectedToken: PropTypes.func, nonceAndDate: PropTypes.string, + token: PropTypes.object, } handleClick = () => { @@ -55,18 +58,50 @@ export default class TransactionListItem extends PureComponent { .then(id => history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)) } + renderPrimaryCurrency () { + const { token, transaction: { txParams: { data } = {} } = {}, value } = this.props + + return token + ? ( + + ) : ( + + ) + } + + renderSecondaryCurrency () { + const { token, value } = this.props + + return token + ? null + : ( + + ) + } + render () { const { transaction, - ethTransactionAmount, - fiatDisplayValue, methodData, showRetry, nonceAndDate, } = this.props const { txParams = {} } = transaction - const fiatDisplayText = `-${fiatDisplayValue}` - const ethDisplayText = ethTransactionAmount && `-${ethTransactionAmount} ETH` return (
    -
    - { fiatDisplayText } -
    -
    - { ethDisplayText } -
    + { this.renderPrimaryCurrency() } + { this.renderSecondaryCurrency() }
    { showRetry && !methodData.isFetching && ( diff --git a/ui/app/components/transaction-list-item/transaction-list-item.container.js b/ui/app/components/transaction-list-item/transaction-list-item.container.js index 209ddb9f6..47644241a 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.container.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js @@ -4,42 +4,16 @@ import { compose } from 'recompose' import withMethodData from '../../higher-order-components/with-method-data' import TransactionListItem from './transaction-list-item.component' import { setSelectedToken, retryTransaction } from '../../actions' -import { getEthFromWeiHex, getValueFromWeiHex, hexToDecimal } from '../../helpers/conversions.util' -import { getTokenData } from '../../helpers/transactions.util' -import { formatCurrency } from '../../helpers/confirm-transaction/util' -import { calcTokenAmount } from '../../token-util' +import { hexToDecimal } from '../../helpers/conversions.util' import { formatDate } from '../../util' const mapStateToProps = (state, ownProps) => { - const { metamask } = state - const { currentCurrency, conversionRate } = metamask - const { transaction: { txParams: { value, data, nonce } = {}, time } = {}, token } = ownProps - - let ethTransactionAmount, fiatDisplayValue - - if (token) { - const { decimals = '', symbol = '' } = token - const tokenData = getTokenData(data) - - if (tokenData.params && tokenData.params.length === 2) { - const tokenValue = tokenData.params[1].value - const tokenAmount = calcTokenAmount(tokenValue, decimals) - fiatDisplayValue = `${tokenAmount} ${symbol}` - } - } else { - ethTransactionAmount = getEthFromWeiHex({ value, conversionRate }) - const fiatTransactionAmount = getValueFromWeiHex({ - value, conversionRate, toCurrency: currentCurrency, numberOfDecimals: 2, - }) - const fiatFormattedAmount = formatCurrency(fiatTransactionAmount, currentCurrency) - fiatDisplayValue = `${fiatFormattedAmount} ${currentCurrency.toUpperCase()}` - } + const { transaction: { txParams: { value, nonce } = {}, time } = {} } = ownProps const nonceAndDate = nonce ? `#${hexToDecimal(nonce)} - ${formatDate(time)}` : formatDate(time) return { - ethTransactionAmount, - fiatDisplayValue, + value, nonceAndDate, } } diff --git a/ui/app/constants/common.js b/ui/app/constants/common.js new file mode 100644 index 000000000..28731ce33 --- /dev/null +++ b/ui/app/constants/common.js @@ -0,0 +1 @@ +export const ETH = 'ETH' -- cgit v1.2.3 From eb17151ff4c668ff1a99bad696cd379ffc3d8f24 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 16 Aug 2018 12:46:40 -0700 Subject: Change "Outgoing" to "Sent Ether" or "Sent Token" --- .../transaction-action/tests/transaction-action.component.test.js | 4 ++-- ui/app/constants/transactions.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/transaction-action/tests/transaction-action.component.test.js b/ui/app/components/transaction-action/tests/transaction-action.component.test.js index bba997c20..158bc8dc4 100644 --- a/ui/app/components/transaction-action/tests/transaction-action.component.test.js +++ b/ui/app/components/transaction-action/tests/transaction-action.component.test.js @@ -34,7 +34,7 @@ describe('TransactionAction Component', () => { assert.equal(wrapper.text(), '--') }) - it('should render Outgoing', () => { + it('should render Sent Ether', () => { const methodData = { data: {}, done: true, error: null } const transaction = { id: 1, @@ -58,7 +58,7 @@ describe('TransactionAction Component', () => { />, { context: { tOrDefault }}) assert.equal(wrapper.find('.transaction-action').length, 1) - assert.equal(wrapper.text(), 'outgoing') + assert.equal(wrapper.text(), 'sentEther') }) it('should render Approved', () => { diff --git a/ui/app/constants/transactions.js b/ui/app/constants/transactions.js index da1d2e590..df6c4c8a4 100644 --- a/ui/app/constants/transactions.js +++ b/ui/app/constants/transactions.js @@ -11,10 +11,10 @@ export const TOKEN_METHOD_TRANSFER = 'transfer' export const TOKEN_METHOD_APPROVE = 'approve' export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom' -export const SEND_ETHER_ACTION_KEY = 'outgoing' +export const SEND_ETHER_ACTION_KEY = 'sentEther' export const DEPLOY_CONTRACT_ACTION_KEY = 'contractDeployment' export const APPROVE_ACTION_KEY = 'approve' -export const SEND_TOKEN_ACTION_KEY = 'outgoing' +export const SEND_TOKEN_ACTION_KEY = 'sentTokens' export const TRANSFER_FROM_ACTION_KEY = 'transferFrom' export const SIGNATURE_REQUEST_KEY = 'signatureRequest' export const UNKNOWN_FUNCTION_KEY = 'unknownFunction' -- cgit v1.2.3 From 342522c6cf23670f931e69ba822eedfd2d6ee252 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 23 Aug 2018 16:44:38 -0700 Subject: Fix naming, add eth.getCode check for actions, fix translations for statuses --- ui/app/components/balance-component.js | 35 +------- .../currency-display/currency-display.component.js | 2 + .../currency-display/currency-display.container.js | 3 +- ui/app/components/index.scss | 4 +- ui/app/components/pages/home/home.component.js | 4 +- .../token-currency-display.component.js | 2 +- ui/app/components/token-view-balance/index.js | 1 - ui/app/components/token-view-balance/index.scss | 76 ----------------- .../tests/token-view-balance.component.test.js | 71 ---------------- .../token-view-balance.component.js | 94 ---------------------- .../token-view-balance.container.js | 30 ------- ui/app/components/token-view/index.js | 1 - ui/app/components/token-view/index.scss | 27 ------- .../components/token-view/token-view.component.js | 27 ------- .../transaction-action.component.js | 8 +- .../transaction-list-item.component.js | 4 +- .../transaction-status.component.js | 32 +++++--- .../components/transaction-view-balance/index.js | 1 + .../components/transaction-view-balance/index.scss | 76 +++++++++++++++++ .../tests/token-view-balance.component.test.js | 71 ++++++++++++++++ .../transaction-view-balance.component.js | 94 ++++++++++++++++++++++ .../transaction-view-balance.container.js | 30 +++++++ ui/app/components/transaction-view/index.js | 1 + ui/app/components/transaction-view/index.scss | 27 +++++++ .../transaction-view/transaction-view.component.js | 27 +++++++ ui/app/ducks/confirm-transaction.duck.js | 3 +- ui/app/helpers/confirm-transaction/util.js | 5 -- ui/app/helpers/transactions.util.js | 15 +++- 28 files changed, 378 insertions(+), 393 deletions(-) delete mode 100644 ui/app/components/token-view-balance/index.js delete mode 100644 ui/app/components/token-view-balance/index.scss delete mode 100644 ui/app/components/token-view-balance/tests/token-view-balance.component.test.js delete mode 100644 ui/app/components/token-view-balance/token-view-balance.component.js delete mode 100644 ui/app/components/token-view-balance/token-view-balance.container.js delete mode 100644 ui/app/components/token-view/index.js delete mode 100644 ui/app/components/token-view/index.scss delete mode 100644 ui/app/components/token-view/token-view.component.js create mode 100644 ui/app/components/transaction-view-balance/index.js create mode 100644 ui/app/components/transaction-view-balance/index.scss create mode 100644 ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js create mode 100644 ui/app/components/transaction-view-balance/transaction-view-balance.component.js create mode 100644 ui/app/components/transaction-view-balance/transaction-view-balance.container.js create mode 100644 ui/app/components/transaction-view/index.js create mode 100644 ui/app/components/transaction-view/index.scss create mode 100644 ui/app/components/transaction-view/transaction-view.component.js (limited to 'ui') diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js index e31552f2d..329d8b8e9 100644 --- a/ui/app/components/balance-component.js +++ b/ui/app/components/balance-component.js @@ -4,8 +4,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const TokenBalance = require('./token-balance') const Identicon = require('./identicon') -const currencyFormatter = require('currency-formatter') -const currencies = require('currency-formatter/currencies') +import CurrencyDisplay from './currency-display' const { formatBalance, generateBalanceObject } = require('../util') @@ -80,38 +79,12 @@ BalanceComponent.prototype.renderBalance = function () { style: {}, }, this.getTokenBalance(formattedBalance, shorten)), - showFiat ? this.renderFiatValue(formattedBalance) : null, + showFiat && h(CurrencyDisplay, { + value: balanceValue, + }), ]) } -BalanceComponent.prototype.renderFiatValue = function (formattedBalance) { - - const { conversionRate, currentCurrency } = this.props - - const fiatDisplayNumber = this.getFiatDisplayNumber(formattedBalance, conversionRate) - - const fiatPrefix = currentCurrency === 'USD' ? '$' : '' - - return this.renderFiatAmount(fiatDisplayNumber, currentCurrency, fiatPrefix) -} - -BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatSuffix, fiatPrefix) { - const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0 - if (shouldNotRenderFiat) return null - - const upperCaseFiatSuffix = fiatSuffix.toUpperCase() - - const display = currencies.find(currency => currency.code === upperCaseFiatSuffix) - ? currencyFormatter.format(Number(fiatDisplayNumber), { - code: upperCaseFiatSuffix, - }) - : `${fiatPrefix}${fiatDisplayNumber} ${upperCaseFiatSuffix}` - - return h('div.fiat-amount', { - style: {}, - }, display) -} - BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) { const balanceObj = generateBalanceObject(formattedBalance, shorten ? 1 : 3) diff --git a/ui/app/components/currency-display/currency-display.component.js b/ui/app/components/currency-display/currency-display.component.js index f1bb933d7..389791b42 100644 --- a/ui/app/components/currency-display/currency-display.component.js +++ b/ui/app/components/currency-display/currency-display.component.js @@ -1,11 +1,13 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' +import { ETH } from '../../constants/common' export default class CurrencyDisplay extends PureComponent { static propTypes = { className: PropTypes.string, displayValue: PropTypes.string, prefix: PropTypes.string, + currency: PropTypes.oneOf([ETH]), } render () { diff --git a/ui/app/components/currency-display/currency-display.container.js b/ui/app/components/currency-display/currency-display.container.js index b36bba52a..b8a738c65 100644 --- a/ui/app/components/currency-display/currency-display.container.js +++ b/ui/app/components/currency-display/currency-display.container.js @@ -1,13 +1,12 @@ import { connect } from 'react-redux' import CurrencyDisplay from './currency-display.component' import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util' -import { ETH } from '../../constants/common' const mapStateToProps = (state, ownProps) => { const { value, numberOfDecimals = 2, currency } = ownProps const { metamask: { currentCurrency, conversionRate } } = state - const toCurrency = currency === ETH ? ETH : currentCurrency + const toCurrency = currency || currentCurrency const convertedValue = getValueFromWeiHex({ value, toCurrency, conversionRate, numberOfDecimals }) const formattedValue = formatCurrency(convertedValue, toCurrency) const displayValue = `${formattedValue} ${toCurrency.toUpperCase()}` diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index 261d917f6..bdcb5626c 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -24,9 +24,9 @@ @import './tabs/index'; -@import './token-view/index'; +@import './transaction-view/index'; -@import './token-view-balance/index'; +@import './transaction-view-balance/index'; @import './transaction-list/index'; diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js index 65273ec8e..404f42361 100644 --- a/ui/app/components/pages/home/home.component.js +++ b/ui/app/components/pages/home/home.component.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import Media from 'react-media' import { Redirect } from 'react-router-dom' import WalletView from '../../wallet-view' -import TokenView from '../../token-view' +import TransactionView from '../../transaction-view' import { INITIALIZE_BACKUP_PHRASE_ROUTE, RESTORE_VAULT_ROUTE, @@ -58,7 +58,7 @@ export default class Home extends PureComponent { query="(min-width: 576px)" render={() => } /> - +
    ) diff --git a/ui/app/components/token-currency-display/token-currency-display.component.js b/ui/app/components/token-currency-display/token-currency-display.component.js index e992442d4..957aec376 100644 --- a/ui/app/components/token-currency-display/token-currency-display.component.js +++ b/ui/app/components/token-currency-display/token-currency-display.component.js @@ -4,7 +4,7 @@ import CurrencyDisplay from '../currency-display/currency-display.component' import { getTokenData } from '../../helpers/transactions.util' import { calcTokenAmount } from '../../token-util' -export default class TokenCurrencyDisplayContainer extends PureComponent { +export default class TokenCurrencyDisplay extends PureComponent { static propTypes = { transactionData: PropTypes.string, token: PropTypes.object, diff --git a/ui/app/components/token-view-balance/index.js b/ui/app/components/token-view-balance/index.js deleted file mode 100644 index e0509096a..000000000 --- a/ui/app/components/token-view-balance/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './token-view-balance.container' diff --git a/ui/app/components/token-view-balance/index.scss b/ui/app/components/token-view-balance/index.scss deleted file mode 100644 index b522a10f9..000000000 --- a/ui/app/components/token-view-balance/index.scss +++ /dev/null @@ -1,76 +0,0 @@ -.token-view-balance { - display: flex; - justify-content: space-between; - align-items: center; - flex: 1; - height: 54px; - - &__balance { - margin-left: 12px; - display: flex; - flex-direction: column; - - @media screen and (max-width: $break-small) { - align-items: center; - margin: 16px 0; - } - } - - &__token-balance { - margin-left: 12px; - font-size: 1.5rem; - - @media screen and (max-width: $break-small) { - margin-bottom: 12px; - font-size: 1.75rem; - } - } - - &__primary-balance { - font-size: 1.5rem; - - @media screen and (max-width: $break-small) { - margin-bottom: 12px; - font-size: 1.75rem; - } - } - - &__secondary-balance { - font-size: 1.15rem; - color: #a0a0a0; - } - - &__balance-container { - flex: 1; - display: flex; - flex-direction: row; - align-items: center; - - @media screen and (max-width: $break-small) { - flex-direction: column; - } - } - - &__buttons { - display: flex; - flex-direction: row; - - @media screen and (max-width: $break-small) { - margin-bottom: 16px; - } - } - - &__button { - min-width: initial; - width: 100px; - - &:not(:last-child) { - margin-right: 12px; - } - } - - @media screen and (max-width: $break-small) { - flex-direction: column; - height: initial - } -} diff --git a/ui/app/components/token-view-balance/tests/token-view-balance.component.test.js b/ui/app/components/token-view-balance/tests/token-view-balance.component.test.js deleted file mode 100644 index 909b4dc7f..000000000 --- a/ui/app/components/token-view-balance/tests/token-view-balance.component.test.js +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react' -import assert from 'assert' -import { shallow } from 'enzyme' -import sinon from 'sinon' -import TokenBalance from '../../token-balance' -import CurrencyDisplay from '../../currency-display' -import { SEND_ROUTE } from '../../../routes' -import TokenViewBalance from '../token-view-balance.component' - -const propsMethodSpies = { - showDepositModal: sinon.spy(), -} - -const historySpies = { - push: sinon.spy(), -} - -const t = (str1, str2) => str2 ? str1 + str2 : str1 - -describe('TokenViewBalance Component', () => { - afterEach(() => { - propsMethodSpies.showDepositModal.resetHistory() - historySpies.push.resetHistory() - }) - - it('should render ETH balance properly', () => { - const wrapper = shallow(, { context: { t } }) - - assert.equal(wrapper.find('.token-view-balance').length, 1) - assert.equal(wrapper.find('.token-view-balance__button').length, 2) - assert.equal(wrapper.find(CurrencyDisplay).length, 2) - - const buttons = wrapper.find('.token-view-balance__buttons') - assert.equal(propsMethodSpies.showDepositModal.callCount, 0) - buttons.childAt(0).simulate('click') - assert.equal(propsMethodSpies.showDepositModal.callCount, 1) - assert.equal(historySpies.push.callCount, 0) - buttons.childAt(1).simulate('click') - assert.equal(historySpies.push.callCount, 1) - assert.equal(historySpies.push.getCall(0).args[0], SEND_ROUTE) - }) - - it('should render token balance properly', () => { - const token = { - address: '0x35865238f0bec9d5ce6abff0fdaebe7b853dfcc5', - decimals: '2', - symbol: 'ABC', - } - - const wrapper = shallow(, { context: { t } }) - - assert.equal(wrapper.find('.token-view-balance').length, 1) - assert.equal(wrapper.find('.token-view-balance__button').length, 1) - assert.equal(wrapper.find(TokenBalance).length, 1) - }) -}) diff --git a/ui/app/components/token-view-balance/token-view-balance.component.js b/ui/app/components/token-view-balance/token-view-balance.component.js deleted file mode 100644 index 89e9246e2..000000000 --- a/ui/app/components/token-view-balance/token-view-balance.component.js +++ /dev/null @@ -1,94 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' -import Button from '../button' -import Identicon from '../identicon' -import TokenBalance from '../token-balance' -import CurrencyDisplay from '../currency-display' -import { SEND_ROUTE } from '../../routes' -import { ETH } from '../../constants/common' - -export default class TokenViewBalance extends PureComponent { - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - showDepositModal: PropTypes.func, - selectedToken: PropTypes.object, - history: PropTypes.object, - network: PropTypes.string, - balance: PropTypes.string, - } - - renderBalance () { - const { selectedToken, balance } = this.props - - return selectedToken - ? ( - - ) : ( -
    - - -
    - ) - } - - renderButtons () { - const { t } = this.context - const { selectedToken, showDepositModal, history } = this.props - - return ( -
    - { - !selectedToken && ( - - ) - } - -
    - ) - } - - render () { - const { network, selectedToken } = this.props - - return ( -
    -
    - - { this.renderBalance() } -
    - { this.renderButtons() } -
    - ) - } -} diff --git a/ui/app/components/token-view-balance/token-view-balance.container.js b/ui/app/components/token-view-balance/token-view-balance.container.js deleted file mode 100644 index f6cdc30e1..000000000 --- a/ui/app/components/token-view-balance/token-view-balance.container.js +++ /dev/null @@ -1,30 +0,0 @@ -import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import { compose } from 'recompose' -import TokenViewBalance from './token-view-balance.component' -import { getSelectedToken, getSelectedAddress } from '../../selectors' -import { showModal } from '../../actions' - -const mapStateToProps = state => { - const selectedAddress = getSelectedAddress(state) - const { metamask: { network, accounts } } = state - const account = accounts[selectedAddress] - const { balance } = account - - return { - selectedToken: getSelectedToken(state), - network, - balance, - } -} - -const mapDispatchToProps = dispatch => { - return { - showDepositModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER' })), - } -} - -export default compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(TokenViewBalance) diff --git a/ui/app/components/token-view/index.js b/ui/app/components/token-view/index.js deleted file mode 100644 index f49cb034f..000000000 --- a/ui/app/components/token-view/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './token-view.component' diff --git a/ui/app/components/token-view/index.scss b/ui/app/components/token-view/index.scss deleted file mode 100644 index 438147ad9..000000000 --- a/ui/app/components/token-view/index.scss +++ /dev/null @@ -1,27 +0,0 @@ -.token-view { - flex: 1 1 66.5%; - background: $white; - min-width: 0; - display: flex; - flex-direction: column; - - &__balance-wrapper { - @media screen and (max-width: $break-small) { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - flex: 0 0 auto; - padding-top: 16px; - } - - @media screen and (min-width: $break-large) { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - margin: 2.3em 2.37em .8em; - flex: 0 0 auto; - } - } -} diff --git a/ui/app/components/token-view/token-view.component.js b/ui/app/components/token-view/token-view.component.js deleted file mode 100644 index 2267047af..000000000 --- a/ui/app/components/token-view/token-view.component.js +++ /dev/null @@ -1,27 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' -import Media from 'react-media' -import MenuBar from '../menu-bar' -import TokenViewBalance from '../token-view-balance' -import TransactionList from '../transaction-list' - -export default class TokenView extends PureComponent { - static contextTypes = { - t: PropTypes.func, - } - - render () { - return ( -
    - } - /> -
    - -
    - -
    - ) - } -} diff --git a/ui/app/components/transaction-action/transaction-action.component.js b/ui/app/components/transaction-action/transaction-action.component.js index 9f447fe5c..81a1e96d0 100644 --- a/ui/app/components/transaction-action/transaction-action.component.js +++ b/ui/app/components/transaction-action/transaction-action.component.js @@ -25,7 +25,7 @@ export default class TransactionAction extends PureComponent { this.getTransactionAction() } - getTransactionAction () { + async getTransactionAction () { const { transactionAction } = this.state const { transaction, methodData } = this.props const { data, done } = methodData @@ -34,18 +34,18 @@ export default class TransactionAction extends PureComponent { return } - const actionKey = getTransactionActionKey(transaction, data) + const actionKey = await getTransactionActionKey(transaction, data) const action = actionKey && this.context.tOrDefault(actionKey) this.setState({ transactionAction: action }) } render () { - const { className, methodData: { isFetching } } = this.props + const { className, methodData: { done } } = this.props const { transactionAction } = this.state return (
    - { (!isFetching && transactionAction) || '--' } + { (done && transactionAction) || '--' }
    ) } diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index da1741731..d9e63d6e0 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -127,13 +127,13 @@ export default class TransactionListItem extends PureComponent {
    { this.renderPrimaryCurrency() } { this.renderSecondaryCurrency() }
    { - showRetry && !methodData.isFetching && ( + showRetry && methodData.done && (
    - { statusToTextHash[status] || status } +
    + { statusText }
    ) } diff --git a/ui/app/components/transaction-view-balance/index.js b/ui/app/components/transaction-view-balance/index.js new file mode 100644 index 000000000..8824737f7 --- /dev/null +++ b/ui/app/components/transaction-view-balance/index.js @@ -0,0 +1 @@ +export { default } from './transaction-view-balance.container' diff --git a/ui/app/components/transaction-view-balance/index.scss b/ui/app/components/transaction-view-balance/index.scss new file mode 100644 index 000000000..12045ab6d --- /dev/null +++ b/ui/app/components/transaction-view-balance/index.scss @@ -0,0 +1,76 @@ +.transaction-view-balance { + display: flex; + justify-content: space-between; + align-items: center; + flex: 1; + height: 54px; + + &__balance { + margin-left: 12px; + display: flex; + flex-direction: column; + + @media screen and (max-width: $break-small) { + align-items: center; + margin: 16px 0; + } + } + + &__token-balance { + margin-left: 12px; + font-size: 1.5rem; + + @media screen and (max-width: $break-small) { + margin-bottom: 12px; + font-size: 1.75rem; + } + } + + &__primary-balance { + font-size: 1.5rem; + + @media screen and (max-width: $break-small) { + margin-bottom: 12px; + font-size: 1.75rem; + } + } + + &__secondary-balance { + font-size: 1.15rem; + color: #a0a0a0; + } + + &__balance-container { + flex: 1; + display: flex; + flex-direction: row; + align-items: center; + + @media screen and (max-width: $break-small) { + flex-direction: column; + } + } + + &__buttons { + display: flex; + flex-direction: row; + + @media screen and (max-width: $break-small) { + margin-bottom: 16px; + } + } + + &__button { + min-width: initial; + width: 100px; + + &:not(:last-child) { + margin-right: 12px; + } + } + + @media screen and (max-width: $break-small) { + flex-direction: column; + height: initial + } +} diff --git a/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js b/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js new file mode 100644 index 000000000..bb95cb27e --- /dev/null +++ b/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js @@ -0,0 +1,71 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import TokenBalance from '../../token-balance' +import CurrencyDisplay from '../../currency-display' +import { SEND_ROUTE } from '../../../routes' +import TransactionViewBalance from '../transaction-view-balance.component' + +const propsMethodSpies = { + showDepositModal: sinon.spy(), +} + +const historySpies = { + push: sinon.spy(), +} + +const t = (str1, str2) => str2 ? str1 + str2 : str1 + +describe('TransactionViewBalance Component', () => { + afterEach(() => { + propsMethodSpies.showDepositModal.resetHistory() + historySpies.push.resetHistory() + }) + + it('should render ETH balance properly', () => { + const wrapper = shallow(, { context: { t } }) + + assert.equal(wrapper.find('.transaction-view-balance').length, 1) + assert.equal(wrapper.find('.transaction-view-balance__button').length, 2) + assert.equal(wrapper.find(CurrencyDisplay).length, 2) + + const buttons = wrapper.find('.transaction-view-balance__buttons') + assert.equal(propsMethodSpies.showDepositModal.callCount, 0) + buttons.childAt(0).simulate('click') + assert.equal(propsMethodSpies.showDepositModal.callCount, 1) + assert.equal(historySpies.push.callCount, 0) + buttons.childAt(1).simulate('click') + assert.equal(historySpies.push.callCount, 1) + assert.equal(historySpies.push.getCall(0).args[0], SEND_ROUTE) + }) + + it('should render token balance properly', () => { + const token = { + address: '0x35865238f0bec9d5ce6abff0fdaebe7b853dfcc5', + decimals: '2', + symbol: 'ABC', + } + + const wrapper = shallow(, { context: { t } }) + + assert.equal(wrapper.find('.transaction-view-balance').length, 1) + assert.equal(wrapper.find('.transaction-view-balance__button').length, 1) + assert.equal(wrapper.find(TokenBalance).length, 1) + }) +}) diff --git a/ui/app/components/transaction-view-balance/transaction-view-balance.component.js b/ui/app/components/transaction-view-balance/transaction-view-balance.component.js new file mode 100644 index 000000000..bdc46f714 --- /dev/null +++ b/ui/app/components/transaction-view-balance/transaction-view-balance.component.js @@ -0,0 +1,94 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Button from '../button' +import Identicon from '../identicon' +import TokenBalance from '../token-balance' +import CurrencyDisplay from '../currency-display' +import { SEND_ROUTE } from '../../routes' +import { ETH } from '../../constants/common' + +export default class TransactionViewBalance extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + showDepositModal: PropTypes.func, + selectedToken: PropTypes.object, + history: PropTypes.object, + network: PropTypes.string, + balance: PropTypes.string, + } + + renderBalance () { + const { selectedToken, balance } = this.props + + return selectedToken + ? ( + + ) : ( +
    + + +
    + ) + } + + renderButtons () { + const { t } = this.context + const { selectedToken, showDepositModal, history } = this.props + + return ( +
    + { + !selectedToken && ( + + ) + } + +
    + ) + } + + render () { + const { network, selectedToken } = this.props + + return ( +
    +
    + + { this.renderBalance() } +
    + { this.renderButtons() } +
    + ) + } +} diff --git a/ui/app/components/transaction-view-balance/transaction-view-balance.container.js b/ui/app/components/transaction-view-balance/transaction-view-balance.container.js new file mode 100644 index 000000000..1d3432b15 --- /dev/null +++ b/ui/app/components/transaction-view-balance/transaction-view-balance.container.js @@ -0,0 +1,30 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import TransactionViewBalance from './transaction-view-balance.component' +import { getSelectedToken, getSelectedAddress } from '../../selectors' +import { showModal } from '../../actions' + +const mapStateToProps = state => { + const selectedAddress = getSelectedAddress(state) + const { metamask: { network, accounts } } = state + const account = accounts[selectedAddress] + const { balance } = account + + return { + selectedToken: getSelectedToken(state), + network, + balance, + } +} + +const mapDispatchToProps = dispatch => { + return { + showDepositModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER' })), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(TransactionViewBalance) diff --git a/ui/app/components/transaction-view/index.js b/ui/app/components/transaction-view/index.js new file mode 100644 index 000000000..9eb0c3c83 --- /dev/null +++ b/ui/app/components/transaction-view/index.js @@ -0,0 +1 @@ +export { default } from './transaction-view.component' diff --git a/ui/app/components/transaction-view/index.scss b/ui/app/components/transaction-view/index.scss new file mode 100644 index 000000000..af9771ce0 --- /dev/null +++ b/ui/app/components/transaction-view/index.scss @@ -0,0 +1,27 @@ +.transaction-view { + flex: 1 1 66.5%; + background: $white; + min-width: 0; + display: flex; + flex-direction: column; + + &__balance-wrapper { + @media screen and (max-width: $break-small) { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + flex: 0 0 auto; + padding-top: 16px; + } + + @media screen and (min-width: $break-large) { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + margin: 2.3em 2.37em .8em; + flex: 0 0 auto; + } + } +} diff --git a/ui/app/components/transaction-view/transaction-view.component.js b/ui/app/components/transaction-view/transaction-view.component.js new file mode 100644 index 000000000..7014ca173 --- /dev/null +++ b/ui/app/components/transaction-view/transaction-view.component.js @@ -0,0 +1,27 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Media from 'react-media' +import MenuBar from '../menu-bar' +import TransactionViewBalance from '../transaction-view-balance' +import TransactionList from '../transaction-list' + +export default class TransactionView extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + render () { + return ( +
    + } + /> +
    + +
    + +
    + ) + } +} diff --git a/ui/app/ducks/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction.duck.js index ccb418143..30c32f2bf 100644 --- a/ui/app/ducks/confirm-transaction.duck.js +++ b/ui/app/ducks/confirm-transaction.duck.js @@ -12,10 +12,9 @@ import { addEth, increaseLastGasPrice, hexGreaterThan, - isSmartContractAddress, } from '../helpers/confirm-transaction/util' -import { getTokenData, getMethodData } from '../helpers/transactions.util' +import { getTokenData, getMethodData, isSmartContractAddress } from '../helpers/transactions.util' import { getSymbolAndDecimals } from '../token-util' import { conversionUtil } from '../conversion-util' diff --git a/ui/app/helpers/confirm-transaction/util.js b/ui/app/helpers/confirm-transaction/util.js index cbbc27666..d1a4994e4 100644 --- a/ui/app/helpers/confirm-transaction/util.js +++ b/ui/app/helpers/confirm-transaction/util.js @@ -123,8 +123,3 @@ export function roundExponential (value) { // In JS, numbers with exponentials greater than 20 get displayed as an exponential. return bigNumberValue.e > 20 ? Number(bigNumberValue.toPrecision(PRECISION)) : value } - -export async function isSmartContractAddress (address) { - const code = await global.eth.getCode(address) - return code && code !== '0x' -} diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js index e92a22e16..54df54aa8 100644 --- a/ui/app/helpers/transactions.util.js +++ b/ui/app/helpers/transactions.util.js @@ -41,8 +41,8 @@ export function isConfirmDeployContract (txData = {}) { return !txParams.to } -export function getTransactionActionKey (transaction, methodData) { - const { txParams: { data } = {}, msgParams } = transaction +export async function getTransactionActionKey (transaction, methodData) { + const { txParams: { data, to } = {}, msgParams } = transaction if (msgParams) { return SIGNATURE_REQUEST_KEY @@ -53,6 +53,12 @@ export function getTransactionActionKey (transaction, methodData) { } if (data) { + const toSmartContract = await isSmartContractAddress(to) + + if (!toSmartContract) { + return SEND_ETHER_ACTION_KEY + } + const { name } = methodData const methodName = name && name.toLowerCase() @@ -92,3 +98,8 @@ export function getLatestSubmittedTxWithNonce (transactions = [], nonce = '0x0') } }, {}) } + +export async function isSmartContractAddress (address) { + const code = await global.eth.getCode(address) + return code && code !== '0x' +} -- cgit v1.2.3 From c0e97d17393c9ce5024af8626c1bafc5d5b4efe4 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 23 Aug 2018 19:19:48 -0700 Subject: Fix tests --- .../tests/transaction-action.component.test.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/transaction-action/tests/transaction-action.component.test.js b/ui/app/components/transaction-action/tests/transaction-action.component.test.js index 158bc8dc4..218792847 100644 --- a/ui/app/components/transaction-action/tests/transaction-action.component.test.js +++ b/ui/app/components/transaction-action/tests/transaction-action.component.test.js @@ -1,10 +1,18 @@ import React from 'react' import assert from 'assert' import { shallow } from 'enzyme' +import sinon from 'sinon' import TransactionAction from '../transaction-action.component' describe('TransactionAction Component', () => { const tOrDefault = key => key + global.eth = { + getCode: sinon.stub().callsFake(address => { + console.log('CALLED') + const code = address === 'approveAddress' ? 'contract' : '0x' + return Promise.resolve(code) + }), + } describe('Outgoing transaction', () => { it('should render -- when methodData is still fetching', () => { @@ -46,7 +54,7 @@ describe('TransactionAction Component', () => { gas: '0x5208', gasPrice: '0x3b9aca00', nonce: '0x96', - to: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706', + to: 'sentEtherAddress', value: '0x2386f26fc10000', }, } @@ -58,6 +66,7 @@ describe('TransactionAction Component', () => { />, { context: { tOrDefault }}) assert.equal(wrapper.find('.transaction-action').length, 1) + wrapper.setState({ transactionAction: 'sentEther' }) assert.equal(wrapper.text(), 'sentEther') }) @@ -83,7 +92,7 @@ describe('TransactionAction Component', () => { gas: '0x5208', gasPrice: '0x3b9aca00', nonce: '0x96', - to: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706', + to: 'approveAddress', value: '0x2386f26fc10000', data: '0x095ea7b300000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000000003', }, @@ -96,6 +105,7 @@ describe('TransactionAction Component', () => { />, { context: { tOrDefault }}) assert.equal(wrapper.find('.transaction-action').length, 1) + wrapper.setState({ transactionAction: 'approve' }) assert.equal(wrapper.text(), 'approve') }) }) -- cgit v1.2.3